上周组里有个同事突然在群里喊:我上午还能push代码,午休起来就Permission denied了,SSH密钥过期了吗?我过去看了一眼,.ssh目录里的文件都还在,GitLab那边Key也没有删,最后发现是他前几天重装系统时把公钥重新生成过,本地私钥和服务端公钥对不上了。
这种"SSH密钥过期"其实每天都会在团队里发生几次。大多数情况下,并不是密钥真的有过期时间,而是配置链路里某个环节悄悄变了。这篇就围绕SSH密钥失效这个场景,把密钥的生成、GitLab配置SSH密钥、Gerrit配置SSH密钥和问题定位完整过一遍,看完你大概率能自己解决团队里80%的"密钥过期"问题。
1. SSH密钥为什么会"过期"
1.1 你可能遇到了假"过期"
先说结论:SSH密钥对本身是没有过期时间这个概念的。你在终端里跑ssh-keygen生成的那一串RSA或Ed25519密钥,如果没人动它,放十年它还是同一对密钥,技术上完全能用。
那"你的SSH密钥可能已经过期了"这个说法是怎么来的?我归纳了一下,实际工作中见到的所谓过期,绝大多数是下面几种情况:
- 本地私钥文件被误删、重装系统没备份,而服务端还留着旧公钥。
- 服务端把旧的
authorized_keys记录清理掉了,比如你离职又回来、或者公司做了周期性账号审计。 - 你重新生成过密钥对,新私钥和远端GitLab或Gerrit上存的旧公钥不是配对关系。
- GitLab的管理员在后台手动撤销了你的Key,GitLab界面里显示状态正常,但实际服务端已经拒绝。
- 主机指纹变了,也就是
known_hosts里记录的服务端公钥和你当前连的那台机器对不上,SSH会直接拒绝连接。
前四种属于"身份认证对不上",最后一种其实是"伪过期",它报错方式和密钥错误非常像,但根因完全不同。后面第五节专门讲排查时怎么区分。
判断是不是"假过期",最直接的方法是看一眼SSH客户端的报错。Permission denied (publickey)是密钥认证失败,而WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED是known_hosts问题。这两种错了以后要走的排查路径完全是两条。
1.2 "真过期"的幕后原因
如果排除上面那些假象,有没有真正算得上"过期"的情况?有,但在代码托管场景里不是密钥自身过期,而是策略层面把它置为失效:
- 你在GitLab里上传的公钥虽然界面写着Active,但管理员可能开了Key过期时间限制,定期强制轮换。
- 很多公司做账号生命周期管理,员工账号被禁用后关联的SSH Key会一并失效,即使你的本地文件和服务端记录都还在。
- Gerrit这类做代码评审的平台,如果用户账号被标记为Inactive,密钥同样无法使用。
所以当你看到"密钥过期"这类话术时,建议立刻去检查的清单是:本地私钥在不在、服务端公钥在不在、账号状态正不正常、known_hosts有没有变。这四个方向覆盖了我在企业里遇到的九成以上问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零配置一套可用的SSH密钥
2.1 生成密钥前先想清楚这3个问题
不要一上来就ssh-keygen按回车按到底。我在实际帮人排查时发现,很多问题恰恰是生成阶段埋下的。
第一个问题是选什么算法。早些年大家都用RSA 2048,现在更推荐Ed25519,速度快、安全性好而且密钥短。不过注意一点:如果你要连接的是一些比较老旧的内部系统,可能只支持RSA,那就用ssh-keygen -t rsa -b 4096。GitLab和Gerrit对Ed25519的支持都很好,所以新环境优先Ed25519。
第二个问题是给不给私钥设passphrase。设了的优点是私钥泄露后别人也难用,缺点是每次连接都要输入密码。不设则反过来,方便但风险高。我的习惯是:工作电脑上不设passphrase,但开启磁盘加密;如果是在共享服务器上使用,则必须设passphrase。这个决策直接影响后面是否要用ssh-agent。
第三个问题是一台电脑多平台怎么管理。同时用GitLab和Gerrit,甚至还有GitHub、公司自建Git服务器时,需要为每个平台生成单独的密钥对,通过~/.ssh/config文件做映射。千万不要一键生成同一个密钥往所有平台传。
2.2 生成密钥和agent配置实操
以Ed25519为例,最基本的生成命令是:
bash复制ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_gitlab
其中-C是注释,建议写自己的邮箱或用途,方便以后辨认;-f是指定生成的文件名,如果你只有一个用途,用默认的id_ed25519就行,但如果平台多,强烈建议每个平台一个文件。
生成后目录里会有两个文件:id_ed25519_gitlab是私钥,永远不要离开自己的机器;id_ed25519_gitlab.pub是公钥,要复制到GitLab或者Gerrit后台。
如果设了passphrase,为了防止每次操作都要输入密码,可以启动ssh-agent做缓存:
bash复制eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519_gitlab
macOS上还可以用ssh-add --apple-use-keychain把passphrase写进钥匙串,避免重启后失效。Linux上则需要在桌面环境里配置gnome-keyring,或者在shell配置里手动添加agent启动逻辑。
2.3 公钥和私钥分别放到哪里
这个基础点如果不讲透,后面排查时容易一头雾水。完整链路是这样:
- 你电脑上保存私钥,也就是
~/.ssh/目录下没有.pub后缀的那个文件。 - GitLab/Gerrit服务器上保存的是公钥,你通过网页后台粘贴进去。
- 连接时,客户端用你的私钥签名,服务端拿存的公钥去验签。验签通过,就认为你是密钥对应的合法用户。
所以出现认证失败时,你要检查的核心就是:本地私钥和服务端公钥是否配对。检查方法很简单,用ssh-keygen -y -f ~/.ssh/某个私钥文件可以导出这个私钥对应的公钥,再和服务端页面上的比对,如果不一样,说明你贴错公钥或者用错私钥文件了。
3. GitLab与Gerrit配置SSH密钥的完整实操
3.1 GitLab配置SSH密钥全流程
GitLab配置SSH密钥算是所有平台里比较简单顺滑的,整体分四步。
第一步,确认本地生成完密钥对之后,查看公钥内容:
bash复制cat ~/.ssh/id_ed25519_gitlab.pub
输出是一行以ssh-ed25519开头、以你注释邮箱结尾的长字符串,这才是要复制的内容。见过很多人把私钥内容复制上去,GitLab界面会提示格式错误。
第二步,登录GitLab,点击右上角头像,进入Preferences,找到左侧的SSH Keys菜单。在Key文本框里粘贴刚才的公钥,Title会自动带出一部分内容,建议改成类似"work-laptop"或"home-pc"这样自己能认出来的名字。Expiration date可以留空,但如果公司有安全策略要求填一个到期日,那就按策略填,同时把到期日记到日历上,免得某天突然失联。
第三步,如果后台里已经存在同一条公钥,GitLab会提示Key已经被使用。这种情况通常是你在另一台机器复制过公钥,或者重装系统前导出过公钥备份。处理办法不是强贴上去,而是先确认之前那台机器还要不要用,不要用了,就直接删除旧Key再添加;还要用,就得在这台机器另生成一对新密钥。
第四步验证连接:
bash复制ssh -T git@gitlab.com
注意这里必须是git@,不是你的用户名。GitLab会对固定的git用户做路由,根据密钥识别出具体账号。如果看到Welcome to GitLab, @yourname!就代表通了。如果是公司自建的GitLab,把gitlab.com换成你公司的域名就好,验证命令同样有效。
3.2 Gerrit配置SSH密钥与普通平台的区别
Gerrit和GitLab看着都是Git平台,但Gerrit配置SSH密钥时有个关键区别:默认端口不是22,而是29418。
很多人在Gerrit上配置完密钥后用默认的SSH命令去连,发现连接超时或者被拒绝。其实Gerrit文档里写过,它的SSH daemon监听在29418端口,所以在生成配置时要把端口和Host信息写清楚。
第一步,生成密钥时建议单独指定文件名,比如:
bash复制ssh-keygen -t ed25519 -C "you@example.com" -f ~/.ssh/id_ed25519_gerrit
第二步,登录Gerrit,点击右上角Settings,找到SSH Keys,把公钥粘贴进去保存。Gerrit没有GitLab那样的Title自动填充,但保存后会显示指纹,你可以把指纹和本地的ssh-keygen -lf ~/.ssh/id_ed25519_gerrit.pub对比看看是否一致。
第三步,在~/.ssh/config里添加一段映射,省去以后每次手动指定端口的麻烦:
code复制Host gerrit-work
HostName gerrit.example.com
Port 29418
User your-gerrit-username
IdentityFile ~/.ssh/id_ed25519_gerrit
这里的User不是登录系统用户,是登录Gerrit的用户名,通常和Gerrit网页账号一致。Host是你在本机给这个连接起的昵称,后面clone代码时可以用它替代完整地址。
配置完后测试:
bash复制ssh -p 29418 your-gerrit-username@gerrit.example.com
或者如果你加了config,直接:
bash复制ssh gerrit-work
Gerrit成功响应时会显示一段欢迎信息,然后告诉你可以用gerrit命令做操作。
3.3 配置之后的验证与首次连接测试
无论GitLab还是Gerrit,刚配好密钥后的首次连接都会有一个known_hosts确认提示,问你是否信任这台主机,输入yes回车就好。这个提示只出现一次,但很多新手在这里会犹豫,以为出错了。
还有一个实战细节:如果你的平台配置层面没问题,但还是失败,请加上-v参数重新跑一次连接命令:
bash复制ssh -T git@gitlab.com -v
输出里会包含详细认证过程。重点看Offering public key这一行,如果它列出了你的密钥文件路径和类型,说明客户端尝试用这把私钥去认证了。如果连Offering都没有,说明客户端根本没打算用这把钥匙,问题出在ssh-agent或config配置,而不是服务端。如果出现了Server accepts key,紧接着又是Authentications that can continue,那基本是服务端拿你公钥匹配不到账号了,需要检查GitLab/Gerrit账号状态和公钥是否被禁用。
4. "SSH密钥已过期"的高频场景复盘
4.1 密钥本身没事,是known_hosts在报错
有一种报错最容易让人误判成密钥过期,就是WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED。这句英文不是说你密钥不对,而是说服务端的主机公钥变了。常见诱因包括:公司把Git服务器迁移到了新机器,或者服务器重装系统后没保留旧的SSH host key,也可能你连接的那台机器本身是负载均衡后面的一台新节点。
遇到这种情况,不要直接删掉.ssh/known_hosts整份文件。正确做法是只删除对应主机的条目:
bash复制ssh-keygen -R gitlab.example.com
然后重新连接,根据提示接受新的主机指纹。如果你管理的服务器比较多,-R后面还可以带IP。删除时要注意,如果主机同时提供HTTP和SSH,可能在不同端口,但ssh-keygen -R是针对主机名做的,端口不同时需要在连接时用-p指定。
4.2 多人共用一台电脑时的密钥串号
开发机如果是多人共用的Linux服务器,或者有同事借用你的电脑提交过代码,很容易出现的一种情况是:本地的~/.ssh/id_ed25519被别人覆盖了,或者他生成的密钥写进了你的默认位置。
排查这种问题,第一个动作就是查看密钥文件和目录权限:
bash复制ls -la ~/.ssh/
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
SSH客户端对密钥文件权限非常敏感,如果文件权限是644或者属主不是当前用户,它宁可报错也拒绝使用这把私钥。而且权限问题报错通常是UNPROTECTED PRIVATE KEY FILE,很多新手一看没权限就以为是密钥过期,其实解个权限就行。
用过别人的公钥提交过的账号,记得把服务端里对应的公钥删除,不然之后可能会出现"为什么我的Git提交显示成别人的名字"这类混乱。
4.3 服务端回收导致的批量失效
企业里还有一种"密钥过期"是所有员工一起遇到的,就是管理员在服务器上周期性清理了authorized_keys或者离职账号回收时把关联Key一并删掉。
如果你确认密码文件都在、权限也对、本地也能导出公钥,但GitLab和Gerrit都提示登录不了,试着用浏览器登录一次网页版看账号是否正常。如果网页登录提示账号被禁用或要重置密码,那密钥再对也没用,因为账号层面的状态把认证路径掐断了。
这种问题不是靠重新配置密钥能解决的,要去找管理员确认账号状态,必要时走账号重新激活流程。
5. 常见问题与排查速查表
5.1 排查命令使用顺序
我每次处理SSH密钥问题,习惯按照"从自己到远端"的顺序执行下面这些命令,效率最高:
bash复制# 1. 确认本地密钥文件存在且权限正确
ls -la ~/.ssh/
# 2. 确认私钥能正常导出公钥
ssh-keygen -y -f ~/.ssh/id_ed25519_gitlab
# 3. 确认ssh-agent里加载了哪些密钥
ssh-add -l
# 4. 确认配置文件没有语法问题
ssh -T git@gitlab.com -v
# 5. 确认known_hosts里没有残留旧主机记录
ssh-keygen -F gitlab.example.com
第2步很重要,如果私钥文件和公钥不配对,这个命令会直接报错;如果私钥文件损坏或者设置了passphrase但输入不对,这里也能暴露问题。第3步看agent加载情况,多平台切换时容易因为agent里缓存了旧的默认密钥导致连错账号。第4步的-v参数可以多敲几次逐步变成-vvv,信息越多定位越准。
5.2 典型报错对照表
我整理了这几个高频报错和对应处置方法:
| 报错现象 | 实际原因 | 处理办法 |
|---|---|---|
Permission denied (publickey) |
本地私钥与服务端公钥不配对 | 重传公钥,或检查是否用错私钥文件 |
UNPROTECTED PRIVATE KEY FILE |
私钥文件权限过大 | 执行chmod 600修复权限 |
REMOTE HOST IDENTIFICATION HAS CHANGED |
known_hosts与当前主机指纹不一致 | 用ssh-keygen -R清理旧主机记录 |
Connection refused 或 Connection timed out |
SSH端口不对或网络不通 | 确认Gerrit的29418端口是否放行 |
Load key ... invalid format |
私钥文件内容损坏或被截断 | 重新生成密钥并重新配置公钥 |
Agent admitted failure to sign |
ssh-agent加载出错或密钥类型不匹配 | 重新ssh-add,检查SSH版本 |
your account is inactive |
平台账号被禁用 | 联系管理员恢复账号 |
注意倒数第二种情况如果出现在老系统上,可能是服务器SSH版本太低不支持Ed25519,换成RSA 4096基本能解决。
5.3 我的几条独家避坑建议
最后分享几条从无数次翻车里总结出来的经验,这些在官方文档里通常不会写。
第一,给每个平台单独做密钥对,并坚持在~/.ssh/config里配置一个独立Host别名。虽然只用一个密钥也能同时连接GitLab和Gerrit,但一旦某个平台需要撤换密钥,另一个平台的连接也会受影响。分开之后,废弃某一把钥匙对其它平台毫无影响,排查时也清晰得多。
第二,换电脑或重装系统前,记得先把所有平台的公钥重新上传。具体做法是:旧电脑上新生成一对密钥,把公钥传到各个平台后台并删除旧公钥,然后把私钥通过加密方式传到新电脑。如果已经丢掉旧电脑私钥,至少要到平台后台把旧公钥删掉,避免留下安全隐患。
第三,定期做一次"密钥体检"。可以写一个简单的shell脚本,循环检查本地~/.ssh目录下的私钥权限,然后逐一ssh -T测试各平台连通性。有没有配好脚本不重要,重要的是养成每隔一两个月验证一次的习惯,别等到push不了才发现。
第四,如果发现自己GitLab上的多个项目突然都不能push,先别急着重新配置密钥,去GitLab个人设置页面看SSH Keys列表状态。有时候不是密钥本身的问题,而是你的公钥被标记成了Expired,删除后添加同一条公钥即可恢复,不需要重新生成密钥对。
6. 从"我的过期了"到一套可自愈的密钥管理习惯
把上面这些内容沉淀下来,你会发现所谓SSH密钥过期,本质上是一次身份链路的失配。从本地私钥、ssh-agent、配置文件、known_hosts到服务端账号和公钥记录,整条链路上任何一环变了,就会以"过期"的形式暴露出来。
我个人处理这类问题时的心态是:看到报错先不慌,按链路逐段验证。先确认本地私钥在不在、能不能导出公钥,再确认服务端页面里的公钥是否一致,再确认账号状态,最后才考虑known_hosts和网络端口。这套顺序我用了很多年,几乎能解决所有平台上的SSH认证问题。
如果你手头正好遇到密钥连接失败,按照第五节的对照表走一遍,大概率十分钟内就能定位。如果还搞不定,还有一个笨办法但很有效:删除本地密钥和服务端密钥记录,全部重新走一遍第三节的配置流程。重来一次虽然不优雅,但它会逼你把每个环节都检查到位,很多隐蔽的疏漏就是在这个过程中暴露出来的。
