VPN 上的 SSH ServerAliveInterval:探测包由哪一端发出?
ServerAliveInterval 是客户端计时器,统计的是未应答的探测次数,而不是秒数。本文说明哪一端发送探测、Host 块顺序为何优先,以及如何验证。
大多数关于保持 SSH 会话存活的建议都停在「加上 ServerAliveInterval 30」,却漏掉了真正有用的部分。这个关键字是客户端的计时器:只有在通道安静下来之后才会触发,它统计的是未被应答的探测次数而不是秒数,而且它住在一个「先匹配者胜」的文件里。这三点中任何一点理解错,健康的 VPN 会话照样断开,而死掉的会话反而一直挂着。
两个方向属于不同的守护进程
ServerAliveInterval 由你的 ssh 客户端读取。ClientAliveInterval 由对端的 sshd 读取,并向相反方向探测,即从服务器到客户端。二者不是别名,也不会互相配合,因此设置其中一个不会影响另一端的表现。把它们搞混,正是「同一个修法在一台主机上有效、下一台却无效」的常见原因。
两者都接受零。取零时客户端完全不发包;在服务器一侧,ClientAliveCountMax 为零会关闭原本会在未应答检查之后发生的断开。
探测能证明什么,不能证明什么
当设定的间隔过去而服务器没有任何数据到达时,客户端会通过现有的加密通道发出一个小请求,并等待回应。ServerAliveCountMax 决定允许多少次探测得不到应答之后才拆掉会话。间隔 60、次数 3 时,客户端大约在三分钟的沉默后放弃,而这个数字取决于你的配置,并非网络标准。把间隔留在默认值 0,就永远不会发出探测,所以一个标准客户端可以无限期地停在一条死连接上。
由于交换发生在 SSH 传输层内部,收到回应只能证明对端的 SSH 进程仍在读写。它并不说明你在该会话中启动的程序:数据库客户端、端口转发或长时间运行的脚本完全可能已经卡死,而传输层仍能愉快地应答探测。
TCPKeepAlive 是低一层的另一种机制。它由内核处理,不经加密,即使对端的 SSH 守护进程已经卡死,也仍然可以满足它。所以当你关心的是会话而不是套接字时,SSH 层的探测才是更有意义的信号。
顺序决定哪个值最终生效
OpenSSH 对每个参数采用它拿到的第一个值,因此具体的 Host 块必须出现在任何同样会匹配的更宽泛块之前。把带通配符的默认值放在文件末尾,而不是开头,并记住更靠下的 Match 块无法覆盖已经设置好的值。OpenSSH 客户端配置参考直接写明了「首值优先」的规则,在重新整理共用配置之前值得先读一次。
命令行选项优先于配置文件,所以临时实验的成本很低:
ssh -o ServerAliveInterval=15 -o ServerAliveCountMax=4 office-shell想看出客户端实际解析成了什么,而不是你以为写了什么,就让它把生效配置打印出来。输出里是合并后属于该主机名的值,也是抓出前面那条乱入块最快的方法:
ssh -G office-shell | grep -i alivekeepalive 在隧道上能做什么
周期性的流量可以防止有状态的 NAT 或防火墙映射过期,这对网关在几分钟后就会丢弃空闲流的 VPN 来说是实打实的好处。每 60 秒到达一次的探测会刷新这条映射,却不携带任何应用数据。
它无法重建已经消失的路径。隧道接口一旦消失,探测也和其他一切一样失败,计数用尽后会话就结束。存活探测和路径恢复是两个不同的问题,而只有前者能在 ssh_config 里配置。间隔和次数真正买到的,是客户端多快能察觉,而这正是用户感受到的「卡住」。
多路复用的连接会多一道褶皱。启用 ControlMaster 时,探测属于主连接,而建立在已有主连接之上的新会话会继承那个主连接协商出的结果。如果 mux 主连接已经卡死,再开一个窗口并不会重新测试路径。
从端到端确认设置
在服务器上,不要把文件读一遍再去猜哪个 include 生效,而是直接打印守护进程的生效配置:
sshd -T | grep -i clientalive然后用开启调试输出的真实会话观察。计数用尽时客户端会打印超时行,而前后时间戳能说明沉默是连续的,还是探测根本从未发出。没有任何探测流量就死掉的会话,通常把关键字放在一个从未匹配的块里。
对任何只能通过隧道到达的主机,值得多加两项检查:确认解析出的间隔正是你想要的,并确认该间隔乘以次数之后,短于 VPN 网关上的空闲超时。当第二个数字更大时,网关会先悄悄丢掉流,SSH 探测根本没有机会报告任何东西。