SSH:密钥、配置与真实故障

别再把 SSH 当成一个神秘的登录框,它其实很简单:客户端连上服务器,先核验服务器身份,再证明用户身份。大多数故障信息都会直接告诉你,问题出在哪一步。

🎙️ 发布并录制于: · 更新于 ·

01密钥模型:锁与身份证明

私钥只留在你的电脑上。公钥可以复制到各台服务器。登录时,服务器会让你证明自己持有私钥,但私钥本身不会上传。服务器上的 ~/.ssh/authorized_keys 每多一行,就代表允许一个公钥访问这个账户。

# 生成一对现代密钥;请设置密码短语。
ssh-keygen -t ed25519 -a 100 -C "laptop-2026"

~/.ssh/id_ed25519       私钥——绝不要发给别人
~/.ssh/id_ed25519.pub   公钥——把它安装到服务器上

# 安全地查看公钥:
cat ~/.ssh/id_ed25519.pub
我的建议
不要用邮件发送私钥,不要让整个团队共用一把密钥,也不要为了省事去掉密码短语。每个人、每台设备都应使用单独的密钥。撤销权限时,只需删除对应的一行公钥,不必让全公司一起更换秘密。

02先连接一次,再看详细输出

一个 SSH 目标包含四项信息:用户、主机、端口和身份密钥。默认值会藏起其中三项,平时很方便,用错时却很难察觉。连接失败后,给原命令加一个 -v。按顺序看网络连接、主机密钥检查和身份验证。

ssh [email protected]
ssh -p 2222 [email protected]

# 诊断视图;在 -v 信息不够之前,-vvv 通常只会增加干扰。
ssh -v -p 2222 [email protected]

# 值得留意的输出:
debug1: Connecting to server.example.com [203.0.113.10] port 2222.
debug1: Server host key: ssh-ed25519 SHA256:...
debug1: Offering public key: /home/alice/.ssh/id_ed25519

远程用户名一定要写准确。你电脑上的用户名,不能证明服务器账户也叫这个名字。云主机镜像常用 ubuntuec2-user,也可能要求其他预先创建的账户。

03处理 Permission denied (publickey)

看到这条原始报错,说明网络和 SSH 服务都正常,失败的是身份验证。此时不要再查防火墙。先确认远程用户名,再看客户端提交了哪把密钥。然后强制使用目标密钥,并确认配套公钥装在服务器的那个账户下。

[email protected]: Permission denied (publickey).

# 1. 详细输出中有没有 "Offering public key"?
ssh -v [email protected]

# 2. 强制使用目标身份密钥,并忽略意外出现的其他密钥:
ssh -o IdentitiesOnly=yes -i ~/.ssh/work_ed25519 [email protected]

# 3. 比对指纹,不要比文件名:
ssh-keygen -lf ~/.ssh/work_ed25519.pub
# 在服务器控制台中:
ssh-keygen -lf /home/alice/.ssh/authorized_keys
排查顺序
先查用户名,再查客户端提交的密钥,然后查 authorized_keys 是否缺少对应记录。接着查权限,最后才查服务器策略。不要随手重新生成密钥,那会破坏线索,还可能把一把错密钥变成两把。

04权限:私钥就该只有自己能读

如果其他用户也能读取私钥,OpenSSH 会拒绝使用它。如果错误的人能写入目录或文件,服务器也可能忽略 authorized_keys。先修正所有者,再修正权限模式。文件属于 root 时,单用 chmod 解决不了问题。

WARNING: UNPROTECTED PRIVATE KEY FILE!
Permissions 0644 for 'id_ed25519' are too open.

# 客户端:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

# 服务器端,以目标账户为准执行:
chown -R alice:alice /home/alice/.ssh
chmod 700 /home/alice/.ssh
chmod 600 /home/alice/.ssh/authorized_keys

在 Windows 上,用文件的“安全”设置或 icacls,移除无关用户继承到的访问权限。把 Unix 权限数字照搬进 PowerShell,不是跨平台解决办法。真正的规则只有一条:私钥只能由所有者读取。

05主机身份发生变化

主机密钥用来识别服务器。密钥变化,可能是正常重装、IP 被重新分配,也可能有人正在拦截连接。这条警告无法替你判断,所以必须自行核验。不要一看到它就删除记录后重连。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
Offending ED25519 key in /home/alice/.ssh/known_hosts:12
Host key verification failed.

# 1. 通过可信渠道核验新指纹:
#    云控制台、服务商元数据,或管理员。
# 2. 只删除这个主机的旧记录:
ssh-keygen -R server.example.com
ssh-keygen -R "[server.example.com]:2222"
# 3. 重新连接,并比对屏幕显示的指纹。
安全边界
StrictHostKeyChecking=no 不是解决办法。它只是把碍眼的警告换成悄无声息的冒充。核验只花一分钟;跳过这一步,就等于放弃 SSH 判断凭据究竟交给了哪台机器的能力。

