1. 问题现象与排查思路拆解
1.1 这个报错到底在说什么
ssh_exchange_identification: read: Connection reset by peer这个报错,但凡做过几年运维或者后端开发的人,大概率都遇到过。它的字面意思是:SSH 客户端在尝试和远程主机交换身份标识(identification)的阶段,读取数据时连接被对端重置了。注意关键词——交换身份标识阶段。这个阶段发生在 TCP 三次握手完成之后、密钥交换和用户认证之前。换句话说,TCP 层是通的,但 SSH 协议层的握手还没走完就被掐断了。
很多人第一次看到这个报错会懵,因为它的表现很“分裂”:有时候能连上,有时候连不上;有时候重试几次就好了,有时候怎么试都不行。这种间歇性特征恰恰说明问题不在 SSH 配置本身,而更可能出在连接建立的上游环节——可能是服务端资源限制、可能是网络中间设备干扰、也可能是服务端守护进程本身出了问题。
我先把结论性的排查方向摆出来,后面再逐层展开:
- 服务端
sshd进程是否正常监听、是否达到最大连接数 - 服务端 TCP Wrapper(
/etc/hosts.deny、/etc/hosts.allow)是否拦截了客户端 - 服务端
MaxStartups、MaxSessions等并发参数是否触顶 - 客户端 IP 是否被
fail2ban或类似防护工具封禁 - 网络路径中是否存在会话数限制或连接重置行为
- 服务端系统资源(文件描述符、内存、PID)是否耗尽
1.2 为什么这个报错容易误判
这个报错最坑的地方在于,它把“连接被重置”这个结果抛给了客户端,但真正的原因往往藏在服务端。客户端的 SSH 只是如实报告了“我读到一半,对面把连接断了”。所以如果你只在客户端折腾ssh_config、换密钥、加-v参数,大概率是白费功夫。
我见过有同学遇到这个问题后,第一反应是删掉known_hosts重新生成密钥,结果当然没用。还有人怀疑是本地网络问题,换了热点、换了网线,依然无效。原因很简单:问题不在客户端,而在服务端或中间链路。
正确的排查姿势应该是:先确认服务端sshd的状态,再看服务端的连接限制和防护策略,最后才考虑网络中间设备。这个顺序不能反,否则你会浪费大量时间在无关环节上。
1.3 排查前的信息收集清单
在动手之前,先收集这几项信息,能帮你快速缩小范围:
| 收集项 | 获取方式 | 用途 |
|---|---|---|
| 客户端完整报错 | ssh -vvv user@host | 定位卡在哪个阶段 |
| 服务端 sshd 状态 | systemctl status sshd | 确认守护进程是否正常 |
| 服务端监听端口 | ss -tlnp | grep :22 | 确认端口是否被监听 |
| 服务端当前连接数 | ss -tn state established '( dport = :22 )' | wc -l | 判断是否触顶 |
| 服务端系统日志 | journalctl -u sshd -n 100 | 查看拒绝原因 |
| 客户端 IP 是否被封 | 检查防护工具日志 | 排除封禁可能 |
提示:
ssh -vvv的输出非常关键,它会告诉你连接卡在Connecting to host之后、SSH2_MSG_KEXINIT之前还是之后。如果卡在SSH2_MSG_KEXINIT之前就断了,基本可以确认是服务端在协议握手前就拒绝了连接。
2. 服务端 sshd 配置与资源限制深挖
2.1 MaxStartups 参数:最容易被忽视的元凶
MaxStartups是sshd_config里一个非常关键的参数,它控制的是同时处于未认证状态的 SSH 连接数。默认值通常是10:30:100,意思是:当未认证连接数达到 10 个时开始随机拒绝,达到 30 个时拒绝 30% 的新连接,达到 100 个时全部拒绝。
这个参数的设计初衷是防止暴力破解和资源耗尽,但在实际场景中,它经常成为“背锅侠”。比如你的服务端前面有一个负载均衡或者跳板机,大量连接在短时间内涌入,未认证连接数瞬间超过阈值,后面的连接就会被直接重置,客户端看到的就是Connection reset by peer。
查看当前配置:
grep -i maxstartups /etc/ssh/sshd_config如果输出是注释状态或者默认值,而你的业务场景确实存在并发连接较多的情况,可以适当调大:
# 编辑 sshd_config MaxStartups 100:30:200这行的含义是:未认证连接数达到 100 时开始随机拒绝,达到 200 时全部拒绝。调大之后记得重载配置:
systemctl reload sshd注意:调大
MaxStartups只是缓解症状,不是根治。如果你的服务端频繁触顶,说明要么有异常连接在冲击,要么服务端处理能力不足,需要从根源上排查。
2.2 MaxSessions 与 MaxConnections 的区别
很多人会把MaxSessions和MaxConnections搞混。简单说:
MaxConnections:控制单个网络连接上可以复用多少个 SSH 会话(多路复用场景)MaxSessions:控制单个已认证连接上允许的最大会话数
这两个参数通常不会导致Connection reset by peer,因为它们在认证之后才生效。但如果你用的是 SSH 多路复用(ControlMaster),并且配置不当,可能会间接引发连接异常。检查方式:
grep -iE "maxsessions|maxconnections" /etc/ssh/sshd_config一般情况下保持默认即可,除非你有明确的调优需求。
2.3 文件描述符与进程数限制
sshd作为系统服务,受 systemd 的资源限制约束。如果文件描述符(nofd)或进程数(nproc)达到上限,新的连接就无法建立,表现同样是连接被重置。
查看 sshd 服务的资源限制:
systemctl show sshd | grep -iE "limitnofile|limitnproc"如果LimitNOFILE偏低(比如 1024),在高并发场景下很容易触顶。可以通过 systemd 的 override 机制调整:
mkdir -p /etc/systemd/system/sshd.service.d cat > /etc/systemd/system/sshd.service.d/override.conf <<EOF [Service] LimitNOFILE=65535 LimitNPROC=65535 EOF systemctl daemon-reload systemctl restart sshd实操心得:调整
LimitNOFILE之后,一定要用systemctl show sshd | grep LimitNOFILE确认生效,而不是只看配置文件。systemd 的 override 有时候会被其他配置覆盖,确认生效才算数。
2.4 TCP Wrapper 的拦截逻辑
虽然现在很多新系统已经默认不安装 TCP Wrapper,但一些老系统或者特定发行版仍然在用/etc/hosts.deny和/etc/hosts.allow来控制访问。如果hosts.deny里有sshd: ALL这样的规则,而hosts.allow里没有放行你的客户端 IP,连接就会被拒绝。
检查方式:
cat /etc/hosts.deny cat /etc/hosts.allow如果发现hosts.deny里有针对sshd的拒绝规则,而hosts.allow里没有对应的放行规则,就需要添加:
# 在 hosts.allow 中添加 sshd: 你的客户端IP注意:TCP Wrapper 的规则是“先 allow 后 deny”,如果
hosts.allow里放行了,hosts.deny里的规则就不会生效。但反过来,如果hosts.allow里没有,hosts.deny里又有拒绝规则,就会被拦截。
3. 防护工具与网络中间环节排查
3.1 fail2ban 封禁的识别与解封
fail2ban是服务端最常见的防护工具之一,它会监控 SSH 登录失败日志,达到阈值后自动封禁来源 IP。如果你的客户端 IP 被封了,表现就是连接被重置。
查看 fail2ban 状态:
fail2ban-client status sshd输出会显示当前被封禁的 IP 列表。如果你的 IP 在列表里,说明被 ban 了。解封方式:
fail2ban-client set sshd unbanip 你的客户端IP但解封只是临时措施,更重要的是搞清楚为什么会被封。常见原因包括:密码错误次数过多、使用了错误的密钥、自动化脚本频繁重试等。如果是自己的脚本导致的,需要调整脚本的重试策略;如果是密码问题,建议改用密钥认证。
实操心得:我遇到过一种情况,某台跳板机上的定时任务用错误的凭据反复尝试连接,导致跳板机 IP 被目标服务器封禁,进而影响了所有经过跳板机的连接。排查时只看客户端日志是看不出来的,必须去服务端看 fail2ban 日志才能定位。
3.2 网络中间设备的会话限制
有些网络环境(比如某些云平台的安全组、企业防火墙、NAT 网关)会对单个 IP 的并发连接数或新建连接速率做限制。当连接数超过阈值时,中间设备会直接重置连接,客户端看到的就是Connection reset by peer。
这类问题的特征是:间歇性出现,且与连接频率正相关。连接越频繁,越容易触发;隔一段时间再连,可能就正常了。
排查方法:
- 在服务端抓包,看是否收到了客户端的 SYN 包
- 如果服务端收到了 SYN 但没收到后续的 ACK 或 SSH 协议数据,说明中间设备可能拦截了
- 如果服务端根本没收到 SYN,说明问题在更上游的网络路径
抓包命令示例:
tcpdump -i any -nn port 22 -w ssh_debug.pcap抓包后分析,重点看 TCP 三次握手是否完成、SSH 协议版本交换是否发生。
3.3 DNS 反向解析超时
sshd默认会尝试对客户端 IP 做反向 DNS 解析,用于日志记录。如果 DNS 服务器响应慢或者不可达,sshd会在这个环节卡住,超过LoginGraceTime后断开连接。客户端看到的就是连接被重置。
解决方式有两种:
- 在
sshd_config中关闭反向解析:UseDNS no - 确保服务端的 DNS 配置正常,能快速解析客户端 IP
推荐第一种,直接关闭:
echo "UseDNS no" >> /etc/ssh/sshd_config systemctl reload sshd注意:关闭
UseDNS后,SSH 日志里记录的就是 IP 而不是主机名。如果你依赖主机名做审计,需要权衡一下。不过大多数场景下,IP 已经足够定位问题了。
3.4 GSSAPI 认证导致的握手延迟
GSSAPI认证在某些环境下会导致 SSH 握手阶段出现明显延迟,严重时可能触发超时断开。如果你不需要 GSSAPI 认证,可以在客户端和服务端都关闭它。
客户端配置:
# ~/.ssh/config Host * GSSAPIAuthentication no服务端配置:
# /etc/ssh/sshd_config GSSAPIAuthentication no关闭后重载配置,再测试连接速度。
4. 常见问题速查与实战排查记录
4.1 问题速查表
| 现象 | 可能原因 | 排查命令 | 解决方式 |
|---|---|---|---|
| 间歇性连接重置 | MaxStartups 触顶 | grep MaxStartups /etc/ssh/sshd_config | 调大 MaxStartups |
| 特定 IP 无法连接 | fail2ban 封禁 | fail2ban-client status sshd | 解封并排查原因 |
| 连接卡住后重置 | DNS 反向解析超时 | grep UseDNS /etc/ssh/sshd_config | 设置 UseDNS no |
| 高并发时大量重置 | 文件描述符耗尽 | systemctl show sshd | grep LimitNOFILE | 调大 LimitNOFILE |
| 所有连接都失败 | sshd 未监听或崩溃 | systemctl status sshd | 重启 sshd 并查日志 |
| 特定网段无法连接 | TCP Wrapper 拦截 | cat /etc/hosts.deny | 在 hosts.allow 放行 |
| 握手阶段延迟高 | GSSAPI 认证 | ssh -vvv观察卡点 | 关闭 GSSAPIAuthentication |
4.2 一次完整的排查记录
某次线上环境出现批量连接重置,现象是:大约 30% 的 SSH 连接在握手阶段被重置,重试后有时能成功。按照下面的顺序排查:
第一步,客户端ssh -vvv确认卡在SSH2_MSG_KEXINIT之前,说明服务端在协议握手前就断了。
第二步,服务端journalctl -u sshd -n 200查看日志,发现大量error: maximum authentication attempts exceeded和Connection closed by authenticating user。这说明有大量未认证连接在冲击服务端。
第三步,ss -tn state established '( dport = :22 )' | wc -l查看当前连接数,发现稳定在 80 左右,而MaxStartups是默认的10:30:100。未认证连接数长期在 30 以上,触发了随机拒绝。
第四步,进一步排查发现,某台应用服务器上的定时任务配置错误,每秒尝试建立 5 个 SSH 连接,且全部使用错误的凭据。这些连接在认证阶段失败,但会占用未认证连接配额。
第五步,修复定时任务配置,同时将MaxStartups调整为100:30:200,问题消失。
这个案例的教训是:不要只盯着报错本身,要顺着连接链路往上找。Connection reset by peer只是结果,真正的原因可能在应用层的错误配置。
4.3 避坑技巧汇总
- 不要急着重启 sshd:重启会断开所有现有连接,如果是生产环境,影响面很大。先排查原因,确认需要重启再操作。
- 善用
ss而不是netstat:ss更快更详细,netstat在很多新系统上已经默认不安装了。 - 日志级别调高:临时将
sshd_config中的LogLevel调为VERBOSE,可以获取更详细的握手信息,排查完记得调回来。 - 客户端也用
-vvv:不要只看服务端日志,客户端的详细输出能告诉你连接卡在哪个阶段。 - 注意时间同步:如果服务端和客户端时间偏差过大,某些认证方式可能会失败,虽然不直接导致这个报错,但会增加排查复杂度。
提示:如果你在云平台上遇到这个问题,优先检查云平台的安全组和网络 ACL 规则。有些云平台的安全组会对 SSH 连接做速率限制,超过阈值就会重置连接。这种情况在服务端日志里往往看不到任何记录,因为连接根本没到达服务端。
4.4 长期预防建议
与其每次出问题再排查,不如提前做好预防。几个实用的建议:
- 定期检查
sshd的并发连接数,设置监控告警 - 合理配置
MaxStartups,根据实际业务并发量调整 - 关闭不必要的认证方式和 DNS 解析,减少握手环节
- 对应用层的 SSH 连接做连接池管理,避免频繁新建连接
- 保留足够的日志轮转周期,方便事后追溯
我个人在实际操作中的体会是,这类问题的排查效率取决于你对 SSH 握手流程的理解程度。如果你清楚TCP 握手 -> 协议版本交换 -> 密钥交换 -> 认证这个顺序,就能通过ssh -vvv的输出快速定位卡点,而不是盲目尝试各种配置。另外,服务端的日志永远比客户端的报错更有价值,养成先看服务端日志的习惯,能帮你省下大量时间。