现象 链接到标题
打开新的终端窗口,执行 ssh <内网服务器> 时反复提示:
Enter passphrase for key '~/.ssh/id_rsa':
明明之前在钥匙串中保存了密码,不该每次都输。
排查 链接到标题
SSH_AUTH_SOCK 指向僵尸 socket 链接到标题
$ ssh-add -l
Error connecting to agent: Connection refused
$ echo $SSH_AUTH_SOCK
/var/folders/.../agent.45029 ← 指向一个已经死掉的 agent 进程
SSH_AUTH_SOCK 环境变量保存着一个已经不存在的 agent 进程的 socket 路径。
多个 ssh-agent 进程同时运行 链接到标题
$ ps aux | grep ssh-agent
marshal 45030 ... ssh-agent -s
marshal 45031 ... ssh-agent -s
marshal 45312 ... /usr/bin/ssh-agent -l
一个用户账号下同时跑了 3 个 ssh-agent,不正常。
根因:zshrc 中的 pkill 逻辑 链接到标题
检查 ~/.zshrc_custom.sh:
if [ ! -S "$SSH_AUTH_SOCK" ] || ! ssh-add -l >/dev/null 2>&1; then
unset SSH_AUTH_SOCK SSH_AGENT_PID
pkill -9 -f ssh-agent # ← 杀掉所有 ssh-agent 进程
sleep 0.5
eval "$(ssh-agent -s)" # ← 新建一个 agent
ssh-add --apple-load-keychain # ← 从钥匙串加载 key
fi
这段逻辑在每个新终端启动时执行。问题在于:
pkill -9 -f ssh-agent无差别杀掉所有 agent,包括其他终端正在使用的- 多个终端打开时互相残杀对方创建的 agent
- agent 被杀后,socket 文件变成僵尸,
SSH_AUTH_SOCK指向无效路径 - 新创建的 agent 如果钥匙串处于锁定状态,
--apple-load-keychain静默失败(stderr 被重定向到/dev/null) - 最终
ssh-add -l失败,SSH 降级为交互式输入 passphrase
macOS 的 SSH agent 架构 链接到标题
macOS 自带由 launchd 管理的系统 SSH agent:
$ launchctl list com.openssh.ssh-agent
{
"Label" = "com.openssh.ssh-agent";
"Program" = "/usr/bin/ssh-agent";
"ProgramArguments" = ("/usr/bin/ssh-agent", "-l");
"PID" = 7750;
...
}
特点:
- 进程名:
/usr/bin/ssh-agent -l(注意-l参数) - socket 路径:
/private/tmp/com.apple.launchd.xxx/Listeners - 跨登录 session 持久化,重启电脑后自动恢复
- 支持
ssh-add --apple-load-keychain从钥匙串加载 key
不需要手动 eval "$(ssh-agent -s)" 启动 agent。
解决方案 链接到标题
去掉 pkill,不再杀进程,优先使用系统 agent。
# ~/.zshrc_custom.sh
# SSH agent — 优先用 macOS launchd 系统 agent,跨终端持久化,避免多个 agent 互相残杀
if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
export SSH_AUTH_SOCK=$(launchctl getenv SSH_AUTH_SOCK 2>/dev/null)
fi
if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
export SSH_AUTH_SOCK=$(lsof -U 2>/dev/null | awk '/ssh-agent/ && /Listeners/ {print $NF; exit}' 2>/dev/null)
fi
if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
rm -f "$SSH_AUTH_SOCK"
ssh-agent -a "$SSH_AUTH_SOCK" -s >/dev/null 2>&1
fi
ssh-add -l >/dev/null 2>&1 || ssh-add --apple-load-keychain
三层 fallback 逻辑:
flowchart TD
A["新终端启动"] --> B{"SSH_AUTH_SOCK
已设置且有效?"} B -->|是| C["ssh-add -l"] B -->|否| D["launchctl getenv
获取 launchd socket"] D --> E{"获取到有效
socket?"} E -->|是| C E -->|否| F["lsof -U
扫描 ssh-agent Listeners"] F --> G{"找到
agent?"} G -->|是| C G -->|否| H["自建 ~/.ssh/agent.sock"] H --> C C -->|key 已加载| I["完成"] C -->|key 未加载| J["ssh-add --apple-load-keychain"] J --> I
已设置且有效?"} B -->|是| C["ssh-add -l"] B -->|否| D["launchctl getenv
获取 launchd socket"] D --> E{"获取到有效
socket?"} E -->|是| C E -->|否| F["lsof -U
扫描 ssh-agent Listeners"] F --> G{"找到
agent?"} G -->|是| C G -->|否| H["自建 ~/.ssh/agent.sock"] H --> C C -->|key 已加载| I["完成"] C -->|key 未加载| J["ssh-add --apple-load-keychain"] J --> I
关键改动:
- 去掉
pkill,不再互相残杀 - 优先用 launchd 系统 agent,所有终端共享同一个 socket
ssh-add --apple-load-keychain只在需要时执行一次
验证 链接到标题
# 1. 清理僵尸 socket
$ find /private/var/folders/... -name "ssh-*" -type d -exec rm -rf {} +
# 2. 新终端启动后
$ ssh-add -l
4096 SHA256:... ~/.ssh/id_rsa (RSA) ← key 已加载
# 3. SSH 测试
$ ssh -o BatchMode=yes <内网服务器> echo ok
ok
多个终端同时打开不冲突,重启后 agent 由 launchd 自动恢复。
总结 链接到标题
macOS 下不需要手动管理 ssh-agent 进程。利用系统自带的 launchd agent,配合 ssh-add --apple-load-keychain,即可实现跨终端持久化、免重复输入 passphrase。
核心原则:不要杀 agent,不要新建 agent,用系统的。