SSH 登录慢不是一个单点故障,而是一条请求链路的等待:名称解析、TCP 建连、SSH 协商、认证、PAM 会话和 shell 初始化都可能阻塞。排查时最忌讳一上来就改 UseDNS、关掉 GSSAPI 或直接重启 sshd。先确定等待发生在哪一段,才能避免破坏域认证、审计或自动化登录。
现象:先把“慢”量化
先记录问题客户端、源 IP、目标主机、账号、是否经跳板机、认证方式以及开始和结束时间。至少准备一个快样本和一个慢样本;快样本可以是同一客户端访问另一台服务器,也可以是另一客户端访问同一服务器。
在客户端执行下面的最小测试:
ssh -vvv -o ConnectTimeout=10 ops@ssh.example.internal true
将 ops 替换为测试账号,将 ssh.example.internal 替换为目标域名或 IP。末尾的 true 会在认证成功后立即退出,可把 shell 初始化影响与认证影响初步分开。命令只建立一次正常 SSH 会话,不修改远端配置。
示例输出:
debug1: Connecting to ssh.example.internal [10.20.30.40] port 22.
debug1: Connection established.
debug1: Authenticating to ssh.example.internal:22 as 'ops'
debug1: Offering public key: /home/ops/.ssh/id_ed25519 ED25519 SHA256:...
debug1: Server accepts key: /home/ops/.ssh/id_ed25519 ED25519 SHA256:...
Authenticated to ssh.example.internal ([10.20.30.40]:22) using "publickey".
debug1: Sending command: true
停在 Connecting to 前后,优先查 DNS、路由和 TCP;停在 Authenticating to 附近,重点查 GSSAPI、反向解析、PAM 和目录服务;已经出现 Authenticated to 但还在等待,重点查会话脚本、home 目录、挂载和 motd。这个分段比“登录耗时 8 秒”更有价值。
信息收集:确认客户端实际行为
客户端配置常会覆盖系统默认值。先查看解析结果、实际路由和展开后的 SSH 配置:
date -Is
getent ahosts ssh.example.internal
ssh -G ssh.example.internal | sed -n '1,140p'
ip route get 10.20.30.40
特别关注 proxyjump、proxycommand、addressfamily、gssapiauthentication、preferredauthentications 与 connecttimeout。若走跳板机,分别对跳板机和最终目标执行 ssh -vvv;最终连接实际有两段网络和两次认证,不能只测最后一个别名。
服务端先做只读检查:
sudo sshd -T | sort
sudo ss -ltnp '( sport = :22 )'
sudo journalctl -u sshd --since '20 minutes ago' --no-pager
sudo journalctl -u ssh --since '20 minutes ago' --no-pager
不同发行版 service 名称不同,可能是 sshd 或 ssh。RHEL 系认证日志常见于 /var/log/secure,Debian/Ubuntu 常见于 /var/log/auth.log,但优先使用 journal,避免假定日志路径。对含 Match 条件的配置,应按问题用户和源地址检查有效值:
sudo sshd -T -C user=ops,addr=10.20.30.50,host=ssh.example.internal | sort
把 10.20.30.50 替换为问题客户端的真实源地址。该操作不重载服务,但能发现用户命中了与主配置不同的规则。
初步判断:用对照缩小范围
先做域名与 IP 的对照:
time ssh -o ConnectTimeout=10 ops@10.20.30.40 true
time ssh -o ConnectTimeout=10 ops@ssh.example.internal true
若 IP 快、域名慢,再查 DNS;两者都慢时,不要把 DNS 当成默认根因。再按地址族测试:
time ssh -4 -o ConnectTimeout=10 ops@ssh.example.internal true
time ssh -6 -o ConnectTimeout=10 ops@ssh.example.internal true
仅 IPv6 慢时,修复 IPv6 路由、ACL、AAAA 记录或相应出口策略。全局强制 IPv4 会影响纯 IPv6 环境,并掩盖网络基础问题。
可以把判断结果整理成下面四类:
| 阶段 |
主要证据 |
常见方向 |
| 解析前 |
域名慢、IP 快 |
DNS、搜索域、IPv6 |
| 建连 |
SYN 重传、无 Connection established |
路由、ACL、监听、丢包 |
| 认证 |
Authenticating 停顿 |
GSSAPI、反向 DNS、LDAP/AD |
| 会话 |
已认证但命令不返回 |
PAM、NFS、profile、motd |
根因结论必须能被日志、抓包、NSS 查询或命令耗时支持,不能只因“改完好了”倒推原因。
命令检查:DNS 和反向解析
服务端可能在认证阶段对客户端 IP 做反向解析。OpenSSH 的 UseDNS 控制 sshd 是否使用该结果,但默认值会随版本和发行版策略变化,应以实际输出为准:
sudo sshd -T | grep '^usedns '
getent hosts 10.20.30.50
grep -E '^(hosts|passwd|group|shadow):' /etc/nsswitch.conf
示例输出:
hosts: files dns myhostname
passwd: files sss
group: files sss
如果针对问题源 IP 的 getent hosts 重复出现数秒等待,且等待时间与 SSH 认证日志对齐,反向解析是强证据。还要检查 DNS 服务器可达性、反向区域记录与 nsswitch.conf,不要因为一次超时就删掉 sss、ldap 或 winbind;这可能让域用户、组解析和 sudo 授权全部失效。
确认 sshd 的反向解析导致等待后,可以在配置管理系统中实施最小变更。支持 Include 的环境常用独立片段:
# /etc/ssh/sshd_config.d/90-login-latency.conf
UseDNS no
配置内容不是 shell 命令。执行前需确认该目录已被主配置 Include;不支持的旧版本应按现有基线修改主配置。执行路径如下:
sudo install -D -m 0600 /etc/ssh/sshd_config /var/backups/sshd_config.$(date +%F-%H%M%S)
sudo sshd -t
sudo systemctl reload sshd
sudo sshd -T | grep '^usedns '
影响范围是该主机后续新建 SSH 会话的反向 DNS 行为;现有会话通常不受 reload 影响。执行前保持第二条具备 sudo 权限的 SSH 会话或带外控制台可用。备份方式是保存原配置;灰度方法是在一台非关键节点执行;验证方式是从问题网段重复最小测试;回滚方式是恢复本次备份、先 sshd -t 再 reload。若配置由 Ansible、Puppet、Salt 或镜像基线维护,必须在声明式源中修改,否则会被下一轮下发覆盖。
命令检查:TCP 与中间网络
停顿发生在 TCP 建连阶段时,检查监听与策略,但不要把“端口在监听”当成路径正常的证明:
sudo ss -ltnp '( sport = :22 )'
sudo nft list ruleset
sudo firewall-cmd --state
nft、firewall-cmd 是否可用取决于系统;云安全组、硬件防火墙和宿主机规则也可能拦截流量。必要时在服务端针对一个测试客户端短时间抓包:
sudo timeout 30 tcpdump -ni any 'host 10.20.30.50 and tcp port 22' -vv
示例输出:
IP 10.20.30.50.52134 > 10.20.30.40.22: Flags [S], seq 123456789, win 64240
IP 10.20.30.40.22 > 10.20.30.50.52134: Flags [S.], seq 987654321, ack 123456790
IP 10.20.30.50.52134 > 10.20.30.40.22: Flags [.], ack 1, win 502
服务端反复收到 SYN、却看不到最后 ACK,说明回程、客户端侧策略或中间设备需要进一步验证;三次握手完整而 SSH banner 延迟,则继续看应用层。抓包含网络元数据,采集时间应短并遵循组织的数据规范。必要时在客户端同步抓包,不能因为服务端发出了 SYN-ACK 就断言客户端收到。
VPN、隧道或特定网段才慢时可做 MTU 探测:
ping -M do -s 1472 10.20.30.40
1472 加 IPv4 的 28 字节头部为 1500;IPv6 或隧道环境需要重新计算。ICMP 被限制时这个试验没有结论,不能单独定性 MTU 问题。
命令检查:GSSAPI、PAM 与目录服务
GSSAPI/Kerberos 是认证前常见的等待源。先做一次仅用于对照的测试:
ssh -vvv -o GSSAPIAuthentication=no ops@ssh.example.internal true
ssh -G ssh.example.internal | grep -i '^gssapiauthentication '
若关闭后稳定变快,说明 GSSAPI 在链路中,但不等于应该全局关闭。依赖 Kerberos 单点登录的环境关闭后会改变功能,应只在不需要它的主机范围内、经身份平台确认后变更。
密码输入后慢或只有域账号慢时,检查 NSS 与 SSSD:
sudo systemctl status sssd --no-pager
getent passwd 'domain-user'
id 'domain-user'
sudo journalctl -u sssd --since '30 minutes ago' --no-pager
sudo grep -R --line-number --no-messages -E 'pam_(sss|ldap|winbind|mkhomedir|exec|motd)' /etc/pam.d
将 domain-user 替换为获授权的真实测试账号。若 getent passwd 和 id 的耗时与登录一致,并且 SSSD 日志显示上游超时,目录服务调用才可作为根因。不要对生产账号反复测试失败密码,可能触发锁定。更不要直接删除 PAM 行:PAM 常被 SSH、sudo、控制台共同使用,变更前需要控制台验证、配置备份和单节点灰度。
已认证后仍慢:查会话而不是认证
用非交互命令与 TTY 会话对照:
time ssh ops@ssh.example.internal true
time ssh -tt ops@ssh.example.internal 'exit'
第二条会触发交互 shell 相关逻辑。若只在交互模式慢,检查 profile、motd 和 home 目录:
sudo find /etc/profile.d -maxdepth 1 -type f -printf '%f\n' | sort
sudo grep -R --line-number --no-messages -E '(curl|wget|nc |dig |ldapsearch|mount |df )' \
/etc/profile /etc/bash.bashrc /etc/profile.d /home/ops/.profile /home/ops/.bashrc
findmnt -T /home/ops
df -hT /home/ops
上述路径需按真实用户替换,且 /etc/bash.bashrc 并非所有发行版存在。登录脚本中调用网络命令、动态生成欢迎信息、NFS home 目录失联或自动挂载超时,都可能表现为认证成功后没有提示符。grep 命中只是线索,必须阅读脚本、比对变更并在测试账号上验证,不能直接删除。
获授权的复杂问题可对对应的 sshd 子进程短时间跟踪,观察文件和网络系统调用:
sudo strace -ff -tt -T -p <sshd_child_pid> -e trace=file,network
<sshd_child_pid> 替换为本次测试连接的子进程 PID,不要长期附着监听主进程。若 connect()、openat() 或 stat() 在同一测试窗口对某个外部服务、挂载路径或脚本耗时,才构成可操作证据。strace 可能暴露路径和账号信息,也有轻微开销,应限制时长和访问范围。
修复、验证与回滚
修复应针对已证实的环节:DNS 问题修 DNS 记录、递归解析器或路由;目录服务问题修 LDAP/AD/KDC 可达性、证书或缓存策略;TCP 问题修最小范围的 ACL、路由或 MTU;profile 问题修脚本或挂载依赖。不要用全局禁用认证功能来掩盖上游故障。
所有涉及 SSH 配置、PAM、目录服务或防火墙的操作都属于高风险变更。影响范围可能是新 SSH 登录、域用户、自动化任务和管理通道。执行前检查应包含:第二条管理员会话或带外控制台、目标配置备份、sshd -t 语法校验、当前防火墙规则导出和变更窗口。备份以版本库或本地时间戳副本为准;灰度仅限一台非关键主机或一个小主机组;防火墙不具备通用 dry-run,需通过最小规则和限定源网段灰度。
验证不应只验证“管理员能登上去”,还要覆盖原先慢的网段、域名和 IP、必要的跳板路径、密钥认证和原本允许的密码/域认证:
ssh -o BatchMode=yes -o ConnectTimeout=10 ops@ssh.example.internal true
ssh -o PreferredAuthentications=publickey -o PasswordAuthentication=no ops@ssh.example.internal true
sudo journalctl -u sshd --since '10 minutes ago' --no-pager
BatchMode=yes 禁止交互输入,适合密钥自动化验证;需要密码或 MFA 的环境会按设计失败。Prometheus 侧可以观察 TCP 重传、主机网络错误、DNS 请求失败、SSSD 服务状态等指标,具体指标名称以实际 exporter 暴露的指标为准。
出现新账号无法登录、域认证异常或自动化失败时,应停止扩大范围,恢复本次备份或撤销配置管理变更,先执行 sshd -t 再 reload。外部 DNS、目录服务、防火墙变更按各自变更单回滚,不能用 SSH 主机上的命令替代。
复盘
复盘应写清等待阶段、对照样本、日志或抓包证据、最终根因、变更范围、验证账户和回滚耗时。把 ssh -vvv、sshd -T、DNS/NSS 检查、目录服务检查和双会话要求固化到运行手册。这样下一次“SSH 慢”不再是一个模糊感受,而是一条可以被分段观测、最小化修复和安全回滚的链路。
值班排障清单:把测试做成可重复动作
当问题尚未稳定复现时,最容易出现的错误是每位值班同学各跑一套命令,最后无法对齐时间线。建议为 SSH 慢建立一个最小采集模板:客户端记录 date -Is、ssh -vvv ... true、域名/IP 对照和源地址;服务端记录同一分钟的 sshd 日志、sshd -T、监听状态和限定条件的抓包;涉及域用户时额外记录一次 getent passwd 的耗时。所有输出的时区必须统一,最好使用 ISO 8601 时间并明确是否为 UTC。
不要把调试选项永久写入全局 /etc/ssh/ssh_config。例如临时设置 LogLevel DEBUG3、强制认证顺序或禁用某项认证,会改变全部用户的连接行为,并可能在日志中泄露更多连接元数据。测试参数应只放在单次命令上;完成排查后保留证据摘要、删除临时抓包文件或按数据保留规范归档。
对于跳板机链路,建议把“到跳板机”和“从跳板机到目标”分别作为监控与值班项。跳板机 CPU 过高、DNS 解析失败、目录服务慢、连接复用 socket 失效,都可能让最终用户感觉目标主机慢。只有分段测量,才能避免把跳板机问题错误归责给业务服务器。
当某一项修复被确认有效后,应把“有效”的判据写成可复验条件,例如同一源网段连续多次最小命令的阶段日志不再出现原先停顿,同时域账号、密钥账号和跳板路径仍保持可用。这样后续配置升级或身份系统变更时,才能及时发现回归。
最后,账号认证失败、目录服务超时和网络阻塞应使用不同的告警与升级路径。将它们混为“SSH 不可用”会延长定位时间,也会让真正负责的系统缺少可行动的证据。