06Connection refused 不等于连接超时

这两类故障都发生在身份验证之前。Connection refused 是目标很快给出的答复:主机能到达,但该端口没有服务接收连接,或者防火墙主动拒绝了连接。连接超时则表示回复被丢弃,或路由不通。不要把两者都含糊地叫作“SSH 挂了”。

ssh: connect to host server.example.com port 22: Connection refused
→ 检查端口、sshd 是否监听,以及服务状态。
ss -ltn | grep ':22'
systemctl status sshd   # 有些系统把服务命名为 ssh

ssh: connect to host server.example.com port 22: Connection timed out
→ 检查 DNS/IP、VPN、路由、安全组和防火墙允许列表。
ssh -v -o ConnectTimeout=8 [email protected]

请从实际使用的客户端网络,测试真正的主机名和端口。在服务器本机测试成功,几乎不能证明入站防火墙没有问题。等 TCP 连接成功后,再回头检查密钥和用户名。

07把固定信息写进 SSH 配置

命令行适合临时试验,长期使用的主机应写进 ~/.ssh/config。给机器起个好记的别名,并固定用户、端口和身份密钥。如果配置继承的结果出乎意料,就用 ssh -G alias 查看最终生效的完整配置。

Host reports
    HostName reports.internal.example.com
    User alice
    Port 2222
    IdentityFile ~/.ssh/work_ed25519
    IdentitiesOnly yes

Host *.internal.example.com
    ServerAliveInterval 30
    ServerAliveCountMax 3

# 现在可以这样用:
ssh reports
scp results.csv reports:/srv/import/
ssh -G reports | less
顺序陷阱
对每个参数来说,通常是最先取得的值生效。具体主机块要放在宽泛的通配符之前。不要不断在文件末尾追加重复设置,还指望最后一项覆盖前面所有内容。

08密钥代理与跳板机

密钥代理保存已经解锁的签名能力,让你不必每次连接都输入密码短语。它不会复制私钥。如果客户端提交了错误的身份,就检查代理载入了哪些密钥。访问私有网络时,请使用跳板机,不要登录堡垒机后把私钥存到那里。

# 查看代理中的身份,并载入本地密钥:
ssh-add -l
ssh-add ~/.ssh/work_ed25519
The agent has no identities.  # 载入一把密钥,或用 -i 指定

# 通过堡垒机访问内部主机:
ssh -J [email protected] [email protected]

# 对应的配置:
Host private-app
    HostName 10.0.4.12
    User app
    ProxyJump [email protected]

默认不要启用代理转发。只要会话还开着,被入侵的远程主机就可能要求你的转发代理代为签名。ProxyJump 可以转发连接,却不会把代理访问权交给堡垒机,因此更适合作为默认选择。

09把端口转发说清楚

本地转发会在你的电脑上打开一个端口,再通过 SSH 把流量送到服务器一侧可访问的目标。-L 8080:db.internal:5432 可以这样读:本机监听 8080,然后从远程一侧连接 db.internal 的 5432 端口。只需要隧道、不需要 shell 时,请加 -N

# 本机 15432 → gateway 能访问的数据库
ssh -N -L 15432:db.internal:5432 gateway
psql -h 127.0.0.1 -p 15432 appdb

# 本机 8080 → 绑定在远程 localhost:3000 的服务
ssh -N -L 8080:127.0.0.1:3000 app-server
# 在浏览器中打开 http://127.0.0.1:8080

# 转发无法建立时立即失败:
ssh -N -o ExitOnForwardFailure=yes -L 8080:127.0.0.1:3000 app-server
安全的默认选择
把转发端口绑定到 loopback,不要绑定到 0.0.0.0。后者可能把内部数据库或管理面板暴露给整个本地网络。隧道只提供私密传输,不会自动提供访问控制。

10SSH 故障检查清单

改动任何东西之前,先判断故障处在哪个阶段。运行一次详细模式,再按下面的顺序检查,通常比胡乱猜二十次更快。

□ DNS 是否解析到了预期 IP?
□ 端口是否正确、可以到达,而且确实在监听?
□ 是拒绝连接,也就是主机可达但端口关闭,还是超时,也就是数据被丢弃或没有路由?
□ 是否核验了服务器主机密钥的指纹?
□ 远程用户名是否完全正确?
□ `ssh -v` 显示正在提交哪把密钥?
□ 那把公钥是否在该用户的 authorized_keys 中?
□ 两端的所有者和权限是否足够严格?
□ 配置展开后,`ssh -G alias` 显示什么?
□ 代理是否持有目标身份密钥?
□ 建立隧道时,究竟由哪台机器解析目标主机?
□ 是否只绑定到 loopback,并使用了 ExitOnForwardFailure?

把各个阶段分开看。先连到端口,再确认服务器身份,然后验证用户身份,最后才打开通道或隧道。只要不再把所有问题都当成一种登录失败,SSH 的报错就很好用。

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.