前两天有朋友发消息问我,说自己明明 GitHub 账号密码都能登录网页,但 git push 到仓库的时候始终报 Permission denied (publickey),网上搜了半天也没看明白,发过来的截图里一半是英文报错,一半是各种看不懂的 ssh-keygen 参数。这个场景我太熟悉了,GitHub 的 SSH 配置不复杂,但它卡人的地方不在“配置”本身,而在很多人没搞懂 SSH 到底在验证什么——你以为是密码不对,其实是本地根本没有能证明“你是你”的钥匙。
这篇文章我把 GitHub SSH 配置从原理到实操重新梳理一遍。不管你是刚接触 GitHub 的新手,还是已经在用 HTTPS 但受不了每次输凭证的老用户,又或者想在一台电脑上同时管理 GitHub、GitLab、服务器多套密钥,按下面的顺序操作一遍基本都能跑通。文末我还会把这些年真正踩过的几个坑单独列出来,那些才是官方文档和大多数教程不会跟你讲明白的细节。
1. 先搞懂一件事:SSH 认证和“密码”没有任何关系
1.1 HTTPS 和 SSH 两种连接方式的本质区别
GitHub 给用户提供了两种远程仓库的交互协议:HTTPS 和 SSH。很多人默认用 HTTPS,因为 clone 的时候复制链接最方便,但从来没细想过两者背后的认证机制完全不一样。
HTTPS 方式下,Git 会把请求发送到 GitHub 服务器,由服务器验证你的身份,验证通过后才有权限读写仓库。早期可以直接用账号密码,后来 GitHub 出于安全考虑,基本禁止了纯密码访问,必须用 Personal Access Token(个人访问令牌)替代。所以“HTTPS + 密码”这条路在今天几乎走不通,这就是为什么很多人在 HTTPS 推送时输入密码却反复失败的直接原因。
SSH 方式则不同。它的验证逻辑是“证明你持有一把钥匙”:你的电脑上保存了一把私钥,GitHub 账号里保存了对应的公钥。每次连接时,服务器用公钥生成一个只有对应私钥才能解开的挑战,客户端解开了,身份就验证通过。整个过程不涉及任何账号密码,也不需要每次都输入凭据。
这里有一个非常关键的认知转换:SSH 验证的是“这台电脑 + 这把私钥”,而不是“GitHub 账号 + 密码”。很多人在 A 电脑上配置好 SSH 后,换到 B 电脑又报 Permission denied,原因就是 B 电脑上没有那把能对上号的私钥。理解了这一点,后面所有排查思路都会变得清晰。
1.2 SSH 目录里每个文件是干什么用的
配置 SSH,本质上是往 ~/.ssh 目录里放几样东西。我们拿最常见的 Linux/macOS 环境举例,Windows 上路径一般在 C:\Users\你的用户名\.ssh,结构是一样的:
| 文件/目录 | 作用 | 保密程度 |
|---|---|---|
id_ed25519 |
私钥,你的身份凭证核心 | 绝密,绝不能外传 |
id_ed25519.pub |
公钥,可以放心提交给 GitHub | 公开 |
known_hosts |
记录已确认过指纹的主机,防止中间人攻击 | 不必保密 |
config |
配置不同主机使用哪把私钥,多账号场景的关键 | 可以公开 |
你可以把 ~/.ssh 想象成一个钥匙圈,公钥是钥匙的“锁芯图纸”,可以复印给别人;私钥是真正能开锁的铁钥匙,丢了就意味着任何拿到它的人都能冒充你访问仓库。GitHub 网站后台填的是那份“锁芯图纸”,本地要保住的是“铁钥匙”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从生成密钥到测试连通:一次跑通 GitHub SSH 配置
2.1 选算法与生成命令
现代 OpenSSH 客户端建议直接用 Ed25519 算法,生成的密钥短、安全性高、验证速度快,而且是 GitHub 官方明确支持的格式。打开终端执行下面这条命令:
bash复制ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519
解释一下每个参数的含义:-t 指定密钥算法类型,这里用的 ed25519;-C 是注释,通常填你的邮箱,它会被写进公钥文件的末尾,方便你在 GitHub 后台一眼看出这把钥匙是用来干什么的;-f 指定生成密钥的文件路径。如果不加 -f,程序会交互式询问你保存在哪里,回车用默认路径也完全没问题。
如果你的团队或公司服务器还在用比较老的 OpenSSH 版本,或者某些内部代码托管平台不支持 Ed25519 算法,可以退一步用 RSA 算法,命令改成:
bash复制ssh-keygen -t rsa -b 4096 -C "you@example.com" -f ~/.ssh/id_rsa
-b 4096 指密钥长度,RSA 场景下低于 2048 位已经不够安全,4096 是稳妥选择。
2.2 passphrase 到底要不要设置
执行 ssh-keygen 后,命令行会提示你输入 passphrase,英文好的朋友可能已经猜到了,这就是“私钥的解锁密码”。很多教程会建议直接留空,因为这样免密登录才真正“免密”。但我个人的习惯是:本机单用户使用可以留空,携带笔记本出门或者电脑可能被他人接触时一定要设。
设了 passphrase 并不会让你每次推送都输入密码——现代系统的 ssh-agent 会在解锁一次后把私钥驻留在内存里,后续连接自动使用。我在 macOS 上配合钥匙串存储,基本做到重启后第一次连接才解锁一次,之后全程无感。从安全角度说,哪怕私钥文件被拷贝走了,没有 passphrase 对方也解不开,相当于多了一道保险锁。
2.3 把公钥内容添加到 GitHub 账号
生成完成后,公钥文件是 id_ed25519.pub。查看内容的命令在不同系统上略有差异:
- macOS:
pbcopy < ~/.ssh/id_ed25519.pub - Windows(PowerShell):
clip < $env:USERPROFILE\.ssh\id_ed25519.pub - Linux:
xclip -sel clip < ~/.ssh/id_ed25519.pub(需要先安装 xclip)
用 cat ~/.ssh/id_ed25519.pub 直接看到内容后手动复制也可以。复制的时候记得把整行内容全部选中,包括开头的 ssh-ed25519 前缀和结尾的邮箱注释,缺了哪一段都会导致校验失败。
然后登录 GitHub,进入 Settings → SSH and GPG keys → New SSH key,标题栏随便填一个你能认出来的名字,比如“My MacBook Pro”,Key 文本框里粘贴公钥内容,最后点绿色按钮保存。
2.4 测试连接,确认认证成功
保存完公钥,回到终端执行下面的命令:
bash复制ssh -T git@github.com
第一次连接时,SSH 会提示你确认 GitHub 服务器的主机指纹。这里不要直接输 yes 了事,建议先到 GitHub 官方文档里比对当前的主机指纹信息,确认无误再确认。确认之后 configured,known_hosts 会自动记录这台主机,以后不会再重复提示。
如果一切正常,你会看到类似这样的输出:
code复制Hi yourusername! You've successfully authenticated, but GitHub does not provide shell access.
看到 successfully authenticated 就说明身份验证已经通过。这里有个很多人会惊讶的点:明明自己没输任何账号,GitHub 却能叫出你的用户名,靠的就是公钥哈希换算出来的身份关联。
2.5 把已有仓库的 remote 地址切换成 SSH
很多人是先 clone 了 HTTPS 链接的仓库,后来才配置 SSH,这时候需要把本地的远端地址切换一下:
bash复制git remote set-url origin git@github.com:你的用户名/仓库名.git
用 git remote -v 可以确认是否切换成功。不必把 HTTPS 地址彻底忘掉,有些网络环境下 HTTPS 比 SSH 更稳,但日常高频操作推荐 SSH,一旦配好确实能省掉一堆重复认证的麻烦,尤其是“想要往 GitHub 上传整个文件夹”这种场景,只需要 git add、git commit、git push 三步,中间不会被任何凭证输入打断。
3. SSH config 文件与多账号场景:一台电脑管理 GitHub、GitLab 和服务器
3.1 默认行为下多个密钥会互相干扰
如果你只用一个 GitHub 账号、一台电脑,前面两节的配置已经足够。但现实里很多人手上有多个代码托管平台的账号:公司的 GitLab、自己的 GitHub、还有几台需要免密登录的 Linux 服务器。如果每个平台都按默认路径生成密钥,第二个密钥就会把第一个覆盖掉,或者 SSH 协议在连接时不知道该用哪把钥匙,默认拿 id_ed25519 去试,结果自然是认证失败。
这个问题的标准解法就是 ~/.ssh/config 文件。它相当于一个“连接路由表”,告诉 SSH 客户端:当你连接哪个主机地址时,应该使用哪个用户、哪把私钥,以及是否需要额外参数。
3.2 一个可直接套用的多账号配置模板
下面这个配置我用了挺长时间,覆盖了最常见的几类需求:
code复制Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host gitlab.company.com
HostName gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
IdentitiesOnly yes
Host myserver
HostName 192.168.1.100
User root
IdentityFile ~/.ssh/id_ed25519_server
几个字段逐个说明:Host 是你在命令行/工具里看到的名字,可以起别名;HostName 是真实的服务器地址;User 指定登录用户名,对 GitHub 和 GitLab 这类代码托管平台固定是 git;IdentityFile 指向这把连接要用的私钥;IdentitiesOnly yes 的意思是只使用配置里指定的密钥,不要自作主张去试别的密钥,这个选项在多账号场景下特别重要,能避免 ssh-agent 里同时存在多把钥匙时的混乱局面。
有了这份配置,连接 GitHub 时自动用 id_ed25519_github,连接公司 GitLab 时自动用 id_ed25519_gitlab,连接自己的服务器时则直接 ssh myserver 就免密登录了,原理上就是把默认的 22 端口请求分发到不同的身份认证流程里去。
3.3 不同操作系统间的差异
- Linux/macOS:
~/.ssh/config的路径和行为基本一致,配置文件权限建议设为600,即只有当前用户可读写。macOS 还可以在 ssh-add 时加上--apple-use-keychain参数,把 passphrase 存进系统钥匙串,重启后不用重新解锁。 - Windows 10/11:系统自带 OpenSSH 客户端,默认读
C:\Users\你的用户名\.ssh\config。确保 Windows 服务里“OpenSSH Authentication Agent”服务已启动并设为自动,否则ssh-add可能报无法连接代理的错误。 - GitHub Desktop:它本身可以走系统 SSH 配置。只要
config文件写好了,Desktop 推送时也会继承这套认证配置,不需要额外设置。
3.4 群晖这类 NAS 设备上怎么配
“群晖上配置好 ssh 密钥”是很多人搜索过的需求,我在自己机器上也配过一遍,流程和普通 Linux 服务器基本一致:先在群晖控制面板里开启 SSH 服务,然后用 ssh-copy-id 或者手动把公钥追加到目标用户家目录的 ~/.ssh/authorized_keys 文件里。这里有个细节值得注意:authorized_keys 文件的权限必须是 600,.ssh 目录权限必须是 700,权限太宽松的话即使公钥正确,sshd 也会出于安全策略拒绝加载。
authorized_keys 的追加命令是这样的:
bash复制cat ~/.ssh/my_key.pub | ssh admin@nas_address "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
4. 排查实录:从 Permission denied 到 VSCode 连不上,问题到底出在哪
4.1 最常见的报错是这四种原因
很多网上教程喜欢直接给答案,但实际排查过程中四个方向必须按照优先级逐个确认。我整理了一张查错顺序表,按从上到下的顺序执行,百分之九十的问题十分钟内能定位:
| 排查步骤 | 所需命令 | 预期结果 |
|---|---|---|
| 确认本地私钥存在且被 ssh-agent 加载 | ssh-add -l |
能列出至少一个私钥路径 |
| 确认远端地址是 SSH 形式 | git remote -v |
看到 git@github.com 开头 |
| 确认 GitHub 后台公钥和本地公钥一致 | 对比 cat ~/.ssh/id_ed25519.pub 与网页内容 |
完全一致 |
| 确认 SSH 连接输出 | ssh -vT git@github.com |
在 verbose 输出中定位失败环节 |
ssh -vT 里的 -v 是 verbose 模式,会打印整个连接握手过程的日志,这是排查信息量最大的命令。报错回溯到“能读到私钥但认证失败”,基本就是公钥不匹配或账号关联问题;报错停留在“连接超时”,那就要先看网络层连通性问题,而不是一味地重试密钥。
4.2 文件目录权限导致的神秘失败
一个非常隐蔽的坑是 ~/.ssh 目录权限过于宽松。OpenSSH 客户端在检测到私钥文件可以被其他用户读取时,会为了安全直接拒绝使用这把私钥,即使你明明把它配置成了 IdentityFile。标准权限应该是:
bash复制chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
Windows 上虽然没有 Unix 那种严格的权限位,但如果你用 WSL(Windows 子系统)执行 SSH 相关命令,同样要遵守这套权限规则,否则会报 Permissions too open 或 Bad owner or permissions 之类的错误。
4.3 VSCode Remote-SSH 连不上、一直弹密码怎么办
“vscode ssh插件配置密码”是高频搜索词,但实际上很多人想要的并不是配密码,而是免密。VSCode 的 Remote-SSH 插件本质上是读取你本地 ~/.ssh/config 文件,再调用系统 SSH 客户端建立连接。如果你本地能 ssh servername 免密登录,VSCode 里通常也能直接免密连上;如果 VSCode 一直弹密码,问题几乎都出在以下三点:
~/.ssh/config里没写对IdentityFile,VSCode 不知道用哪把私钥;- ssh-agent 没启动或者私钥没加载,插件找不到可用的本地密钥;
- config 里
Host别名和实际地址不一致,比如你写了Host myserver,连的时候用的是root@192.168.1.100,两边匹配不上。
排查方式也很简单:先在终端里执行 ssh myserver 确认命令行能通,再打开 VSCode 命令面板找 Remote-SSH: Connect to Host,选择列表里出现的别名。如果列表里没有,说明它没有加载到你默认的 config 路径,需要检查插件设置里的 SSH 配置文件路径是否指向了正确文件。
4.4 公司 GitLab 平台上的 SSH 密钥配置差异
GitLab 的 SSH 密钥添加入口和 GitHub 很像,在 Preferences → SSH Keys 页面。有个容易忽略的区别是 GitLab 支持给同一把公钥设置“过期时间”,如果配置完一段时间后突然失效,可以去后台看密钥是不是过期了。另外很多企业内部 GitLab 用的不是默认 22 端口,这时需要在 config 里增加 Port 字段,或者在 clone 地址里显式写出端口号,否则无论密钥怎么配都会连接失败。
5. 一些掏心窝的实战建议:配好 SSH 之后怎么少走弯路
5.1 给私钥做好备份,但别放进仓库
私钥丢失之后,你能做的事情只有重新生成密钥、删除旧公钥、再添加新公钥,操作倒是不难,但所有 clone 过你旧仓库本地 remote 配置都要更新一遍,非常烦。建议在生成密钥后立刻把 id_ed25519 和 id_ed25519.pub 用加密方式备份到安全的地方,比如密码管理器里。
这里也提醒一句:千万别为了图方便把私钥文件提交到 GitHub 仓库里。我见过真实的公开仓库事故,开发者把 ~/.ssh/id_rsa 直接 git add 进去了,之后 GitHub 自动扫描检测到泄露并给账号发了安全告警,处理起来极其麻烦。私钥一旦公开,就要默认为已经泄露,必须立刻在后台删掉对应公钥并重新生成。
5.2 先写 config 文件再生成新密钥
多账号场景下,正确顺序是先想好密钥文件名(比如 id_ed25519_github、id_ed25519_gitlab),然后生成密钥时直接用 -f 参数指定文件名,最后再写 config 引用这些路径。不要先按默认路径生成了一对密钥,写到 config 里才发现 IdentityFile 路径对不上,回头又要重新生成,白白浪费时间。
5.3 优先用 SSH 测试连接来验证是否真的配好
每次配置完新密钥、新账号或者新服务器,我的习惯都是第一时间执行一次对应的 SSH 测试连接命令。GitHub 是 ssh -T git@github.com,GitLab 是 ssh -T git@gitlab.company.com,服务器则直接 ssh 别名 看是否免密登录成功。这一步能暴露百分之八十的配置问题,而且是在终端里可以立刻得到反馈的,比直接跑去 git clone 再等报错高效得多。
5.4 SSH 连接 GitHub 时偶发超时,先查网络环境
有时候你会在某个网络环境下发现 SSH 连接 GitHub 很慢甚至超时,而 HTTPS 却能正常访问。这种情况通常不是密钥配置的问题,而是本机到 GitHub 服务器 22 端口的连通性受到了网络环境影响,比如本地运营商或公司防火墙策略限制。GitHub 官方已经提供了替代思路:使用 ssh.github.com 的 443 端口来建立 SSH 连接,这是官方支持的方案,可以在 ~/.ssh/config 里配一段:
code复制Host github.com
HostName ssh.github.com
Port 443
User git
IdentityFile ~/.ssh/id_ed25519
这样就不需要依赖 22 端口,在网络受限的环境下也能正常推拉代码。如果这一步也解决不了,再去检查本地 DNS 解析,单纯改 hosts 文件或调整 DNS 设置属于常规网络排查手段。
5.5 定期清理 known_hosts 和不再使用的公钥
随着时间推移,known_hosts 会积累大量旧指纹记录。如果某天 GitHub 更换了服务器 host key,本地会因为记录不匹配而拒绝连接,此时只要删除 known_hosts 里对应的一行或整个文件,重新连接确认指纹即可。GitHub 后台的 SSH keys 列表也值得偶尔检查,不用的老公钥该删就删,账号安全不在于公钥数量多,而在于每一把都能追溯到具体的设备和用途。
配置 SSH 这件事,说到底就是在“身份”和“连接”之间建立一套清晰的对应关系。前几次操作可能会因为不熟悉而踩到权限、路径、格式这些细节的坑,但只要按“生成密钥 → 填入公钥 → config 分流 → 测试连接”这个链路走一遍,后面基本是一劳永逸。我现在新换一台电脑,从安装 Git 到成功推送首个 commit,不到五分钟就能全部搞定,省下来的时间用来写代码比什么都划算。
