
SSH 连接频繁掉线这个问题我真的是被折腾到没脾气之后才彻底搞明白的。手里管着几十台云上服务器经常是部署脚本跑到一半终端就卡死合上笔记本再打开所有会话全部超时还得重新输密码重新建会话。排查了大半个月发现真正的问题往往不在 SSH 服务本身而在网络链路、NAT 设备、以及两端配置的细节配合上。这篇文章把我踩过的坑、验证过的方案以及最终的稳定配置全部整理出来希望能让正在被同样问题困扰的人少走弯路。先说结论方向掉线分两类一类是“连接空闲久了被人为掐断”一类是“网络链路本身不稳导致连接断开”。前者靠 keepalive 心跳解决后者靠重连机制兜底。绝大多数情况下你需要的不是换 SSH 工具而是把两端的参数配好。1. 先给“掉线”分个类三分钟判断你的问题属于哪一种很多人一上来就改配置结果越改越乱。我建议先花三分钟定位问题属于哪种场景对症下药效率最高。1.1 空闲久了被掐断NAT 老化与“安静连接”的宿命最常见的场景终端挂着去开个会回来一看已经断线。或者传输大文件中途停了几分钟连接就没了。这种掉线的根源十有八九是网络中间的 NAT 设备或防火墙把“安静”的 TCP 连接给清了。原理也不复杂——大到一个公网网关小到你家路由器都会维护一张会话映射表这张表有容量上限。一个连接长时间没有数据包经过就会被判定为“死连接”从表里删掉。家用路由常见的 TCP idle timeout 只有 300 秒到 600 秒也就是说连接空转五分钟到十分钟就会被清理。可以理解为你在前台登记了一个访客牌规则是十分钟内没有任何活动就作废。你一直不来办事牌子就会被回收回到工位才发现进不了门。SSH 默认的 TCP keepalive 间隔是 7200 秒内核参数 net.ipv4.tcp_keepalive_time这个时间远超 NAT 老化时间所以掉线是必然的。理解了这一点后面配置心跳机制的原理就清楚了。1.2 传输途中不稳定跨公网的长连接本来就是高危场景第二种场景连接偶尔能连上但传文件传一半就断或者跑长任务时随机卡死。这种往往不是配置问题而是网络本身在丢包。如果你在公司、家庭、机房之间来回连中间要经过运营商骨干网、城域网、甚至跨越不同运营商。链路任何一处发生拥塞TCP 就会重传重传超过一定时间依然失败系统就会判定连接死亡。尤其是跨运营商线路晚高峰时段丢包概率明显上升。WiFi 环境更严重2.4GHz 频段干扰大偶尔一两秒的断流就能让 SSH 卡住。这种场景下单纯靠 keepalive 救不了你因为 keepalive 只能让连接不停活不能修复物理链路的丢包。1.3 工作一会就断先怀疑服务端和客户端的配置冲突第三种场景相对隐蔽连接建立后能正常用一会儿但运行十几分钟到半小时突然报 Connection closed / Broken pipe。这时候要关注两端是否启用了 ssh 层面保活参数以及服务的会话超时策略。比如你客户端开了 ServerAliveInterval但服务端开了强制空闲超时两边策略冲突就会导致保活包发出去了却没人理。还有一种常见情况服务端反向 DNS 解析超时导致 SSH 连接建立时响应慢表现为“连接卡住”甚至最终失败。这属于配置层面的问题也是本文要重点解决的。2. 抓包和日志先走起一次完整的掉线定位流程排查 SSH 掉线最忌讳盲改参数。正确姿势是先看日志、再抓包确定方向后再动手。我自己从“怀疑人生”到“十分钟定位”靠的就是下面这套流程。2.1 服务端日志是破案的关键SSH 服务端记录的日志信息量非常大。Debian/Ubuntu 系看/var/log/auth.logRHEL/CentOS 系看/var/log/secure用 systemd 的系统也可以直接看 journald# Debian/Ubuntu tail -f /var/log/auth.log | grep sshd # RHEL/CentOS tail -f /var/log/secure | grep sshd # systemd 通用 journalctl -u sshd -f如果掉线是服务端主动掐断的日志里会明确写着Timeout, client not responding。看到这行字基本可以确定服务端已启动保活检测并且多次未收到客户端响应于是主动断开了连接。如果日志里什么也没有说明服务端根本没感知到异常连接是断在半路的通常是被中间设备静默丢弃。这就说明问题在网络链路不在服务端。这两种日志表现对应的排查方向完全不同一定要先分清。2.2 tcpdump 看包是没收到还是被重置了日志之外tcpdump 是最直接的证据来源。在服务端上执行tcpdump -i eth0 -nn port 22观察连接断开前的数据包行为通常能看到几类典型特征保活包发出去后对方没有任何回复紧接着出现多次 TCP 重传最终连接消失 —— 说明中间链路断了。包交互过程中突然出现一个[R]RST包通常是中间防火墙主动重置连接。断开前一段时间完全没有任何包交互 —— 说明保活机制没生效连接是真的“安静”到被 NAT 清了。抓包看到的现象比任何日志都直白。我以前遇到一个“午夜十二点准时掉线”的诡异情况抓包一看是机房防火墙凌晨任务重置了 idle 连接跟 SSH 配置半点关系没有。2.3 ping、mtr、traceroute区分网络层与应用层问题还需要确认网络链路是否健康。mtr 比单纯 ping 更有用它能展示到目标 IP 每一跳的丢包率和延迟mtr -rwz -c 100 服务器IP如果 mtr 结果显示某几跳出现零散丢包基本可以认定是链路问题如果整条链路丢包率都是 0%但 SSH 还会掉则优先怀疑 NAT 会话老化而不是“网络不好”。这里有个容易误判的点连接断了之后马上重新 SSH 又能连上。这恰恰说明网络本身是通的只是长连接会话被清了。如果网络真的不通你连新连接也建立不起来。3. 服务端配置sshd_config 里这几个参数决定了连接寿命确定服务端是主动断开的一方之后就该动sshd_config了。记住改之前先备份并且不要断开当前已建立的会话最好提前开一个 tmux 窗口跑着万一配置改错了还有后路。3.1 TCPKeepAlive、ClientAliveInterval、ClientAliveCountMax 各管什么这三个参数经常被混淆我把它们的职责拆开讲TCPKeepAlive yes让 SSH 使用操作系统层面的 TCP 保活机制。但注意它依赖内核参数net.ipv4.tcp_keepalive_time默认 7200 秒才发一次探测包根本防不住 NAT 老化。ClientAliveInterval 60服务端每隔 60 秒主动向客户端发送一个 SSH 协议层的保活消息。关键区别是这会立刻产生真实的 TCP 数据包能有效延长 NAT 会话老化时间。ClientAliveCountMax 3服务端连续 3 次未收到客户端响应判断客户端已失联主动断开连接。三个参数配合起来效果是服务端每 60 秒发一次保活如果 180 秒内客户端一直没有回应服务端断开连接。这个配置既能保持连接活跃又能自动清理真正死掉的连接防止资源占用。有个技巧很多人不知道客户端即使没有开启任何保活参数当服务端发来 SSH 保活消息时客户端协议栈也会自动回应。所以只要服务端开启了ClientAliveInterval即使客户端什么都不配连接也能保持活跃。反过来如果客户端开了ServerAliveInterval服务端也会回应。两端的保活机制是互补的。我自己实测后的推荐配置TCPKeepAlive yes ClientAliveInterval 60 ClientAliveCountMax 3 UseDNS no这组参数的含义心跳 60 秒一次足够应对绝大多数 NAT 设备。3 次无响应才断开容忍短暂网络波动。UseDNS no是另一个重要优化它禁用服务端对客户端 IP 的反向域名解析。默认开启时每次连接服务端可能做一次 PTR 查询DNS 超时会拖慢连接甚至引发连接卡顿。在无内网 DNS 服务器时尤其明显。3.2 实际配置示例与重启验证修改/etc/ssh/sshd_config后建议先用语法检查确认没有拼写错误sshd -t确认无误后重载配置# 不是迫不得已尽量用 reload不会中断现有会话 systemctl reload sshd # 或者 systemctl restart sshd重启后怎么确认参数生效了用这个命令查看实际生效的配置而不是看注释掉的模板sshd -T | grep -E clientalive|tcpkeepalive|usedns输出应该是tcpkeepalive yes clientaliveinterval 60 clientalivecountmax 3 usedns no看到这几行服务端配置就稳了。3.3 顺手解决 UseDNS 导致的连接缓慢很多人掉线的体验其实是“连接卡了半天然后失败”这里 UseDNS 的锅很大。尤其客户端 IP 是动态 IP反向解析根本查不到域名服务端要等 DNS 超时表现就是 SSH 登录转圈、间歇性连接失败。把UseDNS设为no是最直接的解法。如果系统里还开了 GSSAPI 认证Kerberos在不需要的纯密码登录场景下也可以把GSSAPIAuthentication设为no进一步减少认证阶段的额外延迟。注意如果你的企业环境确实用 Kerberos 认证就别动 GSSAPI 相关配置只改掉 UseDNS 就好。4. 客户端配置你的 SSH 工具也该主动保活服务端配好了客户端也得跟上。虽然服务端单方面就能维持连接存活但我建议两端都配双保险毕竟客户端代码更了解自己用户的使用状态。4.1 命令行 ssh 的 ~/.ssh/config 写法我在所有管理机的用户目录下放了一份统一配置Host * ServerAliveInterval 60 ServerAliveCountMax 3 TCPKeepAlive yes ConnectTimeout 30参数作用ServerAliveInterval 60客户端每 60 秒向服务端发送一个保活请求。ServerAliveCountMax 3客户端连续 3 次未收到服务端响应断开连接。ConnectTimeout 30建立连接时如果 30 秒内没连上放弃重试。避免 IP 不通时长时间卡住。这两组参数和服务端的意义几乎一一对应道理完全一样定期发心跳阻止中间设备把连接当“死连接”清掉。有的发行版上没有/etc/ssh/ssh_config的文件权限直接改用户级~/.ssh/config最灵活。注意如果这个文件存在但没被引用检查~/.ssh目录权限是否过宽权限不正确 ssh 会直接忽略配置文件。4.2 VSCode Remote-SSH、MobaXterm、Termius 这类工具怎么设置如果你用 VSCode Remote-SSH 连服务器配置逻辑一样只是入口不同。VSCode 的 Remote-SSH 插件最终是通过命令行 ssh 工作的所以改~/.ssh/config对 VSCode 也有效。如果在 VSCode 里仍然出现掉线可以在用户设置里把remote.SSH.remoteServerListenOnSocket设为true这个参数影响远端 server 的连接方式有时候能解决“VSCode 窗口一开一关就断”的问题。Windows 上常用的 MobaXterm 直接在图形界面里设置也很方便新建 SSH 会话时进入Advanced SSH settings勾选Use keepalive间隔填60秒。Termius 桌面版则在会话编辑器的SSH选项里修改 keepalive 间隔。SecureCRT 则在终端选项的“保持活动”里设置。很多图形工具默认是不开 keepalive 的这是个容易忽略的坑。装好工具第一件事先把这项目打开能少掉 80% 的线。4.3 批量管理多台服务器时如何统一处理保活参数管理几十台服务器时你一定不希望每台都手动去改。用 Ansible 推配置很快- name: 配置 sshd 保活参数 hosts: all tasks: - name: 设置 ClientAliveInterval lineinfile: path: /etc/ssh/sshd_config regexp: ^ClientAliveInterval line: ClientAliveInterval 60 - name: 设置 ClientAliveCountMax lineinfile: path: /etc/ssh/sshd_config regexp: ^ClientAliveCountMax line: ClientAliveCountMax 3 - name: 禁用 DNS 反查 lineinfile: path: /etc/ssh/sshd_config regexp: ^UseDNS line: UseDNS no - name: 重载 sshd service: name: sshd state: reloaded没有 Ansible 的用一条循环命令也能搞定效果一样。核心是批量推送一致配置省得每台机器行为不一致掉线表现千奇百怪。如果公司有跳板机、堡垒机从堡垒机到目标机的链路也建议配置保活参数特别是堡垒机上如果常驻中继进程它的保活策略直接决定你远端会话是否能稳定存活。我遇到过一次堡垒机上连接池空闲超时设置过短导致所有人都是过一会儿就掉线的情况改 NAT 无用最终是调整堡垒机侧会话超时解决的。5. 网络侧才是隐藏大头NAT 超时、防火墙、运营商级 CGN 的锅很多人在两端配好 keepalive 之后掉线问题依然没有解决这时候就要把目光放到客户端到服务器之间的整条链路上。5.1 家用路由器和光猫NAT 会话老化时间怎么查怎么调如果你是在家里连公司、连云服务器中间的家用光猫/路由器就是第一道坎。登录路由器管理后台在“NAT”或“高级设置”里通常能找到“连接数超时时间”“TCP idle timeout”之类的参数。我见过的路由器默认值从 300 秒到 600 秒不等有的光猫甚至只有 180 秒。能改就直接改大比如改成 3600 秒。改不了运营商定制光猫经常锁死就要靠上面第 3、4 节的心跳配置兜底——因为你把心跳设为 60 秒后NAT 会话表永远不会因为空闲而被删路由器的超时设置再多也不会触发。这是唯一不受制于设备的方案。有个细节要注意光猫和路由器是两层 NAT任何一层的会话老化都可能断线。单纯调外层路由器内层光猫的标准配置没跟着调同样会出问题。所以在条件允许的情况下光猫改成桥接模式让路由器直接拨号否则至少保证两侧的超时时间都大于你 SSH 心跳间隔。5.2 公网 IP 直连的场景运营商和云厂商的 idle timeout你以为有公网 IP 就不会被 NAT 清了错。云服务器购买时给你的是一个公网 IP但出机房时几乎所有流量都会经过云厂商的 NAT 网关或负载均衡设备这些设备同样有时间戳限制叫 idle timeout。阿里云、腾讯云、AWS 之类的服务商在负载均衡、NAT 网关控制台可以查看到空闲连接超时配置一般是 300 秒到 600 秒部分支持自定义调整。如果你用的是服务器自带公网 IP大部分情况下空闲超时时间较长但保险起见还是建议把 SSH 保活间隔调到 60 秒避开这类限制。还有一种场景是跨运营商访问某运营商的 CGN运营商级 NAT会把用户侧的地址转成公网地址这个大 NAT 的会话老化时间你根本无法控制。这种情况下唯一能做的就是让连接保持“活跃”让 NAT 表永远认为这个连接还在使用。我有个同学的服务器每隔两三小时必断一次排查了半个月发现是运营商侧 NAT 超时 120 秒不管怎么调自己的设备都没用最后把心跳间隔压到 30 秒才稳定下来。5.3 WiFi、睡眠、DHCP 变化日常最容易忽略的隐性断因笔记本合盖睡眠再打开发现 SSH 全断了。这个锅真的不该让服务器背。睡眠期间无线网卡和 TCP 协议栈都被挂起连接早就断了服务端感知不到客户端意识到异常后会重新尝试连接。DHCP 租约到期重新续租如果公网 IP 变了旧的 TCP 连接自然全部失效。双网卡切换网络从有线切到无线连接源 IP 变了服务端会拒绝这个“幽灵连接”。这类问题可通过客户端ServerAliveInterval加速感知断线但不能修复物理断连。真要彻底解决得靠下一节的重连方案。6. 断线之后怎么办autossh、mosh 与 tmux 的组合拳配置好心跳之后普通掉线场景基本能解决但网络真正不稳定的时候掉线还是会发生的。重点不是“不掉线”而是“掉了能立刻恢复工作现场”。6.1 autossh让每次意外断开自动重连autossh 是 SSH 的守护壳它启动一个 ssh 进程并额外开一个监控通道来检查连接健康状态一旦发现问题就自动把 SSH 拉起来。安装方式# Debian/Ubuntu apt install autossh # RHEL/CentOS yum install autossh一个典型的使用场景本地转发端口的持久隧道。AUTOSSH_GATETIME0 \ autossh -M 0 \ -o ServerAliveInterval 60 \ -o ServerAliveCountMax 3 \ -N -L 3306:localhost:3306 \ rootserver逐项解释AUTOSSH_GATETIME0让 autossh 忽略启动阶段的瞬时报错。默认它会在 SSH 启动后等待一段时间如果期间 SSH 退出就认为“启动失败”而不重连但这个判断在系统资源紧张时容易误伤。-M 0关闭 autossh 自带的监控端口改用 ssh 协议层 keepalive这个更干净。-o直接传给 ssh 的参数与服务端配置同步。-L把远程端口映射到本地保持隧道内流量转发。autossh 适合在本地持续拉起保持隧道也适合放在systemd服务里开机自启。使用时最需要记住的一点是它必须在 SSH 掉线时能快速重连所以ServerAliveInterval一定要设否则 autossh 无法敏感感知连接死亡重连速度会变慢。6.2 mosh面对 IP 漂移时最优雅的选择mosh 是什么Mobile Shell。它和 SSH 的区别在于SSH 走 TCPmosh 走 UDP。UDP 连接不维护会话状态即使你的电脑从一个 WiFi 切到另一个 WiFi、换了 IP 地址mosh 会话依然不会断。这项能力对于笔记本用户简直是雪中送炭。安装需要两端都装# 服务端 apt install mosh # 客户端macOS brew install mosh # 客户端Ubuntu apt install mosh连接方式mosh userserver注意mosh 首次连接仍然借助 SSH 完成认证和端口协商所以 SSH 本身要能连接同时需要在防火墙放行 UDP 高位端口段常见放行 60000~60100/UDP。如果运维上不能开额外端口mosh 就没办法用。mosh 在弱网和移动网络下的表现非常优秀但它不支持 X11 转发等依赖 TCP 的功能终端兼容性也略有限制。我的使用原则是固定临时任务用 SSH tmux移动办公和弱网场景用 mosh tmux。6.3 tmux 接管会话就算断了工作现场也还在最后也是最重要的一层不管你用什么客户端、什么参数只要 ssh 连接终有一天会断你内核里跑的任务不能跟着断。这就是 tmux 的价值。基本用法# 新建会话 tmux new -s work # 断开会话不退出进程 Ctrlb d # 重新连接 tmux attach -t work用 tmux 的好处是SSH 断了重连之后一切照旧运行中的脚本、vim 的未保存内容、终端的历史输出都在那里等你。稍微进阶一点的用法让 tmux 自动附着到同名单一会话避免重复开新会话刷屏tmux new -A -s work这样你每次 SSH 上来执行这一条命令会自动回到之前的工作现场忘记attach也没关系。最后分享一个我个人的工作习惯现在任何新服务器上线的第一步不是优化性能而是先装 tmux 改 SSH 心跳。ClientAliveInterval 60、ClientAliveCountMax 3、UseDNS no这三项配好等于把绝大多数掉线问题提前摁死在襁褓里。然后每次开启长任务时都习惯性地套一层 tmux就算某天真碰上网络反复抽风也不会影响任务进程。SSH 掉线是个综合问题不是单一配置能包治百病的。本文的排查链路其实就是一个简单框架先判断掉线场景看日志抓包确定断在哪一端再按服务端、客户端、网络链路逐步调整。真正常见的坑也就是 NAT 会话老化、UseDNS 反查超时、心跳参数没配这三件事把基础打牢了剩下的问题基本都是偶发网络故障交给重连机制兜底就好。