SSH 连接故障排查实战:超时、拒绝与 host key 变更

SSH 连接故障排查是每一位运维和开发者都绕不开的功课。无论是本地连不上云服务器,还是跳板机突然拒绝登录,报错信息往往只有寥寥几个单词:Connection timed out、Connection refused、Permission denied (publickey),或者那句让人一愣的 REMOTE HOST IDENTIFICATION HAS CHANGED。本文用一套“四步定位法”,带你从网络层一路查到认证层,10 分钟内定位绝大多数 SSH 登录失败的根因。

一、先分类:你遇到的是哪种“连不上”

SSH 连不上,本质是 TCP 连接或应用层认证在某个环节断了。把现象分好类,排查就成功了一半。常见报错对应的大致层级如下:

报错关键词断裂层级最可能原因
Connection timed out网络层防火墙/安全组拦截、路由不通、IP 错误
Connection refused端口与服务层sshd 未启动、监听端口不对、监听地址受限
Permission denied (publickey)认证层密钥权限错误、authorized_keys 未生效、配置禁用密码
REMOTE HOST IDENTIFICATION HAS CHANGED主机指纹层服务器重装/换 IP、known_hosts 缓存冲突
Write failed: Broken pipe会话保持层空闲超时、NAT 回收、KeepAlive 未开启

二、四步定位法:从网络到认证逐层排查

不要把所有命令一股脑试一遍。按“网络 → 端口 → 服务 → 认证”的顺序,每一层用一个最小命令验证,能定位到哪一层断,就解决了 80% 的问题。第一步永远是先开详细日志:

ssh -v user@203.0.113.10
# 还看不清,就开最大级别
ssh -vvv user@203.0.113.10

-v 会打印出“正在尝试哪个 IP、连哪个端口、用的是哪把密钥、卡在哪一步”,是 SSH 连接故障排查里最有价值的单条命令。日志里出现 Connecting to ... port 22 之前就失败,是网络层;连上端口后被服务器主动断开,多半是服务或认证层。

三、Connection timed out:网络与防火墙层

超时意味着 SYN 包有去无回。先用 ping 确认三层可达,再用 telnet 或 nc 单独测 22 端口,避免被“能 ping 通”误导(很多云主机禁 ICMP):

# 测端口是否通(比 ping 更准)
nc -vz 203.0.113.10 22
# 或
telnet 203.0.113.10 22
# 端口扫描看 22 是否开放
nmap -p 22 203.0.113.10

如果端口测不通,按这个清单逐项核对:云厂商安全组是否放行进站 22;服务器本地防火墙(iptables/ufw/firewalld)是否放行;目标 IP 是否正确(公网 IP vs 内网 IP);以及本地网络是否屏蔽了 22 出口。在容器或内网环境里,常常还需要确认 Docker 网络模式 与端口映射是否正确。

四、Connection refused:端口与服务层

4.1 确认 sshd 是否在跑

端口通但被拒绝,通常是目标机上的 sshd 没监听。登录控制台(VNC/云终端)后检查:

systemctl status sshd
# 没跑就拉起
sudo systemctl start sshd
sudo systemctl enable sshd
# 看 22 端口到底有没有在监听,以及监听的是哪个地址
ss -tlnp | grep ':22'
# 或老命令
netstat -tlnp | grep ':22'

如果 ss 显示监听的是 127.0.0.1:22 而不是 0.0.0.0:22,说明 sshd 只接受本机连接,外部自然 refused。在 /etc/ssh/sshd_config 里把 ListenAddress 改掉或注释掉,再 systemctl restart sshd 即可。

4.2 端口被改过

很多主机为了防扫描会把 SSH 换到高位端口(如 2222)。此时必须显式指定端口,否则默认 22 必然是 refused:

ssh -p 2222 user@203.0.113.10
# 或写进 ~/.ssh/config 一劳永逸(见第八节)

五、Permission denied (publickey):密钥与认证层

能连上端口却登不进去,且服务端禁用了密码登录,就会卡在 publickey。这是 SSH 连接故障排查里最常被忽略的一环——不是密钥不对,而是权限不对。Linux 对 ~/.ssh 及其下文件的权限极其严格,任何“过松”的权限都会让 sshd 直接拒绝:

# 修复客户端密钥与目录权限
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
# 修复服务端 authorized_keys 与 home 目录权限
chmod 700 ~/.home/$USER
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

然后用 ssh-copy-id 把公钥正确送上去(它会自动处理权限),比手动 cat pub >> authorized_keys 更稳妥:

ssh-copy-id -p 22 user@203.0.113.10

服务端也要确认 /etc/ssh/sshd_config 里 PubkeyAuthentication yes、PasswordAuthentication no(若你要密钥登录)、以及 AuthorizedKeysFile .ssh/authorized_keys 路径正确。改完配置务必 sudo sshd -t 做语法检查,再重启,避免把自己锁在门外。

六、REMOTE HOST IDENTIFICATION HAS CHANGED:known_hosts 冲突

