SSH 的报错信息是有意简短的——协议不会告诉一个陌生人它为什么把人拒之门外。那是好的安全性、糟糕的易用性:你看到的信息,往往离真正的问题隔着两步。这是一份实战手册,覆盖你真正会碰到的十三个报错、每一个到底意味着什么,以及通往修复的最快路线。
开始之前先交代一个工具:ssh -v user@host(最多可加到 -vvv——读懂那段输出(英文))。下面几乎每一个谜题,一旦你看清它死在握手的哪一步,就不再是谜题了。
报错一览
| 报错 | 含义 | 第一步 |
|---|---|---|
Connection refused | 主机应答了;但那个端口上没有东西在监听 | 检查 sshd 是否在运行、是否在你拨的端口上 |
Connection timed out | 数据包在路上消失了 | 检查云防火墙 / 安全组 |
Permission denied (publickey) | 你提供的密钥一个都没被接受 | 先核对用户,再核对密钥,再核对 authorized_keys |
Permission denied(没有提示) | 密码认证已关闭;必须用密钥 | 用密钥,或通过服务商控制台登进去 |
Host key verification failed | 自你信任以来,服务器的密钥变了 | 走带外渠道核实,再 ssh-keygen -R |
Too many authentication failures | 代理提供了太多密钥 | 用 -o IdentitiesOnly=yes 配合 -i |
UNPROTECTED PRIVATE KEY FILE | 密钥文件别人也能读 | 给私钥执行 chmod 600 |
Broken pipe | 一个空闲连接被断开了 | 加上 ServerAliveInterval |
no matching key exchange method | 老旧服务器,现代客户端 | 只为那一台主机重新启用一个算法 |
Connection reset by peer | 在 SSH 打招呼之前 TCP 就被猛地关上 | 检查 fail2ban / MaxStartups |
Bad configuration option | 你的客户端太旧,读不了某一行配置 | 升级 ssh,或给那一行加保护 |
did not receive identification string | 有东西连上了,却没发横幅 | 通常是扫描器;在服务器端忽略即可 |
Unspecified GSS failure | 一次 Kerberos 尝试失败后回退了 | 如果登录仍然成功,忽略它 |
1. Connection refused
机器是能到达的,但你敲的那个端口上没有东西在监听。这一条几乎从来不是你的错:
- sshd 没在运行——从控制台执行
systemctl status sshd(VPS 服务商正是为这种时刻准备了网页控制台); - 它监听在一个非标准端口上——试试
ssh -p 2222,或检查/etc/ssh/sshd_config里的Port; - 一道防火墙在主动拒绝(而不是丢弃)该端口。
如果你刚刚重建了服务器,那就是防火墙。如果它昨天还好好的,那就是 sshd 崩了或端口变了。
完整修复:connection refused 与 timed out 解码。
2. Connection timed out
和 refused 不同:你的数据包正在消失。主机宕了、IP 写错了,或者——在云主机上最常见——一个安全组 / 网络 ACL 悄悄丢掉了 22 端口的流量。先查服务商的防火墙规则,再看你是不是在一个封禁出站 22 的网络上(酒店和办公室的访客 Wi-Fi 特别爱这么干;试试基于 443 的访问方式,或换个网络来确认)。
**完整修复:**同一篇 refused 与 timed out 的逐步讲解里,有 timed-out 这一分支的分步内容。
3. Permission denied (publickey)
历届冠军。服务器只接受密钥认证,而它没有接受你提供的任何一把密钥。按可能性从高到低:
- **用错了用户。**Ubuntu 镜像上是
ubuntu,Amazon Linux 上是ec2-user,大多数裸 VPS 上是root。同一把密钥配错用户名,产生的正是这个报错——云镜像各自挑了自己的登录用户,这占了首次登录 EC2/VPS(英文)一半的内容。 - 你的密钥没被提供出去。
ssh -v会显示试过的每一把密钥。如果你的密钥不在列表里,就明确指向它:ssh -i ~/.ssh/id_ed25519 user@host。 - **密钥不在服务器上。**它的公钥那一半,必须作为那个用户(不是 root)的
~user/.ssh/authorized_keys里的一行。 - **服务器端的权限。**如果家目录、
~/.ssh(700)或文件本身(600)是组可写的,sshd 会拒绝认可authorized_keys。这就是经典的“我把密钥复制过去了,可还是不行”。
完整修复:Permission denied (publickey),一套快速而系统的修法(英文)会按顺序排查每一个元凶。
4. 你本以为会看到密码提示,却得到 Permission denied
如果你想用密码认证却从没被问过,那是服务器设了 PasswordAuthentication no——大多数云镜像出厂就是这样,而且这是正确的默认值。用服务商的控制台或你已有的密钥登进去,然后把新密钥正经地加上,而不是把密码重新打开。
5. Host key verification failed / REMOTE HOST IDENTIFICATION HAS CHANGED
带着 ASCII 骷髅气场的吓人横幅。服务器出示的密钥,和你之前信任的那一把不同。两种解释:机器被合法地重建/重装了(绝大多数情况是这个),或者你和服务器之间有东西在冒充它(这个警告正是为之而存在的那种情况)。
当且仅当你确知服务器是被重建了:ssh-keygen -R hostname 会删掉那条过期的记录,下次连接就会重新信任。别不假思索地把那条删除脚本化进你的工作流里;那个警告为真的那一次,它是你能得到的唯一一次警告。
完整修复:Host key verification failed,以及修复它的正确方式(英文)。
6. Too many authentication failures
你的代理热情地提供了六把密钥,服务器的 MaxAuthTries 说够了。让客户端只提供你想用的那一把:
ssh -o IdentitiesOnly=yes -i ~/.ssh/the_right_key user@host
……或者在 ~/.ssh/config 里按主机把它写死。如果你带着很多密钥,在 Host * 下写一个 IdentitiesOnly yes,能让你永远免除这个报错。
7. WARNING: UNPROTECTED PRIVATE KEY FILE
你的私钥别人也能读,于是客户端拒绝使用它。chmod 600 ~/.ssh/id_ed25519。在 Windows 上,这会在机器之间或从云盘里复制密钥之后出现——通过文件的“安全”属性来修,或者干脆别把密钥放进会同步的文件夹里。
8. client_loop: send disconnect: Broken pipe(你一移开视线就死掉的那个会话)
空闲连接被你和服务器之间的某个 NAT 路由器或激进的防火墙丢弃了。保活可以在客户端这边修好它:
Host *
ServerAliveInterval 30
ServerAliveCountMax 4
容忍两分钟的沉默,每 30 秒发一个包,这样 NAT 表就永远不会把你忘掉。如果会话是在负载下、而不是空闲时死掉,那是另一种生物——去看 MTU(尤其是 VPN),而不是保活。
9. no matching key exchange method found / no matching cipher
你在和某个老东西对话——一台路由器、一台交换机、一台卡在老旧加密上的设备。现代 OpenSSH 出于充分的理由移除了那些算法。只为那一台主机重新启用它们,绝不要全局启用:
Host ancient-switch
KexAlgorithms +diffie-hellman-group14-sha1
Ciphers +aes256-cbc
报错信息会点出服务器提供的算法;把它列出的里面最不糟糕的那个加上。
10. ssh_exchange_identification: read: Connection reset by peer
TCP 连接刚打开就被猛地关上了,在 SSH 还没来得及打招呼之前。常见嫌疑:fail2ban 或 DenyHosts 封了你的 IP(从控制台查 /etc/hosts.deny 和 fail2ban 的 jail)、sshd 过载(MaxStartups 被一个猛敲端口的扫描器耗尽),或者一个负载均衡器的健康检查在吃掉连接。如果是 fail2ban、而且是你自己的服务器,修法是从控制台登录并 fail2ban-client unban <your-ip>——然后把 SSH 从扫描器的雷达上挪开,或者收紧 jail,免得你成了下周的误伤。
11. Bad configuration option: pubkeyacceptedalgorithms
不是服务器的问题——是你本地的 ssh 对你的配置文件来说太旧了。PubkeyAcceptedAlgorithms 是 OpenSSH 8.5 才有的(它取代了 PubkeyAcceptedKeyTypes);把这一行喂给一个更旧的客户端——某个年头的系统自带 macOS ssh、一个老发行版、一个极简容器镜像——它会干脆拒绝解析整个文件。三条出路:升级客户端(ssh -V 告诉你处在什么位置);用旧写法 PubkeyAcceptedKeyTypes,新版本仍把它当别名接受;或者给那一行加保护,让只有有能力的客户端才读到它:
Match all
# only OpenSSH >= 8.5 gets here via Include ordering
——实践中,最简单的保护,是把对版本敏感的选项放进一个单独的文件,用 Include 拉进来,这样同一套 dotfiles 上的旧客户端就直接跳过它。同一种形状的报错(Bad configuration option: <something>)永远意味着同一件事:那个词对这个二进制文件来说是未知的——先查该选项的拼写,再查这个二进制文件的版本。
12. did not receive identification string / invalid ssh identification string
两者都住在 SSH 最开头的那一步——横幅交换,在任何加密之前。did not receive identification string from <ip> 是 sshd 在服务器端记下的,当有东西向 22 端口开了一个 TCP 连接、却从没发过 SSH-2.0-... 那一行时:一个端口扫描器、一个负载均衡器健康检查、一个监控探针,或者一个放弃了的客户端。在你的服务器日志里,它几乎总是背景噪声,而非故障——除非那是你自己的客户端连不上,那样的话,路径上有什么东西(一个代理、一个 TCP wrapper、一个 IDS)正在吃掉横幅。
kex_exchange_identification: ... invalid ssh identification string 是镜像的另一面:有东西确实应答了,但发来的是非 SSH 的数据。常见原因,按顺序:你把 ssh 指向了错误的端口——一个 HTTP 服务器、一个数据库、一个返回 HTML 的强制门户;路径上的一个中间盒在拦截连接、替服务器作答;或者一个 ProxyCommand/ProxyJump 配错了,把一个不是 SSH 流的东西管进了客户端——一个代理的错误页出现在了本该是横幅的地方。确认你确实在和一个 sshd 对话:nc <host> 22 应当立刻打印一个 SSH-2.0-OpenSSH... 横幅。如果它打印的是 HTML 或者什么都没有,那是端口(或主机,或代理跳转)错了,不是你的密钥。
13. Unspecified GSS failure / debug1: unspecified GSS failure. Minor code may provide more information
Kerberos 噪声。OpenSSH 默认会在你的密钥之前尝试 GSSAPI(Kerberos)认证,而如果你不在一个 Kerberos 域里——大多数人都不在——它会失败并在 debug 级别记下这条,然后回退到密钥认证、正常连上。**如果连接最终成功了,这一行是无害的;忽略它。**它只在你确实依赖 Kerberos SSO、而认证正在失败时才要紧,那种情况下它是一个真正的票据/域问题(检查 klist、时钟偏移,以及 DNS 里该主机的 SPN)。要在非 Kerberos 的配置上消掉这噪声,往你的配置里加上 GSSAPIAuthentication no。
让修复固化下来
这些里有一半是一次性的修复;另一半值得在 ~/.ssh/config 里写一行,好让它们再也不回来——保活、IdentitiesOnly、按主机区分的端口和身份。配置胜过记忆。
另一样胜过记忆的,是一款替你保存状态的客户端。Termalin 把每台主机的设置——端口、用户、密钥、保活——和主机本身一起存着,在首次连接时核实服务器,并在指纹变化时大声警告,而它的状态栏实时显示机器的生命体征,于是“这台机器到底在不在?”不再是一个诊断步骤。Free 版主机数量不限——拿去用,把这些报错里的几个永久退役。