一台电脑配置多个Git账号:SSH多密钥与config全攻略
1. 为什么很多人一台电脑只能配好一个Git账号1.1 默认SSH密钥一把钥匙的困境我之前帮团队搭Git托管环境碰到一个几乎人人都会问的问题“我已经在GitHub上配好了SSH密钥为什么换到Gitee就推不上去”或者更常见的场景——公司电脑上先用了公司绑定的GitLab账号自己私下想在同一个环境里推几个GitHub项目怎么配都报错。先说这是怎么回事。Git安装之后如果你直接用ssh-keygen一路回车生成密钥系统会在~/.ssh/目录下创建一对默认密钥文件私钥id_rsa和公钥id_rsa.pub。所有需要走SSH协议的Git操作默认都只会拿着这一把私钥去认证。你把它配到GitHub上GitHub认了再把它配到Gitee上Gitee也认了。表面看“一把钥匙通吃”好像挺方便但实际上你正在给自己埋雷。问题出在身份归属上。GitHub收到你的commit会根据提交人邮箱判断作者而SSH只是负责“证明这台电脑是你这台电脑”。当你把同一把公钥同时挂到GitHub和Gitee又或者挂到公司内网GitLab后端系统里记录的都是一条“来自同一个密钥”的记录。一旦某一天你想撤销某个平台上的访问权限你只能把这把全局公钥删掉其他平台全部跟着失效。这在公司环境中尤其危险——离职交接时公司管理员把你的公钥从GitLab一删你连个人GitHub都一起断开了。1.2 把同一把公钥复制到多个平台短期能用但后患不少我见过不少教程让你“把公钥内容粘贴到GitHub再粘贴到Gitee”这种操作在技术层面确实能通但属于典型的“能跑但别这么干”安全边界完全丧失。一把私钥等于你所有Git平台的通行证。私钥文件一旦泄露攻击者只要扫一遍公钥在哪些平台挂过就能逐一登进去。想单独保护某个平台做不到。账号多了之后无法灵活切换。比如你在GitHub上有个人号和公司号两个账号同一个公钥只能配置在一个账号下。你再怎么在本地切换git config user.emailSSH认证那一步就会先把你拦住——因为服务器端根本分不清你想以哪个身份连接。排查困难。哪天某个平台的推送开始报Permission denied (publickey)你连是哪把钥匙出的问题都看不出来只能把所有密钥全删了重来。我在实际环境中给同事排查过这类问题最后统一改成多密钥方案之后问题基本是一次性根治。正确的做法其实很简单为每个平台或每类身份生成独立的密钥对再通过一个SSH配置文件告诉系统“连接哪个域名时用哪把钥匙”。这套方案对GitHub、Gitee、GitLab、自建Gitea全部通用本质上和操作系统层面的SSH客户端路由规则相关和具体Git平台没太大关系。1.3 正确的全景图多密钥 一个路由配置文件把方案拆开看一共三个组件多个密钥对每个密钥对服务于一个平台或一个账号命名上就能区分比如id_ed25519_github、id_ed25519_gitee。一个SSH config文件~/.ssh/config里写清楚“当连接github.com时使用id_ed25519_github连接gitee.com时使用id_ed25519_gitee”。本地Git仓库的作者身份隔离SSH解决了“这台电脑是谁”的问题git config user.name和user.email还要解决“提交记录上署名是谁”的问题。两个一起配好才算真正做完多账号管理。下文我从原理到实操一步步拆先讲SSH认证时客户端到底怎么选择密钥再给你可以直接抄走的配置模板最后附上我自己调试过的排查思路和同一平台多个账号的进阶玩法。2. SSH密钥认证流程拆解搞清楚连接时到底发生了什么2.1 一次git push背后客户端如何“出示”密钥如果你想真正搞懂多密钥配置必须先理解SSH认证时客户端和服务端的交互过程。它不是你想象中“拿着钥匙直接开门”那么直白更接近一次身份校验谈判你执行git pushGit底层调用SSH连接远程服务器默认走22端口。服务器端返回“我支持哪些公钥认证方式并且我这边存有哪些公钥”的信息。客户端收到后开始在本机查找可用的私钥。OpenSSH的默认行为是尝试~/.ssh/id_rsa、~/.ssh/id_ecdsa、~/.ssh/id_ed25519这些默认文件名。客户端用找到的私钥生成签名发给服务器服务器用自己存的公钥去验签。验签通过认证完成Git才开始传输数据。第3步是重点如果服务器有自己的公钥列表OpenSSH通常会按顺序尝试本地密钥直到某个私钥对应的公钥正好在服务器列表里。当你只有一套默认密钥时这个过程“刚好能用”——因为那个默认密钥就是你唯一能出的牌。可一旦你加入了第二个平台、第二个账号本地多了好几把私钥顺序尝试就不一定命中目标了可能先拿A密钥去试GitHubGitHub不认SSH就直接报错根本不会自动“换一把再试”。很多人在这里有个误解以为把多把私钥都放到~/.ssh/目录下系统就能自动“见哪个平台用哪个钥匙”。实际上OpenSSH没有这种智能识别能力它需要一份明确的“路由表”。这份路由表就是~/.ssh/config。2.2 密钥文件的权限与known_hosts两个常常被忽略的关卡在进入正式配置之前还有两个前置细节经常把人绊倒我单独拎出来说。第一个是文件权限。在Linux和macOS环境下OpenSSH对密钥文件权限非常敏感。私钥文件必须仅限当前用户读写目录本身也最好不要让其他用户有写权限。我曾经把~/.ssh整个打包复制到新电脑结构完全一致但就是报Bad owner or permissions最后发现是目录权限变成了755。标准姿势是chmod 700 ~/.ssh chmod 600 ~/.ssh/id_ed25519_github chmod 600 ~/.ssh/id_ed25519_gitee chmod 644 ~/.ssh/config # 配置文件可以读取但最好也别放开写权限第二个是known_hosts。第一次连接某个SSH服务器时系统会提示The authenticity of host github.com cant be established问你是否继续连接。你输入yes之后服务器指纹会被记录到~/.ssh/known_hosts。这个文件的作用是防止中间人攻击——下次再连同一个域名时系统会比对指纹是否一致。如果你之前已经用某个IP或域名连过GitHub后来把远程仓库URL改成另一个Host别名指纹不一致就会触发REMOTE HOST IDENTIFICATION HAS CHANGED报错。这时候手动清掉对应的旧指纹即可ssh-keygen -R github.com提示这条命令只清理“已知主机”记录不会动你的密钥文件放心执行。2.3 为什么“多放几把钥匙”不能解决问题config才是关键把原理说透之后你就能明白为什么网上有人建议“生成多把密钥放目录下就能用”其实不严谨。OpenSSH默认扫描的是那几个固定文件名的密钥你额外生成的id_ed25519_github、id_ed25519_gitee它并不会自动加载。你必须让SSH知道“哪个主机用哪把钥匙”。~/.ssh/config就是干这个的。它的核心是一组匹配规则每个规则块以Host开头后面跟上后续判断用到的条件然后写相应的连接参数。SSH客户端每次建立连接时从第一个规则块开始匹配命中即使用该规则块里的参数。这个文件本身就是纯文本改完即刻生效不需要重启任何服务。理解了这一点后面的实操内容就顺理成章了。3. 实操第一步同一台电脑生成GitHub与Gitee两套密钥3.1 环境确认与目录准备在动手之前先确认本机Git和SSH客户端都正常。直接在终端执行git --version ssh -V能看到版本号就说明环境没问题。如果Git还没装先去官网下载安装包Windows下记得安装时勾选“ADD Git to Windows PATH”macOS用户如果没装Homebrew可以直接用系统自带的Git或安装官方pkg包。这里不展开讲了安装细节不是重点。然后查看~/.ssh目录是否存在ls -la ~/.ssh如果目录不存在先创建并设置权限mkdir -p ~/.ssh chmod 700 ~/.ssh3.2 生成两把互不干扰的密钥密钥算法我推荐用ed25519它生成的密钥文件短、生成快、安全性高GitHub和Gitee现在都支持。如果某些老旧的内网Git服务只支持RSA你再退回去用rsa -b 4096。生成命令如下# 为 GitHub 生成密钥 ssh-keygen -t ed25519 -C your-github-emailexample.com -f ~/.ssh/id_ed25519_github # 为 Gitee 生成密钥 ssh-keygen -t ed25519 -C your-gitee-emailexample.com -f ~/.ssh/id_ed25519_gitee关键点是-f参数它指定了密钥文件的保存路径和文件名。如果你不写-f命令会一路默认生成到id_ed25519两把密钥就互相覆盖了——这正是大多数人踩的第一个坑。执行过程中会提示你设置passphrase也就是使用私钥时的额外口令。我建议公司电脑或个人主力机上不设口口令方便日常推送如果电脑有被他人使用的可能则设置一个配合下面要讲的ssh-add缓存机制不会频繁打扰你。生成之后检查一下目录ls -l ~/.ssh你应该能看到四个文件两把私钥无.pub后缀和两把公钥.pub后缀。私钥永远不要外发公钥就是要粘贴到Git平台上的内容。3.3 把公钥分别添加到GitHub和Gitee查看公钥内容的方法有很多Windows下可以用cat ~/.ssh/id_ed25519_github.pubmacOS下也可以直接pbcopy ~/.ssh/id_ed25519_github.pub复制到剪贴板。公钥内容形如ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... your-github-emailexample.com注意要把整行都复制完整包括开头的ssh-ed25519和末尾的注释。GitHub添加流程登录GitHub → 右上角头像 → Settings → SSH and GPG keys → New SSH key → Title随便填建议填“Home PC - GitHub”方便以后识别是哪台机器的钥匙→ Key type选 Authentication Key → 把公钥粘贴进去 → Add SSH key。Gitee添加流程登录Gitee → 右上角头像 → 设置 → 安全设置 → SSH公钥 → 标题随便填 → 把公钥粘贴进去 → 确定。加完之后可以先做一个快速验证ssh -T gitgithub.com ssh -T gitgitee.com正常情况下GitHub会返回Hi yourname! Youve successfully authenticated, but GitHub does not provide shell access.Gitee会提示认证成功。如果你现在就在这里遇到了Permission denied (publickey)别急着往下看先去第五章的排查链路定位一下多半是公钥粘贴时多了空格或漏了字符。4. 实操第二步SSH config路由配置与仓库URL写法4.1 一份可以直接抄的config模板光生成密钥还不够因为没有路由规则时SSH依然只会尝试默认密钥。现在编辑~/.ssh/config文件不存在就新建写入以下内容# GitHub 主账号 Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github AddKeysToAgent yes # Gitee 账号 Host gitee.com HostName gitee.com User git IdentityFile ~/.ssh/id_ed25519_gitee AddKeysToAgent yes保存后再次执行ssh -T gitgithub.com ssh -T gitgitee.com这次应该都能成功。GitHub返回信息里会带你的用户名Gitee也可能返回用户名或直接提示成功。看到成功提示之后你再去git clone gitgithub.com:xxx/repo.git或git clone gitgitee.com:xxx/repo.git推送拉取就都正常了。4.2 各字段含义及为什么这样写我来逐行解释这几个参数搞清楚之后你就能举一反三扩展到GitLab、自建服务器等各种场景。字段作用注意事项Host匹配别名/域名决定这个规则块什么时候生效不能有重复SSH按顺序取第一个匹配的块HostName实际连接的服务器域名如果是内网自定义端口还要加PortUserSSH登录用户名Git平台统一填git不要填你的GitHub用户名IdentityFile指定本规则块使用的私钥路径路径要写绝对路径~可以展开AddKeysToAgent每次连接成功后自动把私钥加入ssh-agent设为yes可以省去手动ssh-add的麻烦很多人在User上翻车以为是自己的GitHub用户名。其实GitHub、Gitee这类系统对SSH接入强制使用git作为登录用户真正的身份是看你用哪把私钥认证成功的。你把User改成自己的注册名反而会连接失败。Host字段需要特别强调在绝大多数标准配置里它和HostName写成同一个值比如github.com。这样你现有的远程URLgitgithub.com:user/repo.git不用做任何修改SSH连接时拿github.com去匹配Host github.com命中规则块再用规则块里的密钥。这是最省事、最不容易出错的写法。4.3 自定义Host与远程仓库URL的对应关系还有一种进阶写法是把Host定义成你喜欢的别名比如Host github-blog HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github_blog这样连接时远程仓库URL要写成gitgithub-blog:user/blog-repo.git而不能继续用gitgithub.com:user/blog-repo.git。因为后者会把github.com拿去匹配Host匹配不到github-blog这个块自然也不会用id_ed25519_github_blog这把钥匙。这个别名写法最典型的用途是同一平台多账号场景比如两个GitHub账号。不同账号对应不同的Host别名URL里用别名区分身份。但如果你只有“不同平台”的需求我建议保持Host和HostName一致少改一个地方就少踩一个坑。如果你手头已经有仓库用的是https://github.com/xxx.git这种地址想换成SSH方式可以这样改git remote set-url origin gitgithub.com:xxx/xxx.git查看当前仓库的远程地址git remote -v提示SSH方式相比HTTPS最实在的好处是不用每次推送都输密码也支持密钥变更后的集中管理。新配置完密钥后如果推送仍报错先跑一次git remote -v确认URL写的是不是git开头。5. 验证与排查从“SSH认证失败”到成功推送的完整链路5.1 用ssh -T确认两端密钥认证配好config之后最直接的验证方式就是用SSH自带的测试参数连一下。GitHub和Gitee都支持这种“只认证不登录”的玩法ssh -T gitgithub.com ssh -T gitgitee.com如果一切正常分别会看到欢迎信息。如果其中一个成功、另一个失败基本可以断定问题出在config对应块或者公钥添加环节和另一个平台无关。这种“逐个平台验证”的排查思路效率最高别一上来就怀疑全局配置。5.2 常见报错对照与逐项排查思路我这些年帮人排查SSH问题把最常见的报错和对应原因整理成了一张对照表你可以直接拿来当排查清单报错信息大概率原因处理办法Permission denied (publickey)客户端没有合适的私钥或多密钥下没匹配到正确钥匙检查config里的Host和IdentityFile是否匹配再确认公钥已添加到目标平台gitgitee.com: Permission deniedGitee端公钥未添加或添加错误重新复制.pub文件全文去Gitee设置里核对REMOTE HOST IDENTIFICATION HAS CHANGEDknown_hosts里已有旧指纹和当前服务器指纹冲突执行ssh-keygen -R github.com清除缓存再连接Bad owner or permissions私钥文件权限过宽执行chmod 600 ~/.ssh/id_*、chmod 700 ~/.sshCould not resolve hostname github-blogconfig里写了Host别名但URL或DNS解析不到检查远程URL是否写成了gitHost别名:...自定义别名只用于SSH config匹配Connection timed out网络不通或端口被限制先排查本地网络是否有到目标域名的连通性SSH默认走22端口部分严格网络环境需要改用443端口其中“公钥没匹配上”是最多的。判断方法是看本地公钥内容是否和平台显示的一致在GitHub或Gitee的钥匙列表里每条公钥都会显示完整的.pub内容你直接对比末尾注释就能确认是哪一把。5.3 用-v参数定位密钥选择过程如果上面的表面排查还是定位不了那就上终极大招——verbose日志。SSH支持在命令里加-v可以叠加到-vvv把连接过程中的每一步都打印出来ssh -vT gitgithub.com在输出里重点找两段信息Offering public key: ~/.ssh/id_ed25519_github Authenticated to github.com ([IP]:22) using key ED25519第一行说明客户端实际上尝试了哪把私钥第二行说明服务端接受了哪把私钥。如果客户端“Offering”的钥匙不对说明config匹配出了问题——可能是Host写错、IdentityFile路径不对、或者config文件压根没被读取。如果“Offering”的钥匙对的但服务端不接受那问题就出在公钥没正确上传到对应平台。提示config文件不要放在别的目录必须是~/.ssh/config。有些Windows用户会习惯性把.ssh文件夹放到文档目录下SSH客户端根本不会去读所有配置等于白写。Windows下默认用户主目录通常是C:\Users\你的用户名\确认~/.ssh/config确实存在于这个路径下。5.4 关于“SSH认证失败 git”这类综合报错的排查顺序如果你遇到的不是单纯SSH报错而是git推送时报fatal: Could not read from remote repository这个信息其实掩盖了底层的SSH错误。推荐的排查顺序是先跑ssh -T git对应域名缩小范围到“SSH认证本身能不能通”。能通则问题在Git层的URL或本地配置不能通则继续用ssh -vT看细节。检查远程URLgit remote -v确认是git开头而不是https://开头。检查本地仓库的作者配置git config user.name、git config user.email这个虽然不影响SSH连接但会直接影响提交记录归属。这套顺序我基本背下来了每次排查都是先化整为零再逐个击破比盯着一个报错瞎猜要高效得多。6. 同一平台多账号管理以两个GitHub账号为例6.1 认证隔离与作者身份隔离是两件事同一个平台多账号比如一个GitHub个人号、一个GitHub公司号和多平台场景相比复杂在“同一个域名上要区分两个完全不同的身份”。SSH层面必须靠Host别名来拆分因为真实域名都是github.com你没法写两个Host github.com的规则块让SSH“猜”你想用哪个账号。假设你的两个GitHub账号分别对应两把密钥# 个人 GitHub Host github-personal HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github_personal AddKeysToAgent yes # 公司 GitHub Host github-work HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github_work AddKeysToAgent yes配置完之后个人仓库的远程URL写成gitgithub-personal:username/personal-repo.git公司仓库的远程URL写成gitgithub-work:companyname/company-repo.git这里必须把Host别名写进URL里而不是用github.com。虽然别名不参与实际网络解析网络连接时仍然走HostName github.com但它决定了SSH匹配哪个规则块、使用哪把密钥。同时要注意SSH只解决了“认证”层面也就是服务器端认为“你是这个账号”但commit记录里的作者信息是Git自己根据user.name和user.email生成的。也就是说你即使SSH认证成了A账号如果仓库里的git config user.email还写着B账号的邮箱提交记录依然会显示B账号的归属。这两个必须同时配好缺一个都算没做完。6.2 用includeIf按目录自动切换git身份解决作者身份隔离最简单的方式是对不同仓库分别设置局部的user.name和user.email。在每个仓库里执行git config user.name Personal Name git config user.email personalexample.com但仓库多了之后挨个设置很烦而且容易漏。我自己的做法是借助Git 2.13开始支持的includeIf条件配置按目录自动加载不同的身份配置。在你的全局配置文件~/.gitconfig中追加[includeIf gitdir:~/work/] path ~/.gitconfig-work [includeIf gitdir:~/personal/] path ~/.gitconfig-personal然后创建~/.gitconfig-work[user] name Your Work Name email workcompany.com再创建~/.gitconfig-personal[user] name Your Personal Name email personalgmail.com以后只要仓库位于~/work/目录下Git会自动读取~/.gitconfig-work位于~/personal/目录下就读取~/.gitconfig-personal。这个方案把“目录结构”和“身份配置”绑定一劳永逸。配合前面的SSH config你的完整工作流就变成了在~/work/下clone仓库用gitgithub-work:...自动匹配公司账号密钥提交人自动是公司身份在~/personal/下clone仓库用gitgithub-personal:...自动匹配个人账号密钥提交人自动是个人身份。全自动切换几乎不需要手动干预。6.3 我踩过的坑与最终的日常使用习惯这套方案我自己用了两年多中间踩过几个印象深刻的坑分享出来帮你避雷第一全局邮箱污染。早期我犯过一个错在公司电脑上设置了git config --global user.email为公司邮箱结果下班后往个人GitHub仓库提交开源代码commit记录全部显示公司身份。后来我删掉了全局的user.email强制每个仓库或目录使用独立身份配置。个人建议全局配置里最好只保留user.name或什么都不写身份信息全部交给includeIf控制。第二ssh-agent的缓存过期。如果你给私钥设置了passphrase又没用AddKeysToAgent yes那么每次重启电脑后第一次推送都可能要求你重新输入口令。我在config里统一加上了AddKeysToAgent yes之后就再也没有手动执行过ssh-add。如果你发现某个平台的推送突然开始要密码而之前明明不用可以先执行ssh-add -l看agent里缓存了哪些钥匙。第三known_hosts的残留指纹。同一个域名从A账号切到B账号时本质上服务器的指纹没变不会触发指纹冲突但如果你以前是用自定义端口访问的Git服务器或者本地历史文件里存过旧机器的指纹就可能遇到REMOTE HOST IDENTIFICATION HAS CHANGED。处理方法上面提过ssh-keygen -R一键清除。现在我的日常习惯可以总结成几条硬规矩每个平台各一把密钥密钥文件名和用途强对应。~/.ssh/config里统一管理路由新加一个平台就是加一个规则块的事。仓库一律按目录分组SSH身份绑定URL别名Git作者身份绑定dirname两层分工明确。新电脑交接只需要备份~/.ssh/里的密钥和~/.gitconfig到新机器上恢复路径权限全套配置就能直接用。这个思路不仅适用于GitHub和Gitee换成你公司内网的GitLab、自建的Gitea、或任何走SSH协议的Git服务方法完全一样。把这套逻辑吃透以后接任何新平台都是在既有框架上加一个规则块而已不会再出现“配了新平台老平台挂了”这种低级事故。