这句警告是 SSH 的安全机制在保护你:服务器的主机指纹和你本地 ~/.ssh/known_hosts 里记的不一致。常见于服务器重装系统、更换弹性 IP、或内网 IP 被复用给另一台机器。若你确认是合法变更(例如自己刚重装),清掉旧记录即可:

# 删除指定主机的旧指纹
ssh-keygen -R 203.0.113.10
# 带端口的情况
ssh-keygen -R "[203.0.113.10]:2222
# 然后再连,按提示输入 yes 重新写入
ssh user@203.0.113.10

⚠️ 在清记录之前,请务必通过云控制台等带外通道确认服务器确实是你自己的。中间人攻击也会触发同样的提示,盲目 yes 可能把密钥交给攻击者。

七、Broken pipe / 连接被重置:空闲断开与 KeepAlive

能登录、能操作,但放着不动几分钟就掉线,报错 Write failed: Broken pipe 或 Connection reset by peer。这通常是 NAT/防火墙回收了空闲连接,或服务器主动踢掉空闲会话。靠 KeepAlive 心跳解决:

# 客户端 ~/.ssh/config
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
# 服务端 /etc/ssh/sshd_config(让服务器主动探活)
ClientAliveInterval 60
ClientAliveCountMax 3

ServerAliveInterval 60 表示每 60 秒发一次心跳,连续 3 次无响应才断开,足以撑过大多数 NAT 超时窗口。改完服务端配置记得 sudo systemctl restart sshd。

八、进阶:跳板机、ProxyJump 与多账号 config

生产环境常常需要经跳板机访问内网机器。与其手动两次 ssh,不如用 ProxyJump 一条命令穿透,也顺手解决了“内网 IP 在外网连不上”的问题:

# 命令行直连
ssh -J jump@203.0.113.10 user@10.0.0.20
# 写进 ~/.ssh/config,以后 ssh internal 即可
Host jump
    HostName 203.0.113.10
    User jump
    Port 22
Host internal
    HostName 10.0.0.20
    User deploy
    ProxyJump jump
    IdentityFile ~/.ssh/id_ed25519_internal

多账号、多密钥的场景也建议全部收敛到 ~/.ssh/config,用 Host 分段管理,避免每次手敲一长串参数。在 VS Code 远程开发 里,这份 config 还能被 Remote-SSH 插件直接复用,体验非常顺滑。

九、SSH 连接故障速查表

现象第一步该跑的命令典型修复
timed outnc -vz host 22放安全组/防火墙、核对公网 IP
refusedss -tlnp | grep :22启动 sshd、修正 ListenAddress/端口
Permission deniedssh -v 看用哪把 key修 600/700 权限、ssh-copy-id
host key changedssh-keygen -R host确认合法后清 known_hosts
Broken pipe查空闲时长开 ServerAliveInterval/ClientAlive

十、防御式配置:把“连不上”挡在发生之前

排查是救火,预防才是正道。三条低成本建议能显著降低 SSH 连接故障的发生概率:其一,禁用密码登录、仅用密钥(PasswordAuthentication no),顺带挡掉暴力破解;其二,改到高位端口 + Fail2ban,让扫描器少来烦你;其三,保留一个带外通道(云控制台/VNC),一旦 sshd 配置失误被锁门外,还能救回来。把 ~/.ssh/config 纳入 GitHub Actions 或配置管理,团队换人也不怕丢失登录方式。

SSH 连接故障排查并不神秘,核心就是“分层定位、逐层验证”。把本文的四步法(网络→端口→服务→认证)和速查表存好,下次再遇到连不上,按图索骥,基本都能在十分钟之内恢复远程登录。

上一篇 ShellCheck 实战:用静态分析把 Bash 脚本 Bug 挡在运行前
下一篇 Web 服务器日志分析实战:GoAccess 与命令行
最新评论
Docker 网络模式详解:bridge/host 生产选型 - FSData
Docker 网络模式详解:bridge/host 生产选型 - FSData
8月20日
[…] 注意最后那条:把端口绑定到 127.0.0.1,再由宿主机上的 Nginx 反代进来,比直接 -p 80:80 暴露安全得多,也方便统一做
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
Vue3 + TypeScript 工程化:类型安全的前端架构 - FSData
8月20日
[…] Pinia 的工程化用法,可以参考我们之前的实战文章 《Vue3 组合式 API + Pinia:状态管理从入门到实战》,那里覆盖了 Sto
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
2026 年最值得装的 10 个 VS Code 插件(AI/前端/后端/效率全覆盖) - FSData
8月19日
[…] 状态,点一下就能起停容器,配合我们讲过的 Docker Compose 编排 […]
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
Redis 缓存设计:穿透、击穿、雪崩与最佳实践 - FSData
8月19日
[…] 缓存三大坑的本质都是”缓存失效导致请求穿透到数据库”。记住口诀:穿透用布隆、击穿用锁、雪崩用错峰。配合合理的过期策略
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
GitHub Actions 实战:从零搭建 CI/CD 流水线 - FSData
8月19日
[…] Docker 镜像部署。结合我们之前写的 Docker Compose 一键编排实战,可以在 CI […]