技术指南
SSH 连不上:按协议层定位并安全恢复
从 DNS、TCP、主机指纹到用户认证逐层定位 SSH 失败,并用带外入口安全恢复访问。
SSH 故障排查的关键不是反复重试,而是确认连接最后成功走到了哪一层。名称解析、TCP 连接、SSH 主机身份和用户认证是四个不同关口;只有保留一次完整诊断输出,才能避免把网络问题误修成密码问题,或在主机指纹变化时跳过安全警告。
先固定客户端真正使用的目标
别只看输入的别名。OpenSSH 会合并命令行、`~/.ssh/config`、系统配置、`Host` 和 `Match` 规则。`ssh -G` 会打印计算后的客户端配置;先记录真实主机名、地址族、端口、用户、代理跳板和身份文件,再做网络测试。
[email protected]
ssh -G "$TARGET" | grep -E \
'^(hostname|user|port|addressfamily|proxyjump|proxycommand|identityfile|identitiesonly|connecttimeout) '
getent ahosts example.com若 DNS 同时返回 IPv4 和 IPv6,可以分别用 `ssh -4` 与 `ssh -6` 验证。某一个地址族失败不等于服务器整体离线;也不要因为 `ping` 失败就下结论,ICMP 与 SSH 的 TCP 路径和访问策略可以不同。
用一次详细连接标记最后成功层
ssh -vvv \
-o ConnectTimeout=8 \
-o ConnectionAttempts=1 \
-o BatchMode=yes \
-p 22 [email protected] true 2>&1 | tee ssh-diagnostic.log| 最后看到的现象 | 已经证明 | 下一步 |
|---|---|---|
| Could not resolve hostname | 客户端未得到可用目标地址 | 检查别名、DNS、搜索域和客户端配置 |
| No route / timed out | 尚未完成 TCP 或 SSH 初始握手 | 检查地址族、路由、两层防火墙和外部探测 |
| Connection refused | 目标地址返回了拒绝 | 核对端口、监听进程、socket activation 和映射 |
| REMOTE HOST IDENTIFICATION HAS CHANGED | 收到了与本地记录不同的主机密钥 | 停止认证并通过独立渠道核对指纹 |
| Permission denied | TCP、SSH 握手和主机身份阶段已通过 | 检查用户、所用密钥、服务端策略与日志 |
| 认证后立即断开 | 凭据可能已被接受 | 检查账号 shell、PAM、ForceCommand、资源和会话日志 |
认证之前先证明 TCP 与 SSH 握手
nc -vz -w 5 example.com 22
ssh -vvv -o ConnectTimeout=8 -o BatchMode=yes \
-p 22 [email protected] true若所有外部网络都超时而控制台可用,依次核对实例公网地址、云侧入站规则、主机路由和主机防火墙。只有一个办公网失败时,优先调查该网络的出口 ACL、代理、运营商路径或 IPv6。`nc` 成功只说明 TCP 端口可连接,不证明对端身份,也不证明认证会成功。
从控制台核对监听者与实际配置
Ubuntu 可能同时存在 `ssh.socket` 和按需启动的 `ssh.service`;其他发行版常用 `sshd.service`。不要机械地重启一个猜测的单元。先看监听端口及拥有它的进程,再看 unit 关系、配置语法和包含 snippets、`Match` 条件后的有效值。
sudo ss -lntp | grep -E ':(22|2222)\b'
systemctl status ssh.service ssh.socket sshd.service --no-pager
sudo sshd -t
sudo sshd -T -C user=admin,host=example.com,addr=198.51.100.24 | \
grep -E '^(port|listenaddress|permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|authorizedkeysfile|strictmodes) '主机密钥变化必须先核对指纹
OpenSSH 在已知主机密钥变化时阻止继续认证,用于防止服务器欺骗和中间人攻击。正常重装、IP 复用或主机密钥轮换也会触发同样警告,所以警告本身不能证明攻击,也不能直接忽略。通过云控制台或另一个受信管理通道读取服务器公钥指纹,并与客户端警告逐字符比较。
# 在可信控制台上读取当前服务器指纹
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
# 仅在指纹和变更原因都确认后,在客户端删除精确目标
ssh-keygen -R '[example.com]:2222'
ssh -o StrictHostKeyChecking=ask -p 2222 [email protected]`ssh-keyscan` 可以收集对端当前提供的公钥,但它不会认证这些公钥。不要把同一条可能已被劫持的网络路径同时当作告警来源和可信指纹来源。
Permission denied 要对齐客户端与服务端
`Permission denied` 表示连接已经进入认证阶段。先用 `IdentitiesOnly=yes` 限制客户端只提交指定密钥,避免代理中大量密钥导致 `MaxAuthTries` 提前耗尽;同时在控制台跟随实际 SSH unit 的日志,确认服务器看到的用户、密钥指纹和拒绝原因。
ssh -vvv \
-o IdentitiesOnly=yes \
-i ~/.ssh/id_ed25519 \
-p 22 [email protected]
# Debian / Ubuntu 常见;按实际 unit 名调整
sudo journalctl -fu ssh.service- 确认目标用户名存在、未锁定、登录 shell 有效,并且 `AllowUsers` / `AllowGroups` / `DenyUsers` / `Match` 没有排除它。
- 用 `ssh-keygen -lf ~/.ssh/id_ed25519.pub` 记录客户端公钥指纹,与服务端 `authorized_keys` 中的目标行核对。
- 读取 `sshd -T -C ...` 的 `PubkeyAuthentication`、`AuthorizedKeysFile`、`PasswordAuthentication` 和 `PermitRootLogin`,不要只搜索主配置文件。
- 检查 home、`.ssh` 和 `authorized_keys` 的所有者及可写权限;`StrictModes` 默认会检查用户文件和 home 的 ownership/mode。
- 若认证成功后断开,继续检查 shell、PAM、磁盘/进程限制、`ForceCommand` 和会话日志,而不是重复换密钥。
namei -l /home/admin/.ssh/authorized_keys
sudo -u admin ssh-keygen -lf /home/admin/.ssh/authorized_keys
sudo chmod go-w /home/admin
sudo chmod 700 /home/admin/.ssh
sudo chmod 600 /home/admin/.ssh/authorized_keys
sudo chown admin:admin /home/admin/.ssh /home/admin/.ssh/authorized_keys修改端口或策略时设置验收门
Ubuntu 官方文档建议把自定义放进 `/etc/ssh/sshd_config.d/*.conf`,并在重启前执行 `sshd -t`。但包含顺序、first-set 语义、socket activation 和服务名会随发行版改变;必须以当前主机文档、`systemctl cat` 和有效配置输出为准。
- 确认带外控制台可用,保留当前已认证会话并记录回滚命令。
- 若改端口,先在云防火墙与主机防火墙增加只对管理来源开放的新端口,不先删除旧规则。
- 写入单一、可审计的配置变更,运行 `sshd -t` 并用 `sshd -T -C ...` 验证目标用户。
- 按实际 service/socket 模型重载或重启,从第二个终端验证新端口、主机指纹、密钥登录和 sudo。
- 查看日志和监听状态;验收全部通过后,才移除旧端口和旧规则。
完全失联时按可逆性恢复
- 从服务商控制台确认实例运行状态、当前公网地址和启动日志。
- 通过串行控制台、VNC 或 Web 控制台检查监听、journal、磁盘空间和最近配置变更。
- 进入救援系统后以只读方式检查系统盘,再挂载并修复配置、密钥 ownership 或防火墙;保留修改记录。
- 若最近变更有已验证快照,先评估数据回退范围再回滚。
- 重装只作为最后手段;先确认数据盘、独立备份、主机密钥变化和 DNS/IP 后续影响。
用隔离高端口演练错误边界
VPScope 在回环地址的 22222 端口启动独立 OpenSSH 9.6p1 daemon,使用临时 host/client keys 和禁用密码的专用配置。未启动时客户端得到 `Connection refused`;启动后错误密钥完成 TCP 与主机密钥握手,再得到 `Permission denied (publickey)`;正确密钥执行了合成命令。
随后轮换临时 host key,已记录旧指纹的客户端以状态 255 阻止连接。只有模拟从可信控制台读取新 SHA256 指纹、删除精确 `[host]:port` 记录并重新确认后,连接才恢复。测试未修改端口 22、系统 SSH unit、生产配置或防火墙,结束后 daemon、端口和全部临时文件均已清理。
常见修复动作的风险
| 动作 | 为什么危险 | 更小的修复 |
|---|---|---|
| 删除整个 known_hosts | 丢失所有已建立的主机身份记录 | 核对指纹后只删除精确 host:port |
| 临时开放 SSH 到全网 | 扩大暴力尝试和凭据攻击面 | 只对受控管理来源开放并设置回收时间 |
| 递归 chown 整个 home | 可能破坏应用、部署和共享文件所有权 | 只修复 home、.ssh 与目标 key file |
| 直接重启 sshd 试错 | 语法或监听错误可能切断唯一入口 | 先 sshd -t、有效配置检查和第二会话验收 |
| 启用 root 密码登录 | 把恢复问题变成高权限口令暴露 | 恢复指定普通管理用户的密钥路径 |
关闭事件前留下可复查记录
- 记录 UTC 时间、客户端网络、目标解析结果、有效 user/host/port 和最后成功协议层。
- 保存脱敏后的 `ssh -vvv` 关键行、监听进程、有效 sshd 配置和对应 journal 时间窗。
- 主机密钥变化有独立渠道指纹和变更原因;没有通过跳过检查恢复。
- 认证恢复后验证新会话、sudo、第二管理入口和业务健康,而不是只看端口开放。
- 撤销临时防火墙规则、测试密钥与宽松策略,并记录永久修复和下一次演练日期。
- 检查发行版 OpenSSH 安全更新;本次测试安装版本低于当前 Ubuntu 仓库候选补丁。