“你的SSH密钥可能已经过期了”——这句话是我前阵子在一个工作群里看到的,一个同事发完这句话,后面跟着一连串截图:VSCode连不上远程服务器、GitLab拉代码失败、登录Ubuntu直接被拒。评论区各种“我也是”,场面和事故现场差不多。说句实话,SSH密钥这类东西平时几乎没有存在感,但一旦它失效,你根本没法正常干活。今天这篇就拿这个“密钥过期”当引子,把SSH密钥为什么会失效、怎么判断是不是密钥的问题、失效以后怎么快速修复,以及那些最容易让人误以为“密钥过期”的坑,一次性讲透。内容不挑基础,刚接触服务器的开发者和每天处理几十台机器的运维都能参考。
1. 先说清楚:SSH密钥到底会不会“过期”
先说结论:纯SSH密钥对本身,默认是没有“保质期”的。但你在实际工作中确实会遇到密钥用不了的情况,而且很多平台和工具也会给你弹“密钥过期”的提示。这两件事并不矛盾,关键要搞清楚密钥在什么层面上失效。
1.1 密钥本身的内部结构里没有时间字段
一般我们用的SSH密钥对,是客户端生成一对公私钥:私钥自己留着,公钥放到服务器或Git平台。客户端发起连接时,用私钥签名,服务器用公钥验证,验过了就放行。打开私钥文件看一下,通常长这样:
bash复制-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW...
-----END OPENSSH PRIVATE KEY-----
这个文件里并没有类似“expire at 2027-01-01”的字段。也就是说,如果你五年前生成的密钥一直没人动它,五年后拿它去登录,只要服务器上还保留着对应的公钥,它依然能用。这一点和Windows激活密钥、Office产品密钥完全是两码事,别把两套东西混在一起。
那为什么现实中总有“密钥过期”的说法?“过期”的其实是密钥的使用条件,而不是密钥文件本身。你把它理解成人家的门禁卡:卡本身不会坏,但物业把你从白名单里拉黑,卡就刷不开了。
1.2 现实里“密钥失效”的四种常见真相
我梳理了一下工作中最常见的“密钥失效”场景,基本逃不出下面这四种:
| 失效表现 | 底层原因 | 判断重点 |
|---|---|---|
| Git平台提示密钥过期 | 添加公钥时设置了有效期,时间到后自动失效 | 登录GitLab/GitHub后台看密钥状态 |
| 公司服务器突然不认密钥 | 管理员强制轮换,旧公钥被从authorized_keys里移除了 | 找运维确认是否有轮换计划 |
| 换了电脑或重装系统后连不上 | 新机器上生成的是新私钥,服务器上的公钥没同步更新 | 对比新旧公钥内容 |
| 老板的脚本突然报Permission denied | 证书式SSH证书到了Validity截止时间 | 用 ssh-keygen -L 查看证书有效期 |
第4种要单独提一下,它属于真正的“带有效期”的密钥。部分企业内部会搭SSH证书认证体系,每个用户持有一张由CA签发的短期证书,证书本身带有有效期,到期后需要重新向CA申请,否则即使公钥认证逻辑没问题也登不进去。如果你所在公司的SSH登录依赖这种模式,那你遇到的“密钥过期”是字面意义上的过期。
另外补充一点:GitHub、GitLab这类平台,在用户添加公钥时是可以自己选过期时间的。很多人当时随手设了一个 90 天或 180 天,到期之后再用 git pull,就会看到类似“Key is invalid”或直接认证失败的提示。这个在后台看密钥列表就能确认,有时候甚至能看到一个显眼的红框。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手体检:三步判断你的SSH密钥是不是真有问题
碰上连接失败,别急着重新生成密钥。先花两分钟做个体检,搞清楚“密钥是不是真的失效了”再动手,避免把简单问题搞复杂。
2.1 先看本机密钥文件是否存在且完整
在客户端上执行:
bash复制ls -la ~/.ssh
正常情况下你会看到:
id_ed25519或id_rsa:私钥,权限必须是600- 对应的
.pub文件:公钥,权限644没问题 known_hosts:记录历史连接过的主机指纹
如果这些文件一个都不存在,说明你从来没有生成过密钥,或者重装系统后没有迁移过来,那当然会连不上。如果文件存在但权限异常,比如私钥是 644 甚至 777,OpenSSH会出于安全考虑直接拒绝使用:
text复制Permissions 0644 for '/root/.ssh/id_rsa' are too open.
修复命令:
bash复制chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 600 ~/.ssh/authorized_keys
顺便教大家一个小技巧,用一条命令就能验证本机的私钥和公钥是否配对:
bash复制ssh-keygen -y -f ~/.ssh/id_ed25519
如果输出的内容和你本机 id_ed25519.pub 里的内容一模一样,说明密钥文件本身没坏,问题大概率出在服务端或网络端。
2.2 带着详细日志试连一次
密钥文件没问题,下一步就是用详细模式真实连一次服务器:
bash复制ssh -vvv user@your-server-ip
日志会一步一步展示认证过程,重点看后面几行:
text复制debug1: Offering public key: /home/you/.ssh/id_ed25519 ED25519 SHA256:xxx
debug1: Server accepts key: /home/you/.ssh/id_ed25519 ED25519 SHA256:xxx
debug1: Authentications that can continue: publickey
debug1: No more authentication methods to try.
如果出现 Offering public key,说明客户端在尝试用你的私钥签名;如果之后没有 Server accepts key,而是直接到 No more authentication methods to try,那就是服务端在比对公钥时没找到匹配项,也就是俗称的“密钥不被接受”。这种情况要么是将公钥放错位置,要么是旧公钥确实被删了。
2.3 到服务端侧看 authorized_keys 和认证日志
在服务器上检查当前用户的家目录:
bash复制cat ~/.ssh/authorized_keys
把这份内容和你要使用的客户端公钥 .pub 文件逐字符对一下,注意里面有没有多余的换行、空格,很多人是手动往文件里追加公钥时粘坏了。
如果公钥文件内容正确,就去看系统认证日志。Ubuntu/Debian 通常在 /var/log/auth.log,CentOS/RHEL 通常在 /var/log/secure,用 systemd 的系统也可以统一用:
bash复制journalctl -u sshd -f
在日志里看到类似下面的记录,基本就能锁定原因:
text复制sshd[12345]: Authentication refused: bad ownership or modes for directory /home/user/.ssh
sshd[12345]: user user from 192.168.1.10 not allowed because none of user's authentication methods are allowed
sshd[12345]: Failed publickey for user from 192.168.1.10 port 50212 ssh2: ED25519 SHA256:xxx
日志比命令行输出信息量大得多,建议排查时两边窗口同时开着,一边发连接一边看日志,效率翻倍。
3. 确认密钥失效后的更新实操
体检完如果确实是密钥不被接受,那就进入修复环节。重新生成密钥对是最干净的方案,但要注意别把服务器上其他还在用的公钥覆盖掉。
3.1 重新生成密钥对并完成公钥部署
生成新密钥推荐用Ed25519算法,命令是:
bash复制ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519
参数说明:
-t ed25519:指定算法类型,Ed25519密钥短、生成快、安全性高,现代OpenSSH都支持。-C:加注释,建议写成自己的邮箱或“机器名-用途”之类的标识,以后在Git平台或服务器上看到公钥,一眼就能认出来是谁的。-f:指定保存路径,习惯上放在~/.ssh/下。
生成过程中会提示输入 passphrase,也就是私钥的额外口令。我建议加上,哪怕是一个简单的短语。私钥一旦泄露,passphrase还能给你争取补救时间。嫌每次输入麻烦,后面可以用 ssh-agent 解决。
新密钥生成后,把公钥放到服务器上。最简单的方式:
bash复制ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip
如果服务器上不允许密码登录,或者没有 ssh-copy-id,就手动拷贝公钥内容,登录服务器后执行:
bash复制mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "你的公钥内容" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
改完之后重新连接测试,确认能免密登录再干别的。
3.2 Git平台上的密钥替换流程
GitLab和GitHub的处理方法极其相似,都是把新的公钥内容贴到后台。
以GitLab为例,路径一般是:Preferences(偏好设置) -> SSH Keys,把旧的、失效的密钥删掉,再把新的 id_ed25519.pub 内容粘贴进去,如果平台支持过期时间设置,建议结合自己的使用频率合理选择,别一拍脑袋选个“永久”。公司内部GitLab如果强制要求设置过期时间,顺手按规范来就行。
GitHub的位置是:Settings -> SSH and GPG keys -> New SSH key。
替换完成后,用平台提供的验证命令确认:
bash复制ssh -T git@gitlab.com
# 或
ssh -T git@github.com
看到类似“Welcome to GitLab, @yourname!”就说明通了。
另外,在内部系统上用形如 ssh://dwj@10.0.0.47:29418/~zqq/project_xunjia.git 的地址拉代码时,如果连接失败,除了密钥问题,还要检查服务器IP是否发生变化、项目路径是否正确、目标机器端口是否开放。这类地址里面的用户名、端口、路径三个要素,任何一个错了都会报错,但不一定会提示“密钥过期”。
3.3 VSCode Remote SSH连不上的处理思路
远程开发已经成为常态,VSCode的Remote-SSH扩展也几乎是开发者的标配。很多朋友遇到“VSCode里一直转圈,最后报错连接失败”第一反应就是密钥过期。我的经验是:先在终端用纯命令行 ssh 连一下同一台主机,如果命令行能连上,VSCode基本是配置问题;如果命令行也连不上,再回到前面的密钥排查流程。
VSCode里SSH连接依赖 ~/.ssh/config 配置,一个常见的配置文件示例:
text复制Host my-ubuntu
HostName 192.168.1.20
User ubuntu
Port 22
IdentityFile ~/.ssh/id_ed25519
要注意 IdentityFile 必须指向实际存在的私钥文件。有人在网上复制配置,忘了改路径,结果用的还是别人的私钥,自然连不上。
还有一个容易被忽略的点:如果之前用同样的IP或域名连接过,但服务器重装过系统,指纹对不上,VSCode会提示 Host key verification failed。处理方式是用下面的命令删掉旧记录:
bash复制ssh-keygen -R 192.168.1.20
然后重新连接,确认新的指纹即可。
4. 批量登录与工具链场景下的密钥管理
单台服务器手工处理还好,一旦涉及几十台机器或团队协作,密钥管理的复杂度会直线上升。我见过不少运维同学在上百台服务器上手工改 authorized_keys,忍住不吐槽,但确实有更省力的做法。
4.1 用 ssh-agent 免去 repeated passphrase 输入
如果你给私钥设置了passphrase,每次SSH连接都要输一次,次数多了很容易怀疑人生。ssh-agent 就是解决这个问题的:
bash复制eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
执行一次后,当前会话期间再连接服务器,OpenSSH会直接从agent里拿私钥签名,不再让你重复输入口令。macOS还有把私钥永久记入钥匙串的优化,Linux桌面环境也有类似的keyring机制,有需要的可以自己琢磨。
如果机器上同时存在多个私钥,不想让SSH每次都把所有私钥试一遍,可以借助 ~/.ssh/config 给每个主机指定私钥:
text复制Host gitlab.company.com
User git
IdentityFile ~/.ssh/id_ed25519_gitlab
IdentitiesOnly yes
Host prod-server
HostName 10.0.0.15
User admin
IdentityFile ~/.ssh/id_ed25519_prod
IdentitiesOnly yes
IdentitiesOnly yes 很关键,它告诉SSH“只使用我这里指定的密钥,别把agent里其他密钥都发过去”。这样既能避免某些服务端因为收到的密钥太多而报 Too many authentication failures,也能防止用错密钥。
4.2 批量推送公钥的正确姿势
临时要对一批服务器推送公钥,可以直接写个循环:
bash复制for host in 192.168.1.21 192.168.1.22 192.168.1.23; do
ssh-copy-id -i ~/.ssh/id_ed25519.pub root@"$host"
done
这只是临时方案。在规范的运维体系里,更好的做法是用配置管理工具统一维护。Ansible的 authorized_key 模块就很好用,下面是一个最简单的示例:
yaml复制- name: 下发开发人员公钥
ansible.posix.authorized_key:
user: devops
state: present
key: "{{ item }}"
loop:
- "ssh-ed25519 AAAAC3Nz... dev@host1"
- "ssh-ed25519 BBBB... dev@host2"
这样新增或移除某个人的公钥,只需要改清单,再跑一遍Playbook,所有机器同步生效,比一台台登录去改可靠得多。
不过这里必须提醒一句:别把同一个私钥分发给一群人共用。一旦有人离职,你就要给所有相关机器换密钥,波及面巨大。规范的做法是“一人一秘钥,公钥集中管”,私钥永远只属于个人。
4.3 算法兼容性:不是密钥过期,是对方不认新版算法
有时候你密钥没问题、服务器公钥也没问题,可连接还是报错,而且报错内容容易让人误以为“密钥坏了”。典型的例子:OpenSSH 8.8及以上版本默认禁用了 ssh-rsa(SHA-1)签名算法,如果你拿新版客户端去连只支持老算法的旧设备,会看到:
text复制Unable to negotiate with 192.168.1.30 port 22: no matching host key type found. Their offer: ssh-rsa
或者反过来,老客户端连新服务器时报类似错误。这种情况和“密钥过期”没关系,是算法协商失败。临时绕过的方法是在命令里加参数:
bash复制ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa user@old-server
也可以在 ~/.ssh/config 里针对这台主机单独加:
text复制Host old-server
HostName 192.168.1.30
User admin
HostKeyAlgorithms +ssh-rsa
PubkeyAcceptedAlgorithms +ssh-rsa
但要清楚这只是过渡方案,老设备最好尽快升级系统或调整SSH配置,否则迟早会卡在算法兼容上。另外,在部分国产化环境(比如基于Linux内核的麒麟等系统)中,OpenSSH的命令和配置文件大体一致,排查思路可以完全复用,重点同样是检查算法协商和密钥导入。
5. 常见连接失败与排查技巧实录
这里把我这些年实际踩过、帮别人排查过的高频问题整理成速查表,方便你遇到类似报错时直接查答案。
5.1 典型报错和对应处理速查
| 报错或现象 | 真正原因 | 处理动作 |
|---|---|---|
| Permission denied (publickey) | 公钥不在authorized_keys里,或验证方式不对 | 核对公钥、确认sshd允许publickey认证 |
| Too many authentication failures | 客户端发送了过多私钥,服务端直接断开 | 添加 -o IdentitiesOnly=yes,限制密钥数量 |
| Host key verification failed | known_hosts里记录的指纹和服务器实际指纹不一致 | ssh-keygen -R 主机地址 后重连 |
| Bad owner or permissions | 本地或服务端的目录/文件权限过宽 | 按要求执行chmod |
| Connection refused | SSH端口没开或服务未启动 | 检查sshd服务、防火墙、远端端口 |
| Connection timed out | 网络不通或防火墙拦截 | 用ping/telnet逐层测网络 |
| no matching key exchange method | 两端算法协商失败 | 升级系统或临时添加算法参数 |
5.2 权限和known_hosts:最容易被误判的两个坑
密钥相关的问题里,权限问题出现的频率高得惊人。服务端 ~/.ssh 目录权限必须是 700,authorized_keys 必须是 600,如果家目录本身对组或其他用户可写,sshd的 StrictModes 也会拒绝认证。有时候你在服务器上用root登录后创建了用户,再切到普通用户下添加公钥,目录属主如果变成root,也会有问题。处理方法是把属主改回来:
bash复制chown -R user:user /home/user/.ssh
另一个高频坑是known_hosts。很多人一看到“Remote host identification has changed”就慌,以为是密钥过期,其实是服务器重装系统或IP被重新分配导致指纹变化。这时候不要盲目删known_hosts,而是先确认这台服务器的身份是否可信,确认没问题后再用 ssh-keygen -R 删除对应记录。
5.3 安全策略与密钥轮换
最后聊一下密钥轮换和防爆破。如果你的服务器开放到公网,SSH登录日志里会出现大量“Failed password”或“Invalid user”之类的记录,这不是你的密钥过期,而是有人在扫端口试密码。遇上这种情况,建议做三件事:
- 关闭密码登录,只保留密钥认证,修改sshd配置:
text复制PasswordAuthentication no
PubkeyAuthentication yes
改完记得 systemctl reload sshd。
-
安装并配置 fail2ban,让它自动封禁短时间内多次尝试失败的IP。
-
限制可登录用户和来源IP,能不给root开远程登录就不给。
关于密钥轮换,企业内部如果有合规要求,一般会规定每季度或每半年轮换一次。个人开发者倒是没有强制要求,但我建议至少做到:每年检查一次自己在用的密钥在哪些平台和服务器上登记过,如果长期未用且不确定的,直接从后台删掉。另外,如果你用Git平台,尽量给新增的密钥设置一个你能接受的过期时间,这样到期后你会第一时间注意到,避免密钥长期挂在后台脱管。
再分享一个我在实际排查中发现的小细节:很多人在生成密钥时习惯用默认文件名 id_ed25519,但如果你有多台设备和多个平台要登录,建议按用途拆成多个密钥对,比如 id_ed25519_github、id_ed25519_work,然后在 ~/.ssh/config 里为不同主机指定不同的 IdentityFile。这样做的好处是,某一把私钥即使泄露,你只需要撤回对应的公钥,其他平台完全不受影响,不需要全局返工。
最后说个我自己的习惯,也给读者一个参考:每次重装系统或换新电脑,重启后的第一件事不是装各种软件,而是生成新密钥并更新到所有常用平台。这看起来有点麻烦,但真等到登录服务器时才发现连不上,才是真正的折腾。密钥这东西,更新一次花不了五分钟,省下的排查时间往往是一下午。
