SSH 卡死修复

SSH 卡死修复
狂犬主子问题
最近遇到个挺邪门的事,我在mac上使用 ssh user@192.168.1.22 连接服务器,输入 yes 确认指纹,然后就卡住了。这个问题导致我最近无法执行日常工作,甚至怀疑是单位网络炸了(虽然真的有问题)。
思路
我第一反应是网络问题。ping 了一下,通的。又怀疑服务器挂了,但换了台Windows的机器就连得好好的。那就奇怪了。同一个局域网,其他机器能连就 mac 不能连,总不能是macOS系统的问题吧。倒是也有可能是终端软件的问题,因为我用的是 Ghostty,这个终端会注入 TERM=xterm-ghostty,但我也试过系统的 Terminal.app,还是一样会卡住,所以和终端软件没关系。
于是老老实实上 ssh -v 看日志。输出刷了一大串,最后停在两行:
1 | debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password debug1: Next authentication method: publickey |
然后,没了。正常情况下一两秒内应该出现 Offering public key...,但我这儿永远等不到下一行。卡在“选好了用公钥”和“真正拿出公钥”之间。
我当时猜是 ssh-agent 的问题。因为 SSH 客户端在这一步会去问 ssh-agent 要私钥,如果 agent 没反应,客户端就会一直等。但我不敢确定,就先试了个绕开的办法:
1 | ssh -o IdentityAgent=none -o IdentitiesOnly=yes -i ~/.ssh/id_ed25519 user@192.168.1.22 |
秒连。行,破案了,就是 ssh-agent 的锅。
原因
那这个 ssh-agent 到底是什么呢?
说白了就是个替你拿私钥签名的后台小管家。它是个登录后常驻后台的小进程,专门替你保管私钥。你用 ssh-add 把私钥交给它,之后所有 SSH 连接都找它要签名,省得每次输 passphrase,也不用每个程序自己去读磁盘上的私钥文件。它和 SSH 客户端之间通过一个 Unix socket 通信,路径放在环境变量 SSH_AUTH_SOCK 里。客户端说“我要用某个 key 签个名”,agent 签完把结果还回来。平时你根本感觉不到它存在——直到它挂了。麻烦的地方在于,这是个请求-响应模型,客户端发完请求就等,没有超时重试,也感知不到对面是死是活。agent 一崩,客户端就傻等,表现出来就是“卡住”。
但它为什么炸?我用 launchctl print gui/$(id -u)/com.openssh.ssh-agent 看了一眼,其中:
1 | runs = 14255 last exit code = 255 |
一万四千多次启动,每次都退出码 255。也就是说这玩意儿在后台崩溃了一万四千多次,launchd 一直在拉它,它一直在死,死循环。而我每次 SSH 连接,客户端恰好去问这个正在反复去世的进程要密钥,然后就卡住了。
手动跑了一下 /usr/bin/ssh-agent,错误终于说人话了:
1 | agent_cleanup_stale: stat "/Users/x/.ssh/agent/.": Permission denied unix_listener_tmp: bind "...": Permission denied main: Couldn't prepare agent socket |
~/.ssh/agent 这个目录的权限不对。ssh-agent 启动时要做两件事:进去清理旧的 socket 文件,然后创建一个新的监听 socket。两个操作都被 Permission denied 挡了,它只能退出。至于权限为什么坏了,我猜是之前某次 sudo 操作或者备份恢复的时候被改掉的,因为前两天我确实重置过我mac的 .ssh目录的权限。
修复
修复本身不复杂,就是改所有者、改权限、清掉旧文件、重启服务:
1 | sudo chown -R $(id -u):$(id -g) ~/.ssh/agent chmod 700 ~/.ssh ~/.ssh/agent rm -f ~/.ssh/agent/* launchctl kickstart -k gui/$(id -u)/com.openssh.ssh-agent |
然后 ssh-add -l 能立刻返回了,再连服务器也正常了。
总结
回头想想,这事坑就坑在它不报错。SSH 客户端不知道 ssh-agent 已经死了,它只是发了个请求,然后等一个永远不会来的回复。你看到的现象是“卡住”,但真正的问题在另一个进程里,而且那个进程已经崩溃了一万多次,只是没人告诉你。
所以如果你也遇到 SSH 输完 yes 就卡死、不报错不超时的情况,别急着怀疑网络和服务器。先试试 ssh -o IdentityAgent=none 能不能连,能连的话,八成就是 ssh-agent 在后台偷偷去世了。launchctl print 看一眼 runs 和 last exit code,基本就水落石出了。
折腾完这一通,我决定以后对 ~/.ssh 目录的权限多留个心眼。这玩意儿平时不起眼,坏了是真能让你怀疑人生。




