你有没有遇到过这种情况:正在SSH终端里盯着一个长时间任务跑日志,切出去看了会儿文档,回来发现Xshell窗口卡成了白色,等几秒后直接弹出“连接已断开”。更气人的是,重新连上去,任务已经跑一半没了,日志全丢。这几乎是每个运维和开发都踩过的坑。我最早处理这类问题时也走了不少弯路,查防火墙、看网络、改超时时间,折腾一圈才发现,Xshell会话保持和SSH保活这事,得从客户端、服务端、网络设备三个层面一起下手,缺一个都不行。这篇文章我就把这几年积累的配置经验和排查思路完整写下来,面对“连接总断”这个问题,哪些参数必须改,为什么改,改完怎么验证,一次讲透。
1. 为什么设置完会话保持还是会断?先认清掉线的三个环节
很多人一上来就直接在Xshell里勾选“保持活动状态”,以为万事大吉。用了一两天发现连接该断还是断,于是得出结论:这功能没用。实际上不是没用,而是只做了一半。要弄清楚这个问题,得先理解一条SSH连接从你的电脑到远端服务器,中间到底经过了哪些环节。
1.1 一条SSH连接要穿过三道门
第一道门是你本机的SSH客户端进程。Xshell发出的所有TCP包、维护连接的保活探测,都从这里产生。第二道门是中间的网络链路,包括你家或公司的路由器、运营商网络、云平台的NAT网关、防火墙。第三道门是服务器端的sshd进程和Linux内核TCP协议栈。连接断掉,一定是三个环节里至少有一个出了问题。
最常见的情况是:你只在Xshell里发了心跳,但中间设备(比如公司防火墙、云平台NAT网关)根本不在乎你的心跳内容,它们只管自己的会话老化时间。比如某台防火墙的TCP空闲会话超时设置为300秒,你的Xshell心跳间隔是360秒,那防火墙在第300秒就把会话回收了。你再怎么发心跳,这条TCP连接在中间设备眼里已经是“半路消失”的状态,等下一包传过去,防火墙直接返回RST,连接就断了。
1.2 判断掉线环节的排查方法
遇到掉线问题,我的建议是先别急着改参数,用两步快速定位:
-
看断线时Xshell提示的具体报错。“Connection reset by peer”通常意味着中间设备或服务器主动重置;“Connection timed out”多半是网络不通或防火墙丢弃;而直接卡住很久才提示“Remote side closed connection”,则往往是服务端超时断开。
-
在服务器上同时开一个
ping或tcpdump观察。比如用tcpdump -i eth0 port 22抓包,如果服务器在断线前一段时间根本没收到任何来自你IP的TCP包,说明问题在客户端或中间链路;如果收到了包但sshd主动断开,那问题就在服务端参数。
实际经验来看,我处理过的“SSH老掉线”案例里,大约60%是中间网络设备的NAT会话老化、25%是服务端sshd的保活参数没配置、15%才是客户端设置问题。所以光指望Xshell一个选项救不了命,三个层面必须配合。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Xshell的会话保持:选项、间隔和常见误区
2.1 最常用的配置路径
Xshell的会话保持设置藏在“会话属性”里,不少人第一次找半天找不到。正确的路径是:选中左侧会话列表里的会话,右键点击“属性”,在弹出的窗口里找到“连接”选项卡,勾选“保持活动状态”,然后填一个间隔秒数。
这个“保持活动状态”的本质,是Xshell在SSH会话空闲时,每隔设定的秒数向服务器发送一个加密的keep-alive消息包。它用的是SSH协议层面的消息,不是TCP层面的空包,所以即使中间有代理也更容易穿透。间隔设置多少合适?我通常填30到60秒。太短(比如5秒)会稍微增加无谓的心跳开销,虽然这个开销几乎可以忽略,但在弱网环境下反而会增加重传风险;太长(比如300秒)又容易超过中间设备的老化时间,失去保活意义。
2.2 两种保活机制的区别
Xshell的“保持活动状态”和它底层的TCP KeepAlive是两码事,这个很多人容易混淆。在Xshell的会话属性里,除了“保持活动状态”这个勾选项,还有隐藏在更底层的全局设置里的TCP KeepAlive选项。Xshell提供的“保持活动状态”走的是SSH加密通道内的协议包,服务器一定能感知到;而TCP层KeepAlive是内核自动发的空ACK探测包,中间的路由器和防火墙可能直接忽略它,因为TCP KeepAlive包没有业务数据,NAT网关通常不认为这是“活跃流量”。
| 保活机制 | 层级 | 发出的包内容 | 中间设备感知 | 适用场景 |
|---|---|---|---|---|
| Xshell保持活动状态 | SSH应用层 | 加密协议消息 | 能感知,会刷新NAT状态 | 日常办公、跳板机连接 |
| TCP KeepAlive | 内核传输层 | 空TCP ACK | 部分设备忽略 | 服务端内核级探测 |
所以如果你的网络环境特别复杂,建议两层都打开。客户端开SSH应用层保活,服务端开内核或sshd层保活,双保险。
2.3 配置保存的小坑
很多人在“会话属性”里改完设置,直接点确定,以为保存了。但Xshell里如果会话还开着,修改的属性不会实时生效,需要重新连接或者右键会话选择“重新连接”才能应用。另外,如果你连接的是“快速连接”方式(即没保存为会话文件,直接填主机IP连的那种),属性设置的入口会不一样,得在连接窗口上方点那个小文件夹图标切换到“会话”管理器再去操作。这个细节虽然小,但真的坑过不少人——改了半天发现没生效,以为自己哪里设错了。
3. 服务端sshd_config的心跳参数:决定权其实在服务器手里
3.1 三个关键参数的作用与组合逻辑
连接最终断不断,服务器端的sshd拥有最终解释权。因为TCP连接是双端的,一方认为超时就可以主动断开。所以就算客户端一直发心跳,如果服务器端的超时判断更激进,照样会把连接杀掉。更重要的是,Xshell的“保持活动状态”只是让客户端单方面发消息,但如果服务器端sshd关闭了对应机制,这些消息能不能被纳入“活动”计算,不同OpenSSH版本行为还有差异。
为了从根上解决,我强烈建议在服务器的 /etc/ssh/sshd_config 里配置这几个参数:
code复制TCPKeepAlive yes
ClientAliveInterval 60
ClientAliveCountMax 3
-
TCPKeepAlive:控制sshd是否向内核发送TCP keepalive探测消息。设为yes后,如果连接长时间没有数据,内核的tcp_keepalive机制会介入,探测对端是否存活。需要注意的是,这个参数探测的是“TCP层面”的对端是否可达,如果中间有设备只转发数据但不代理TCP,有些情况下仍会误判。 -
ClientAliveInterval:这是真正意义上的应用层心跳。sshd会每隔这个秒数向客户端发送一条加密的存活探测消息,如果客户端正常就返回响应,sshd认为连接健康。设为60就是每分钟探测一次。 -
ClientAliveCountMax:允许客户端连续多少次不响应探测消息后才判定连接死亡并断开。设置为3,配合60秒的间隔,意味着如果客户端连续3次(也就是3分钟)没有任何响应,服务器才会主动kill这条SSH连接。
三个参数组合起来的逻辑是:先靠TCPKeepAlive在内核层面盯着,再用ClientAliveInterval做应用层心跳,最后用ClientAliveCountMax控制“断连耐心值”。这套配置的最终效果是,一个真正死掉的连接最多3分钟就会被服务端清理,而正常办公时只要客户端网络通畅,每分钟都有心跳刷新,永远不会触发超时。
3.2 修改和验证步骤
改配置的方式没啥新鲜的:
bash复制sudo vim /etc/ssh/sshd_config
找到对应的行,没有就自己加。改完后一定要重启sshd服务才生效:
bash复制sudo systemctl restart sshd
重启ssh服务不会影响已经建立的连接,只会让新连接使用新配置,这点可以放心。验证是否生效,用sshd自带的测试命令:
bash复制sshd -T | grep -E "clientalive|tcpkeepalive"
能看到 clientaliveinterval 60、clientalivecountmax 3、tcpkeepalive yes 这些输出,就说明配置已经加载。再进阶一点的验证方式,是从另一台机器连上来,然后用 ss -tnp 查看连接状态,或者在服务器上 tcpdump -i any port 22 抓包,每隔60秒应该能看到一个长度为几十字节的加密探测包,这就说明服务端在主动保活了。
3.3 为什么服务端设置优先级更高
从协议机制上看,SSH连接的生命周期是由两端共同维护的,但断开动作通常由“先判定超时”的那一方发起。如果客户端不设心跳而服务端设了,服务端每隔60秒主动发探测,客户端的Xshell也会自动响应,这样连接一样能保持。反过来如果只有客户端发心跳而服务端不设置,虽然大多数情况下也能保活,但一些旧版本OpenSSH对新收到的客户端密文消息处理得没那么积极,它自己的空闲超时计时器仍然在走,最终可能照断不误。所以我的结论是:服务端的配置比客户端更关键,有条件的话两边都配,没条件优先保服务端。
4. 网络设备不讲武德:NAT会话超时和防火墙老化机制
4.1 问题的真正来源
有一类掉线,无论你客户端、服务端怎么调都不管用,因为问题出在中间的NAT设备或防火墙上。家用路由器、企业出口防火墙、云平台安全组背后的NAT网关,都有会话状态表的自动老化机制。一条连接长时间没有数据流动,状态表条目就会被清理,后续数据包如果还要走这条连接,NAT设备发现自己“根本不认识这个会话”,大概率直接丢包或回RST。
NAT会话超时时间从30秒到20分钟的都有,但最常见的两个档位是300秒和600秒。如果业务环境下你设置的心跳间隔大于NAT老化时间,那即使两边都正常,连接也撑不过NAT那一关。
4.2 如何探测中间设备的会话超时时间
想知道你这条链路中间设备的超时时间,有个土办法:先连上SSH并让会话完全空闲(不要有任何键盘输入),然后每分钟记录一次“是否能正常敲命令”。比如你在Xshell里把心跳间隔临时改成很大的值(比如3600秒),然后从第1分钟开始,每分钟敲一个回车或echo,看第几分钟开始窗口卡住。如果第4分钟还正常、第5分钟开始卡,那中间超时大概率就是300秒,你要做的就是把心跳间隔调到小于300秒,比如30到60秒,留足余量。
这个方法虽土,但在生产环境里非常有效。注意测试时要保证客户端和服务端本身不产生任何额外保活流量,所以测试前最好把Xshell的会话保持选项临时取消,服务端也临时把ClientAliveInterval调大或用备份配置,避免干扰判断。
4.3 应对中间设备断连的整体策略
要避免被中间设备“拆桥”,唯一的思路就是让链路在NAT老化时间内始终有“看起来像业务流量”的包通过。SSH加密的心跳包长度为几十字节,看起来和真实的业务交互包没本质区别,NAT设备会正常刷新会话状态。所以只要保证心跳间隔小于最小的老化时间,就能让NAT一直认为这条连接是活跃的。
另外很多人忽略的一点:云服务器控制台里的“安全组”或“防火墙策略”,如果配置了“空闲连接超时自动切断”之类的功能(部分云平台有这个选项),也会无视任何心跳直接断连。遇到这种情况,要么在控制台把对应端口或协议的超时调大,要么联系云厂商确认有没有这种机制。这类场景我用云平台时遇到过一次,后来是直接在安全组策略里把TCP空闲超时改成了3600秒才解决。
5. 系统级保活:Linux内核TCP KeepAlive参数的调整边界
5.1 默认参数为什么不够用
Linux内核自带TCP KeepAlive机制,但它默认的行为路径是:一条TCP连接如果空闲达到2小时(net.ipv4.tcp_keepalive_time默认7200秒),内核才开始尝试发出探测包。也就是说,默认情况下,一条空闲的连接要等2小时才会被内核“关心”,这远远超过多数网络设备的会话老化时间。
对于SSH这种交互式连接,2小时的初始探测时间太长了。做运维的人经常开着十几个会话,可能一上午都不碰其中几个,等下午回来想操作时连接早就凉了。这时就算服务端的sshd参数没配置,我们也可以直接调整内核参数让TCP保活在更短时间介入。
ini复制# /etc/sysctl.conf 追加以下内容
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
tcp_keepalive_time:连接空闲多久后开始第一次探测。设300表示空闲5分钟就开始探测。tcp_keepalive_intvl:每次探测之间的间隔,默认75秒,改成30秒更灵敏。tcp_keepalive_probes:连续探测多少次没响应就判定连接断开,默认9次。改成3次,意味着如果对端真的失联,最多再等90秒就能释放资源。
保存后执行 sysctl -p 生效。
5.2 内核参数和sshd参数的配合关系
要提醒一下这里的边界:tcp_keepalive_time 设置的是“内核TCP栈主动开始探测”的时间,而sshd的 ClientAliveInterval 是“应用层发SSH协议消息”的间隔。两者是互相独立的机制,可以同时启用,也可以只启用其中一个。
一般情况下,我觉得优先配好sshd的 ClientAliveInterval 就够了,因为SSH层的探测更“懂业务”,中间设备的兼容性也更好。只有当你的SSH服务端不方便改配置(比如连接的是别人管的路由器或网络设备)时,才需要靠内核参数兜底。
5.3 哪些场景不建议动内核参数
内核参数是全局生效的,修改它影响的不仅仅是SSH,还包括这台机器上的所有TCP连接(比如Web服务的长连接、数据库连接池等)。在以下场景下,我不建议直接调全局内核参数:
- 生产环境有严格的变更流程,不方便动系统级配置。
- 机器同时承载高并发业务,TCP连接非常多,频繁的空闲探测会给系统增加无谓负担。
- 中间有负载均衡器(LB)主动管理长连接,LB自己会配置空闲超时,内核级的keepalive可能打乱LB的连接复用策略。
这种情况建议只在sshd层面配置,不动内核。范围越小,影响面越可控,这是运维的基本素养。
6. 比保活更可靠的兜底:tmux和autossh组合才是最终答案
6.1 心跳保活只解决“不断线”,不解决“网络真断了”
话说到这必须泼一盆冷水:所有KeepAlive和心跳机制,追求的只是“让正常的空闲连接不被误杀”。但如果断网是物理层面的(比如你笔记本Wi-Fi掉了、网线被踢了、公司出口光缆被挖断了),客户端和服务端的TCP连接就是真的断了,任何保活机制都无法阻止。这种情况下的唯一指望,是你重新连上后,之前的工作还能恢复。
这就要用到两个工具:tmux 和 autossh。
6.2 tmux:把任务和SSH连接解耦
tmux是个终端复用器,它可以在服务器上创建一个持续运行的会话,这个会话和你的SSH连接是独立的。SSH断了,tmux会话还在服务器上继续跑;你重新SSH上去,tmux attach 就能回到原来的现场,连屏幕里的输出历史都还在。
bash复制# 创建一个名为work的tmux会话
tmux new -s work
# 在会话里运行长时间任务
tail -f /var/log/app.log
# 按 Ctrl+b 然后按 d 退出会话(但任务继续跑)
# 重新连接服务器后,回到原会话
tmux attach -t work
这个习惯我强烈建议每个用SSH的人都养成。之前我在服务器上跑数据迁移,预计要跑两小时,中途家里网络闪断,重新连上后直接 tmux attach,迁移日志还停在断线前的位置,任务继续跑完,一点没受影响。如果没有tmux,那次迁移就得从头再来。
另外tmux还可以开多个窗口,一个窗口看日志,一个窗口跑命令,一个窗口留着应急操作,比裸SSH体验好太多了。
6.3 autossh:自动重连的SSH守护者
autossh 是一个对SSH连接进行监控和自动重启的开源工具,它每隔一段时间检查SSH进程和底层连接状态,发现连接异常就自动重新发起SSH连接,并把之前的所有参数透传下去。它能让你“感知不到掉线”,至少能做到掉线后在几秒内自动恢复连接。
bash复制# 以保活参数自动重连
autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" user@server
这里的 -M 0 是让autossh不额外占用一个监控端口,而是直接使用OpenSSH自带的ServerAliveInterval参数作为健康检查,这也是官方推荐的现代用法。
6.4 我个人的完整工作流
code复制Xshell定时心跳(30秒会话保持)
+ 服务端sshd_clientAlive(60秒,容错3次)
+ tmux承载工作任务
+ (可选)autossh自动重连
= 稳如老狗
如果只是日常办公连个服务器,Xshell心跳加服务端sshd参数,基本就不掉线了。如果要在服务器上跑长时间任务,tmux是必须的。如果办公网络环境极其恶劣,频繁断线,autossh可以让你的SSH窗口就像“没断过一样”,配合tmux恢复现场,体验极佳。
我个人的实操习惯是:Xshell会话语属性里填30秒心跳,所有管理的Linux服务器统一配好sshd的ClientAliveInterval和ClientAliveCountMax,登录进任意一台机器后第一件事如果有长时间任务就新建一个tmux会话。这套组合拳用了三年多,中途几乎没再因为SSH断线丢过工作现场。其实配置这东西,难的不是参数,而是理解每一层保活机制分别解决哪类问题。搞懂了,以后不管换什么终端工具、连什么设备,都能快速找到对应的配置项,不会再被“连接又断了”折腾到怀疑人生。
