你有没有遇到过这种情况:SSH 连服务器,输入密码或点回车之后,光标卡在原地好几秒,甚至十几秒才出现命令行?用 ssh -v 能看到卡在某个阶段,但不知道卡在等什么。更诡异的是,有时候换一台客户端、换一个网络就快了。这种"SSH 登录慢"是运维里最经典、也最容易被忽略的体验问题之一。本文把常见的几类原因和排查方法讲清楚。
先定位:慢在哪一步
排查任何问题,第一步都是找到现象发生的准确位置。SSH 登录分几个阶段,用 -v(或 -vvv 看更详细)能看到每一步的时间:
ssh -vvv user@server.example.com
观察输出,卡顿通常出现在以下几个位置之一:
- 卡在
Connecting to ...:TCP 层或网络层面慢,可能是路由、防火墙、或目标端口响应慢。 - 卡在
SSH2_MSG_KEX_...之后、认证之前:密钥交换或认证协商慢,常见于反向 DNS 解析、GSSAPI 协商。 - 卡在输入密码后:认证环节慢,比如
pam相关模块在做超时操作。 - 登录成功后、提示符出现前卡顿:登录 shell 的启动脚本(
.bash_profile、.bashrc)里有网络操作或耗时命令。
明确卡在哪一步,才能对症下药。
头号元凶:服务端反向 DNS 解析(UseDNS)
最常见的"登录等好几秒"就是它。SSH 服务端在客户端连接时会尝试对来源 IP 做反向 DNS 查询(PTR),拿到主机名后再做一次正向查询验证(FCrDNS)。如果这台服务器的 /etc/resolv.conf 指向的 DNS 服务器响应慢、或者根本不可达,每次连接都要等 DNS 超时,于是登录就慢。
先验证:在服务端看 /etc/resolv.conf,然后手动测反向解析是否慢:
time getent hosts <你的客户端IP> # 或 time dig -x <你的客户端IP>
如果这条命令要好几秒,基本确定是它。解决办法是在 /etc/ssh/sshd_config 里关闭反向解析:
# /etc/ssh/sshd_config UseDNS no
然后重载:sudo systemctl reload sshd,再登录,通常瞬间就快了。注意:UseDNS 默认值在不同发行版和 OpenSSH 版本里不一样,有的默认 yes,有的在编译时决定。显式写 UseDNS no 最保险。关掉它不影响安全性——它主要用于 hosts.allow/deny 里基于主机名的访问控制,而依赖 IP 的规则不受影响。
第二号元凶:GSSAPI 认证协商(GSSAPIAuthentication)
如果卡在认证协商阶段,而且客户端和服务端都没有用 Kerberos,那很可能是 GSSAPIAuthentication 在作怪。服务端会尝试 GSSAPI/Kerberos 认证,客户端也会去联系本地的 GSSAPI 机制,每次都超时才回退到普通认证,白白浪费几秒。
服务端处理:
# /etc/ssh/sshd_config GSSAPIAuthentication no
客户端如果也慢,还可以在客户端的 ~/.ssh/config 里加:
Host *
GSSAPIAuthentication no
GSSAPIDelegateCredentials no重载 sshd 和重启客户端后,认证阶段通常会明显加快。判断依据是:ssh -vvv 输出里如果反复出现 gssapi-with-mic 或类似行再超时,那就是它。
客户端侧:正向/反向 DNS 与 IPv6 解析
有时候慢不在服务端,而在客户端。客户端也可能对目标主机名做反向解析,或者先尝试 IPv6 连接、等超时才回退到 IPv4。典型表现是:用 IP 连很快,用域名连很慢;或者在某些网络下慢,换个网络就正常。
排查方法:
# 看域名解析耗时 time dig server.example.com # 强制用 IPv4 连,看是否变快 ssh -4 user@server.example.com # 强制用 IPv6 连 ssh -6 user@server.example.com
如果 -4 明显快,说明是 IPv6 路径有问题(比如服务器没有可用的 IPv6,但 DNS 里既有 AAAA 又有 A 记录,客户端先试 IPv6 卡住)。可以在客户端 ~/.ssh/config 里针对该主机强制 IPv4:
Host server.example.com
AddressFamily inet登录后才卡:shell 启动脚本里的坑
如果你已经过了认证、但提示符出现前还要等好几秒,那问题在登录 shell 的初始化脚本里。常见嫌疑:
/etc/profile或~/.bashrc里执行了网络请求,比如curl检查更新、hostname反查、whois、任何带超时的命令。- 启动了交互式检查工具,比如某些发行版会跑
motd的更新脚本(/etc/update-motd.d/),而其中某个脚本在做网络操作。 - conda、nvm、sdkman 等工具在 shell 启动时做初始化,或去联网检查版本。
排查方法:让 SSH 登录时打印每个脚本的耗时。在服务端临时加:
# 在 /etc/bash.bashrc 顶部加 set -x
或者更精确地,用 bash -x -l -c exit 模拟登录并看它卡在哪。定位到具体脚本后,把网络操作改为异步、加超时、或者用 if [ -n "$PS1" ] 之类的判断确保只在交互式登录时才执行非必要逻辑。
其他可能:MTU、防火墙、负载
如果以上都不是,还有几种情况:
- MTU 不匹配:如果网络链路上 MTU 有问题,SSH 握手时的大包会被丢弃,表现为连接建立慢或卡顿。用
ping -M do -s 1472测试路径 MTU,或在客户端连不上时试ssh -o IPQoS=throughput。根治要在网络层修 MTU/MSS。 - 防火墙做了连接跟踪或限速:某些防火墙对新建连接做速率限制或需要等待状态表,导致 SSH 首包慢。检查
iptables -L -n -v、nft list ruleset。 - 服务器负载过高:CPU 满载或内存吃紧时,sshd 派生进程也可能慢。登录后
uptime、top一看便知。这种情况根因不在此,要去解决负载问题。 - PAM 模块超时:少数环境里 PAM 的某些模块(如依赖 LDAP、或
pam_systemd在容器里)会超时。用/etc/pam.d/sshd逐项排查,临时注释非必要模块验证。
用时间戳精确量化每一阶段
ssh -vvv 能告诉你卡在哪一步,但它不带时间。想看"到底每步花了几秒",可以用 ts(moreutils 提供的给每行加时间戳的工具)来给 ssh 的调试输出打上时间:
sudo apt install moreutils -y ssh -vvv user@server 2>&1 | ts '%H:%M:%.S'
这样输出里每一行前面都有精确到毫秒的时间,哪一步停顿了多少一目了然。比如你会看到 debug1: SSH2_MSG_SERVICE_ACCEPT received 和下一行之间隔了 5 秒,那就锁定在这段。这个技巧比单纯 -vvv 高明得多,因为"卡顿"和"几行日志之间"是两回事,有了时间戳才能把二者对应起来。
另一个更"土"但有效的办法:用 date +%s.%N 在客户端脚本里记录开始和结束时间,做多次连接的统计对比:
for i in 1 2 3 4 5; do s=$(date +%s.%N) ssh -o BatchMode=yes user@server true e=$(date +%s.%N) echo "第 $i 次: $(echo "$e - $s" | bc) 秒" done
多次测量能排除偶发波动,确认问题是"每次必慢"还是"时快时慢",这对判断根因很重要。
一个真实案例:从 8 秒到 0.3 秒
说一个很典型的场景。有台服务器,用 IP 连是快的,用域名连就要等七八秒。用 ssh -vvv IP 没用,因为快;用 ssh -vvv 域名 才慢。加了时间戳后发现,卡在 debug1: SSH2_MSG_SERVICE_REQUEST sent 之后、SSH2_MSG_SERVICE_ACCEPT received 之前。
这个位置正是认证服务协商(GSSAPI/Kerberos)的阶段。服务端 GSSAPIAuthentication 开着,但没有配置 Kerberos,于是每次都要等 GSSAPI 超时才回退。ssh -vvv 里还能看到 debug1: Authentications that can continue: gssapi-with-mic,password,publickey——gssapi-with-mic 排在最前,就是它在拖时间。
两处修改:服务端 /etc/ssh/sshd_config 加 GSSAPIAuthentication no,客户端 ~/.ssh/config 加 GSSAPIAuthentication no,重载 sshd。再测,登录从 8 秒降到 0.3 秒。问题根源不是网络、不是服务器性能,而是一个默认开启、却在当前环境里纯浪费时间的认证选项。
预防:把好的配置固化下来
排查完、优化完,要做的最后一件事是固化配置,避免换台机器又踩。服务端 sshd_config 里明确写上这几行:
UseDNS no GSSAPIAuthentication no # 明确认证方式,减少协商往复 PubkeyAuthentication yes PasswordAuthentication no PermitRootLogin no
客户端 ~/.ssh/config 里也加上针对性的设置,尤其如果你经常连一批机器:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
GSSAPIAuthentication no
Compression yes
Host slow-server
AddressFamily inetServerAliveInterval 还能顺手解决另一个常见困扰:网络空闲时 SSH 连接被防火墙悄悄掐断。它定期发送心跳包保活,配合 ServerAliveCountMax 判断真正断线。
和其他"慢"问题的区别
最后提醒一点,别把"SSH 登录慢"和"命令执行慢"混为一谈。前者是连接建立、认证阶段的问题;后者是连上之后每条命令都要等——那通常是 shell 启动脚本、命令补全、或 PROMPT_COMMAND 里的网络操作。还有一种是"首次连接快、隔一会儿再连又慢",那要重点查 DNS 缓存和防火墙连接跟踪表的老化时间。
学会区分现象、用 -vvv 加时间戳定位、再对症修改配置,SSH 登录慢几乎总是可以在十分钟内解决的。这既是具体的排障技能,也是一种通用的运维思维:先测量,再优化,别凭感觉改配置。
一份可复制的排查清单
ssh -vvv定位卡在哪一阶段。- 卡在连接建立 → 测
ping/tcptraceroute,查路由与防火墙。 - 卡在认证协商 → 服务端关
UseDNS no+GSSAPIAuthentication no,重载 sshd。 - 客户端慢 → 测
dig耗时、试ssh -4、检查~/.ssh/config。 - 登录后才卡 → 用
bash -x -l或set -x找 shell 启动脚本里的网络操作。 - 都不是 → 查 MTU、防火墙、系统负载、PAM。
绝大多数情况下,UseDNS no 和 GSSAPIAuthentication no 这两条就能解决 90% 的"SSH 登录慢"。这也提醒我们:很多性能问题不是资源不够,而是某个默认开启的、会做网络查询的选项在拖后腿。养成用 -vvv 定位、用 time 量化的习惯,排查会非常高效。