SSH 公钥登录失败排障:AuthorizedKeysFile 里的一个波浪号

By wwd · 2026-05-29 05:20:40

技术笔记#linux#ssh#tailscale#排障

有时候 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。到这里为止,最常见的几个坑都排除了:

  • 公钥没有放进去:不是
  • 用户名写错:不是
  • .sshauthorized_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 可能解析不了短名,但这和公钥认证失败是两个不同层面的问题。排障时把“网络能不能到”和“认证能不能过”拆开,会清楚很多。

Reactions

Comments

wwd 2026-05-29 05:24:07

Codex app 之前也连不上,原来也是这个 key 的问题。现在我已经连上了服务器上的 Codex CLI,只要服务器上开着 Codex 的 remote-control。

Reactions

Leave a comment