有时候 SSH 问题看起来像网络问题,最后却是配置里一个很小的字符。今天排查的是:本机通过 Tailscale 连接一台叫 wwdeq 的服务器,指定了 ed25519 私钥,但登录时仍然要求输入密码;如果强制禁用密码登录,则直接失败。
现象
命令大概是这样:
ssh -i ~/.ssh/id_ed25519 wwd@<tailscale-ip>
它能连上,但会继续提示输入密码。这一点容易让人误会:-i 不是“必须用这把钥匙登录”,而是“把这把钥匙拿去试一下”。如果服务器不接受这个公钥,客户端仍然会尝试其它认证方式,比如密码。
于是用更干净的方式验证:
ssh -o PasswordAuthentication=no \
-o IdentitiesOnly=yes \
-i ~/.ssh/id_ed25519 \
wwd@<tailscale-ip>
结果是:
Permission denied (publickey,password)
这说明问题已经不在“能不能输密码进去”,而在“公钥认证没有通过”。
先排除网络和用户名
Tailscale 这边其实是通的:节点在线,SSH 的 22 端口也能连上。SSH debug 日志也显示握手已经完成,客户端确实把指定的 ed25519 公钥提交给了服务器。
然后确认服务器用户名不是本机默认用户,而是 wwd。用正确用户名后仍然失败,问题继续收窄到服务端的公钥读取逻辑。
检查 authorized_keys
在服务器上检查:
ls -ld ~ ~/.ssh ~/.ssh/authorized_keys
ssh-keygen -lf ~/.ssh/authorized_keys
看到权限看起来都正常:
/home/wwd drwxr-x---
/home/wwd/.ssh drwx------
/home/wwd/.ssh/authorized_keys -rw-------
authorized_keys 里也确实有本机公钥对应的 fingerprint。到这里为止,最常见的几个坑都排除了:
- 公钥没有放进去:不是
- 用户名写错:不是
.ssh或authorized_keys权限太松:不是- Tailscale 网络不通:也不是
真正的问题:sshd 没看这个文件
关键命令是看 sshd 对这个用户最终生效的配置:
sudo sshd -T -C user=wwd,host=wwdeq,addr=<client-ip> | grep authorizedkeysfile
输出居然是:
authorizedkeysfile /root/.ssh/authorized_keys /root/.ssh/authorized_keys2
这就破案了。公钥明明放在:
/home/wwd/.ssh/authorized_keys
但 sshd 实际在看:
/root/.ssh/authorized_keys
所以无论 /home/wwd/.ssh/authorized_keys 多么正确,sshd 都不会用它。
继续查配置:
sudo grep -RniE 'AuthorizedKeysFile|Match|Include' /etc/ssh/sshd_config /etc/ssh/sshd_config.d 2>/dev/null
找到这一行:
AuthorizedKeysFile ~/.ssh/authorized_keys ~/.ssh/authorized_keys2
问题就藏在这个 ~。在 sshd_config 里,这里不应该写 shell 风格的 ~。它被解析成了 /root,于是最终配置变成了读取 root 的 authorized_keys。
修复
把配置改成相对用户 home 的写法:
AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
或者显式使用 %h:
AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2
然后检查并重载 SSH:
sudo sshd -t
sudo systemctl reload ssh
如果 reload 没生效,可以重启服务:
sudo systemctl restart ssh
再次确认:
sudo sshd -T -C user=wwd,host=wwdeq,addr=<client-ip> | grep authorizedkeysfile
应该变成:
authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2
最后在客户端测试纯公钥登录:
ssh -o PasswordAuthentication=no \
-o IdentitiesOnly=yes \
-i ~/.ssh/id_ed25519 \
wwd@<tailscale-ip>
成功后就不再要求密码。
这次排障的几个提醒
第一,ssh -i key user@host 后仍然能输入密码,并不代表 key 已经生效。要验证 key 是否真的可用,最好临时关掉密码认证。
第二,客户端 debug 只能告诉你“我提交了哪把 key”,不一定能告诉你服务端为什么拒绝。服务端的 sshd -T 和 journal 日志通常更直接。
第三,AuthorizedKeysFile ~/.ssh/authorized_keys 这种写法很像平时 shell 里没问题的路径,但在 sshd 配置里会带来完全不同的结果。这里应该用 .ssh/authorized_keys 或 %h/.ssh/authorized_keys。
最后,Tailscale 名字解析也要单独看。本机如果没启用 Tailscale DNS,ssh wwdeq 可能解析不了短名,但这和公钥认证失败是两个不同层面的问题。排障时把“网络能不能到”和“认证能不能过”拆开,会清楚很多。
Comments
Codex app 之前也连不上,原来也是这个 key 的问题。现在我已经连上了服务器上的 Codex CLI,只要服务器上开着 Codex 的 remote-control。