SSH Agent Forwarding 在 tmux 中失效的问题记录

问题:在本地通过 SSH 登录远程服务器并进入后者的 tmux session 时,经常遇到 GitHub SSH 认证失败问题:

1
2
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.

SSH Agent Forwarding 在 tmux 中失效的问题记录

问题现象

在本地通过 SSH 登录远程服务器时,已经开启了 SSH agent forwarding,例如本地 ~/.ssh/config 中有:

1
2
3
4
5
6
Host <REMOTE_HOST_ALIAS>
HostName <REMOTE_SERVER_IP_OR_DOMAIN>
User <REMOTE_USERNAME>
Port <REMOTE_SSH_PORT>
ForwardAgent yes
IdentityFile ~/.ssh/<LOCAL_PRIVATE_KEY>

登录服务器后,在普通 shell 中测试 GitHub SSH 认证正常:

1
2
3
echo $SSH_AUTH_SOCK
ssh-add -l
ssh -T [email protected]

可以看到类似输出:

1
2
3
/tmp/ssh-XXXX/agent.<PID>
<KEY_BITS> SHA256:<KEY_FINGERPRINT> <LOCAL_KEY_COMMENT>
Hi <GITHUB_USERNAME>! You've successfully authenticated, but GitHub does not provide shell access.

但进入之前长期运行的 tmux session 后,再执行:

1
ssh -T [email protected]

却报错:

1
[email protected]: Permission denied (publickey).

或者在 Git 仓库里执行:

1
git pull

报错:

1
2
[email protected]: Permission denied (publickey).
fatal: Could not read from remote repository.

原因

这是 SSH agent forwarding 和长期存活的 tmux session 共同使用时的常见问题。

本地 SSH agent 转发到服务器后,会在服务器上生成一个临时 socket,例如:

1
/tmp/ssh-XXXX/agent.<PID>

当前 shell 通过环境变量 SSH_AUTH_SOCK 找到这个 socket:

1
echo $SSH_AUTH_SOCK

但是 tmux session 启动后会保存当时的环境变量。之后即使重新 SSH 登录服务器,新的 shell 得到了新的 SSH_AUTH_SOCK,旧 tmux session 也不会自动更新。

因此,旧 tmux 里可能仍然指向过期的 socket,导致 GitHub SSH 认证失败。

这个问题通常出现在以下组合中:

1
2
3
4
SSH agent forwarding
+ 远程 tmux/screen
+ 断线重连或重新 SSH 登录
+ 在旧 tmux session 中执行 git pull / git push

这不是 GitHub key 损坏,也不是 remote 配置错误;本质上是 tmux 中的 SSH_AUTH_SOCK 过期。

短期解决方案

重新 SSH 登录服务器后,在普通 shell 中确认当前有效的 socket:

1
echo $SSH_AUTH_SOCK

假设输出为:

1
/tmp/ssh-XXXX/agent.<PID>

进入 tmux 后手动更新:

1
export SSH_AUTH_SOCK=/tmp/ssh-XXXX/agent.<PID>

然后测试:

1
2
ssh-add -l
ssh -T [email protected]

如果看到:

1
Hi <GITHUB_USERNAME>! You've successfully authenticated, but GitHub does not provide shell access.

说明已经恢复正常。

之后即可执行:

1
2
git pull
git push

也可以在 tmux 外部把当前环境写入 tmux:

1
tmux set-environment -g SSH_AUTH_SOCK "$SSH_AUTH_SOCK"

然后在 tmux 内执行:

1
export SSH_AUTH_SOCK="$(tmux show-environment -g SSH_AUTH_SOCK | cut -d= -f2-)"

长期解决方案

推荐使用一个固定路径的软链接,让 tmux 永远读取固定的 SSH_AUTH_SOCK 路径。

在每次登录服务器后,把当前有效的 agent socket 链接到固定位置:

1
ln -sf "$SSH_AUTH_SOCK" ~/.ssh/ssh_auth_sock

然后在 tmux 中使用固定路径:

1
export SSH_AUTH_SOCK="$HOME/.ssh/ssh_auth_sock"

测试:

1
2
ssh-add -l
ssh -T [email protected]

如果测试成功,就可以正常使用:

1
2
git pull
git push

为了自动化,可以把下面这段加入服务器上的 ~/.bashrc~/.zshrc

1
2
3
4
5
6
# Fix SSH agent forwarding inside long-lived tmux sessions.
if [ -n "$SSH_AUTH_SOCK" ] && [ "$SSH_AUTH_SOCK" != "$HOME/.ssh/ssh_auth_sock" ]; then
ln -sf "$SSH_AUTH_SOCK" "$HOME/.ssh/ssh_auth_sock"
fi

export SSH_AUTH_SOCK="$HOME/.ssh/ssh_auth_sock"

之后流程变为:

1
2
3
4
ssh <REMOTE_HOST_ALIAS>
tmux attach
ssh-add -l
ssh -T [email protected]

如果认证正常,就可以在 tmux 中直接执行 Git 操作。

注意事项

  1. 不要在共用服务器上随意放置自己的 GitHub 私钥,尤其不要放到共享目录中。
  2. 如果已经使用 SSH agent forwarding,通常不需要在服务器上生成或保存 GitHub 私钥。
  3. 不要在 tmux 中随意执行 eval "$(ssh-agent -s)",这会启动服务器本地的新 agent,可能反而绕开本地转发过来的 agent。
  4. 如果 ssh -T [email protected] 显示认证成功,但 git pull 仍然失败,需要检查当前 GitHub 账号是否有对应仓库权限,尤其是组织仓库是否需要 SSO 授权。
1