SSH 偶发卡在 KEX_ECDH_REPLY:从链路、IPv6 到算法协商的排查顺序
先分清“连不上”和“握手中断”
最常见的误判是把 SSH 故障都归因于“服务器挂了”。实际上,如果 nc -vz host 22 能通,说明 TCP 三次握手已经完成,问题出在更上层的 SSH 协议协商。此时服务器 sshd 可能完全正常,uptime 和 load 也看不出异常。
典型现象是:命令一直挂着,不报错也不退出;加上 -vvv 后,最后一行停在:
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
这句话的含义很明确:客户端已经把密钥交换的初始化包发给了服务器,正在等服务器返回 ECDH 公钥和签名。卡在这里,既可能是网络路径把包丢了,也可能是服务器响应了但回程被拦,还可能是双方根本没能就会话算法达成一致。
第一步:用 -vvv 找到确切的卡点
不要凭感觉改配置,先拿到完整日志:
ssh -vvv -o ConnectTimeout=20 user@host 2>&1 | tee /tmp/ssh-debug.log
重点看三段时间点:
- TCP 连接建立:出现
Connection established,说明网络层可达。 - 版本与算法协商:出现
Remote protocol version、kex: algorithm,说明双方已经交换 banner 并选定算法。 - 密钥交换:如果停在
expecting SSH2_MSG_KEX_ECDH_REPLY,问题就在服务器回包或回程链路上。
如果连 Remote protocol version 都没出现,先怀疑 22 端口被中间设备劫持、或者连到了错误的地址。
第二步:看链路质量,而不是只看延迟
跨国或跨运营商链路常见“能 ping 通但偶发丢包”。单次 ping 说明不了问题,要看一段时间的统计:
ping -c 100 host
mtr -rwzbc 100 host
判断标准不是平均延迟,而是丢包是否集中在某一跳。如果从第 N 跳开始持续丢包,后面所有节点都跟着丢,基本可以锁定那段链路;如果只有最后一跳丢,反而可能是服务器侧的 ICMP 限速,不代表 TCP 有问题。
SSH 的密钥交换包不大,但对丢包非常敏感:TCP 会重传,客户端表现为“卡十几秒后突然成功”或“一直卡住”。这类故障用 ss -ti 看重传次数更直接:
ss -ti dst :22
第三步:排除 IPv6 与 DNS 的干扰
很多“偶发连不上”其实是客户端优先走了 IPv6,而目标网络没有可用的 IPv6 回程。表现是 SSH 先等 AAAA 记录、再等 IPv6 连接超时,最后才回落到 IPv4。
强制 IPv4 验证:
ssh -4 -o ConnectTimeout=20 user@host
如果 -4 立刻正常,问题就不在加密算法,而在地址选择。可以在 ~/.ssh/config 里固定:
Host myserver
HostName 203.0.113.10
User ubuntu
AddressFamily inet
ConnectTimeout 20
ServerAliveInterval 30
ServerAliveCountMax 3
AddressFamily inet 只走 IPv4;ServerAliveInterval 让空闲连接定期发心跳,避免被 NAT 或防火墙静默回收。
第四步:算法协商与中间盒
有些老防火墙、DDoS 清洗设备或代理会对特定 SSH 算法做深度包检测。一旦识别失败,它可能直接丢弃包含密钥交换数据的包,表现就是握手停在半路。
先看双方支持什么:
ssh -Q kex
ssh -Q cipher
sshd -T | grep -i -E 'kexalgorithms|cipers'
然后用最小组合做对照测试。下面这组参数刻意选了较常见、包结构较简单的算法:
ssh -4 -o KexAlgorithms=ecdh-sha2-nistp256 \
-o Ciphers=aes128-ctr \
-o ConnectTimeout=20 user@host
如果这组能稳定连上,而默认配置仍然偶发卡住,基本可以确认是算法协商或中间盒干扰,而不是服务器负载。此时有两个方向:
- 临时绕行:把上面几行写进
~/.ssh/config的对应 Host 段,先恢复工作。 - 根治:升级
sshd和客户端,或者换一个不被中间盒重点照顾的端口/入口;如果必须保留默认算法,联系网络管理员确认清洗策略。
第五步:MTU 要不要查
MTU 问题更常出现在认证通过后的大流量阶段,但并不是与握手无关:如果服务器使用了较大的证书链或 banner,密钥交换阶段的单个 TCP 段也可能超过路径 MTU,被中间设备丢弃后表现为“握手卡住”。
用不分片的 ping 逐步试探:
ping -M do -s 1472 host # 1500 MTU
ping -M do -s 1400 host # 1428 MTU
ping -M do -s 1200 host # 1228 MTU
如果 1472 失败、1400 成功,说明路径 MTU 小于 1500。可以在服务器侧临时降低 MTU 验证:
ip link set dev eth0 mtu 1400
或者在网关上做 MSS clamping:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
验证完记得恢复,别把临时值当成永久配置。
一套可直接复用的排查顺序
nc -vz host 22:确认端口可达。ssh -vvv:确认卡在 TCP、banner、算法协商还是 KEX 回包。ping -c 100+mtr:看丢包是否集中在固定跳数。ssh -4:排除 IPv6 和 DNS 回退。-o KexAlgorithms=... -o Ciphers=...:做算法对照。ping -M do -s:确认路径 MTU。- 最后才动服务器配置;每次只改一个变量,改完立刻复测。
SSH 握手的每一段都有明确日志,最怕的是一上来就重装 sshd 或重启服务器。先把卡点定位到“网络、地址、算法、MTU”中的某一层,剩下的处理反而很快。
- 点赞
- 收藏
- 关注作者
评论(0)