防止SSH超时问题的最佳实践

通过客户端保活、合理的服务器设置以及tmux或screen来防止空闲SSH连接断开,适用于长时间运行的工作。

防止SSH超时问题的最佳实践

SSH超时通常表现为终端在空闲几分钟后冻结,然后显示管道破裂或连接重置错误。原因通常是防火墙、NAT网关、VPN或负载均衡器在SSH客户端注意到之前就丢弃了空闲的TCP会话。

最有效的修复方法是从客户端启用SSH保活。服务器端设置也有帮助,但它们的目的不同,如果设置过于激进,可能会断开已死亡的客户端。

理解SSH超时的根本原因

SSH超时发生在客户端和服务器之间的通信链路因双方在特定时间内未检测到活动而被切断时。这通常不是SSH软件本身的问题,而是中间网络设备(防火墙、路由器和NAT表)为了节省资源而激进地修剪空闲连接。

当防火墙在几分钟内未看到特定TCP连接上的流量时,它会假设会话已死亡并丢弃连接状态。下次SSH客户端尝试发送数据时,服务器永远不会收到,导致会话冻结并最终超时错误。

解决方案是配置SSH定期发送保活信号(小的非数据包),确保中间设备识别连接为活动状态。

1. 客户端解决方案:ServerAliveInterval

防止超时最常见且最简单的解决方案是配置SSH客户端定期向服务器发送保活消息。这由ServerAliveInterval指令控制。

ServerAliveInterval的工作原理

ServerAliveInterval指定客户端在未从服务器收到数据后,经过多少秒会向服务器发送一个空包。这个值确保客户端维护连接状态。

通过~/.ssh/config配置

推荐此方法,因为它允许你全局或按主机设置配置,并在重启和不同终端会话中持久化。

创建或修改客户端配置文件,通常位于~/.ssh/config

nano ~/.ssh/config

要全局应用设置(对所有主机):

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

值解释:

  • ServerAliveInterval 60:如果连接空闲,客户端每60秒发送一个保活包。
  • ServerAliveCountMax 3:如果客户端连续发送3个保活消息而未收到服务器响应,客户端将终止连接。(总超时时间:60秒 * 3次尝试 = 180秒)。

通过命令行配置

如果你需要临时修复或仅对单个会话应用设置,可以在连接时使用-o选项:

ssh -o "ServerAliveInterval 60" user@remote_host

提示: 30到60秒的值通常比较理想,因为它足够频繁以绕过大多数防火墙规则(通常设置为5分钟左右),但又不会过于频繁而产生过多的网络开销。

2. 服务器端解决方案:强制保活

虽然客户端解决方案(ServerAliveInterval)通常足够,但管理由许多用户访问的服务器的管理员可能希望集中强制保活设置或设置空闲连接的硬限制。这可以在SSH守护进程配置文件/etc/ssh/sshd_config中完成。

使用ClientAliveIntervalClientAliveCountMax

这些指令是客户端设置的服务器端对应项。它们指示服务器检查客户端是否仍然连接。

  1. 打开SSH守护进程配置文件:

    sudo nano /etc/ssh/sshd_config
    
  2. 添加或修改以下行:

    # 服务器在300秒无客户端流量后发送客户端保活消息。
    ClientAliveInterval 300
    
    # 在3次未响应的客户端保活消息后断开连接。
    ClientAliveCountMax 3
    

关于ClientAliveCountMax的说明:

ClientAliveInterval消息是SSH级别的探测,而不是简单的“空闲这么长时间后断开连接”设置。使用ClientAliveInterval 300ClientAliveCountMax 3,服务器仅在约15分钟未收到探测响应后断开连接。在OpenSSH中,ClientAliveCountMax 0会禁用这些服务器保活消息,因此它不是一个好的超时强制示例。

  1. 重新启动SSH服务以使更改生效:

    sudo systemctl reload sshd
    # 或
    sudo service ssh reload
    

3. 高级弹性策略

虽然SSH保活处理短时间的空闲,但完全的网络中断(例如切换Wi-Fi网络或暂时失去信号)仍然会断开TCP连接。为了真正的弹性,请使用会话管理工具。

使用终端复用器(tmuxscreen

终端复用器是防止连接断开的终极防御。它们在远程服务器上运行一个会话,即使客户端连接被切断,该会话也会持续存在。你可以分离会话,稍后重新连接(从相同或不同的客户端),并重新附加以从离开的地方继续。

基本tmux工作流程:

  1. 连接到服务器:
    ssh user@remote_host
    
  2. 在服务器上启动一个新的tmux会话:
    tmux new -s my_session
    
  3. tmux会话中工作。
  4. 如果连接断开,或者你需要离开,分离会话(Ctrl+B,然后D)。
  5. 通过SSH重新连接到服务器。
  6. 重新附加到现有会话:
    tmux attach -t my_session
    

区分SSH保活和TCP保活

可以使用底层操作系统的TCP保活机制,通常通过sshd_config中的TCPKeepAlive yes指令配置。然而,SSH级别的保活(ServerAliveInterval)通常更受欢迎,因为:

  1. 可移植性: SSH指令无论底层操作系统内核调优如何,都能一致工作。
  2. 应用层: SSH保活在应用层内运行,确保SSH守护进程保持响应。
  3. 防火墙感知: TCP保活有时会被仅检查有效负载活动的防火墙或NAT设备静默阻止,而SSH保活专门设计用于成功穿越这些层。

如果你选择使用TCPKeepAlive yes,请记住实际的时间间隔由操作系统控制(例如Linux的net.ipv4.tcp_keepalive_time),而不是由SSH配置控制。

最佳实践总结

问题 配置指令 位置 推荐值 目的
客户端超时 ServerAliveInterval ~/.ssh/config(客户端) 30 - 60秒 从客户端向服务器发送空包以防止防火墙丢弃。
客户端断开阈值 ServerAliveCountMax ~/.ssh/config(客户端) 3 - 5 客户端在断开前允许的未响应次数。
服务器空闲强制 ClientAliveInterval /etc/ssh/sshd_config(服务器) 300秒(5分钟) 从服务器向客户端发送检查以监控活动。
连接弹性 不适用 服务器会话 tmuxscreen 允许会话在网络故障时持续存在。

从客户端配置中的ServerAliveInterval开始。对于长时间运行的迁移、软件包升级或日志调查,在tmuxscreen内运行工作,以便网络路径断开不会终止任务。