1. 为什么需要多密钥:先把自己的困境说清楚
先说我自己的情况。我同时维护着两个 GitHub 账号(一个工作号、一个个人号),一个 GitLab 账号,还有公司内网一台自建的 GitLab。以前我图省事,把所有仓库的 SSH 公钥全部配到同一个密钥上,结果某天公司安全策略要求强制轮换密钥,内网 GitLab 也换了密钥算法,我的 ~/.ssh 目录里开始堆出 id_rsa、id_rsa_work、id_rsa_personal、id_ed25519_gitlab 一堆文件,然后噩梦就来了——git push 的时候,SSH 总是先尝试第一个密钥,权限被拒了才轮到下一个,经常被 GitHub 直接回一句 Permission denied (publickey),或者提示 git@github.com: Permission denied (publickey).。
这种问题在圈子里太常见了。你随手一搜,满地都是“GitHub 多账号配置”的帖子,但大多数帖子只解决了某一半问题:要么只告诉你不同域名用不同密钥,要么只告诉你用 git config --global core.sshCommand 改密钥,碰到“同一台机器上多个 GitHub 账号”“公司仓库和个人仓库混在同一个目录树里”这种稍微复杂点的场景,就没人能讲明白了。
这篇实战笔记想做的事很简单:把 SSH 客户端真正的工作机制讲透,然后给出三套可落地的配置方案——不同域名、不同仓库、不同目录,分别对应什么思路,配置文件怎么写,踩坑点在哪。适合手上已经有多把 SSH 密钥、被 Permission denied 折磨过、或者马上要配多账号 Git 环境的同学。读完你能直接照着抄,并且知道为什么这么写。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SSH 客户端的选择逻辑:你以为的匹配和实际机制差距很大
很多人配置多密钥失败的根源,不是配置文件写错,而是根本不知道 SSH 客户端是怎么决定“用哪个密钥”的。这里必须先把底层逻辑掰开揉碎讲清楚。
2.1 Host 匹配是第一道关口
当你执行 git clone git@github.com:user/repo.git 时,OpenSSH 客户端会读取 ~/.ssh/config,把命令里的 github.com 拿去做匹配。匹配的关键不是看 URL 里的完整域名,而是看 Host 字段——这个字段可以写完整域名,也可以写一个你自己起的别名。
比如下面的配置:
text复制Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
当 SSH 解析 git@github.com 时,它会逐行扫描 config 文件,找到第一条 Host 能匹配 github.com 的条目,然后把这个条目下的所有参数加载进来。这里有个很多人不知道的细节:SSH 的配置匹配是“第一条命中的生效”,不是“后面命中的覆盖前面”。也就是说,config 文件里写在前面的匹配规则优先级最高,一旦匹配上,后面的同类型规则就不再生效了。
这也解释了为什么很多人会把密钥配错:如果你在 config 文件前面写了一个 Host * 的通用规则,并且指定了 IdentityFile ~/.ssh/id_rsa,那后面针对 github.com 写的独立规则反而可能被通用规则抢先——因为 Host * 也能匹配 github.com。所以多密钥配置的第一条军规是:通用规则放最后,具体规则放前面。
2.2 IdentityFile 与 ssh-agent 的优先级之争
第二个关键机制是密钥的候选顺序。当一个 Host 条目里写了多个 IdentityFile,或者你同时启用了 ssh-agent(绝大多数桌面系统默认会开启),SSH 客户端尝试密钥的顺序大致是:
- 先看 config 条目里显式声明的
IdentityFile; - 再看 ssh-agent 里已经加载的密钥列表;
- 如果都没成功,再看默认位置的默认文件名(
~/.ssh/id_rsa、~/.ssh/id_ed25519等)。
注意,这个“尝试”是逐个来的:第一个密钥认证失败,服务端返回 Permission denied,客户端再拿下一个密钥试。问题在于,GitHub 这类服务端对失败尝试有次数限制,如果一次性试了五六个密钥,很可能在正确的密钥出现之前,服务端已经把你暂时拉黑了,你会看到 git@github.com: Permission denied (publickey) 的同时,浏览器里 GitHub 的安全日志还会多一条失败记录。
所以这里必须提到一个极易被忽略的参数:IdentitiesOnly。
text复制Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
IdentitiesOnly yes 的意思是:我只用 config 里显式指定的 IdentityFile,不要去 ssh-agent 里翻其他密钥,也不要去默认位置乱找。这个参数是解决多密钥冲突的关键开关。没有它,即使你 config 写对了,ssh-agent 里如果加载了别的密钥,客户端依然会拿出去试一通,既浪费时间又可能触发服务端的失败计数。
2.3 关键参数速查:配置前先弄清楚每个参数干什么
下面这几个参数在做多密钥管理时一定会用到,建议先有个整体概念:
| 参数 | 作用 | 我的建议 |
|---|---|---|
Host |
匹配别名,可以是域名也可以是自定义名称 | 具体条目用实际域名或别名,通配符规则放最后 |
HostName |
实际连接的服务器域名/IP | 只有 Host 是别名时才必须写 |
User |
登录用户名 | Git 场景固定为 git |
IdentityFile |
指定私钥路径 | 显式写到具体密钥文件 |
IdentitiesOnly |
只 |
