你给 SSH 密钥设了密码短语,现在 Windows 在每次 git push 和每次 ssh 时都要你输入它。解决办法就是 SSH 代理——解锁一次密钥,之后每次连接都会静默签名。Windows 上的曲折在于:并不是只有一个代理。有三个代理可能同时在运行,它们把密钥存在各自不同的地方,加载进其中一个里的密钥,对其他几个是不可见的。几乎所有“昨天还好好的”代理问题,在 Windows 上其实都是用错了代理。所以本指南先给它们起个名字,再逐个把它们跑起来,最后解读你真正会遇到的报错。
三个代理,以及它们为何不共享密钥
| 代理 | 随附于 | 监听位置 | 哪个 ssh 能看到它 |
|---|---|---|---|
| OpenSSH Authentication Agent(一个 Windows 服务) | Windows 原生的 OpenSSH | 命名管道 \\.\pipe\openssh-ssh-agent | C:\Windows\System32\OpenSSH 里的 ssh.exe |
MSYS2 代理(ssh-agent) | Git for Windows / Git Bash | 由 SSH_AUTH_SOCK 指定的 Unix 式套接字 | Git Bash 内置的 ssh |
| Linux 代理 | 你的 WSL 发行版内部 | 该发行版里的一个 Unix 套接字 | 在 WSL 内运行的 ssh |
表格给出的教训:在 Git Bash 里运行 ssh-add 会把密钥加载进 MSYS2 代理,而 Windows 原生的 ssh.exe 永远看不到它——不同的端点,不同的密钥库。选定一个代理,把密钥加载到那里,再用与之匹配的 ssh。把它们混着用,是下面大多数困惑的根源。ssh-agent 与 ssh-add 指南(英文)讲的是跨平台的机制;本页是 Windows 专属的那一半。
内置的 OpenSSH Authentication Agent 服务
现代的 Windows 10 和 11 都自带 OpenSSH,随之而来的是一个运行该代理的 Windows 服务——但它默认是关闭的。只需在管理员权限的 PowerShell(以管理员身份运行)里启用一次:
Set-Service ssh-agent -StartupType Automatic
Start-Service ssh-agent
Automatic 会在每次开机时启动它,所以这是一次性设置。检查结果:
Get-Service ssh-agent | Select-Object Status, StartType
你要看到的是 Running 和 Automatic。此后,在普通(非 Git Bash)的 PowerShell 或 cmd 里,ssh-add 就会和这个服务通信:
ssh-add # load default keys (id_ed25519, id_rsa, …)
ssh-add $env:USERPROFILE\.ssh\id_ed25519 # load a specific key
ssh-add -l # list loaded key fingerprints
ssh-add -l 是任何登录失败的首要诊断手段——如果你期望的密钥没有列出来,那么无论别的看起来多正确,代理都无法用它来签名。
为了不用反复敲 ssh-add,让第一次连接替你加载密钥。把下面这段放进 %USERPROFILE%\.ssh\config(就是 OpenSSH 到处都在读的那个 ssh_config 文件(英文)):
Host *
AddKeysToAgent yes
现在,第一个需要密码短语的 ssh 会提示一次,并顺带把密钥加载进服务。由于原生的 Windows git 和 ssh 都用这个服务,git push 也会开始静默工作。
错误 1058:“unable to start the ssh-agent service”
你运行 Start-Service ssh-agent,得到:
Start-Service : Service 'OpenSSH Authentication Agent (ssh-agent)' cannot be started due to the following error:
Cannot start service ssh-agent on computer '.'.
... error 1058 ...
错误 1058 意味着该服务被禁用了——Windows 不会启动一个启动类型为 Disabled 的服务,而 Start-Service 不会替你改这个设置。先修好启动类型,再启动它(管理员 PowerShell):
Set-Service ssh-agent -StartupType Manual
Start-Service ssh-agent
Manual 足以让它按需启动;如果你想每次开机都起来,就用 Automatic。如果你用的是受管或域内机器,某个组策略可能已经禁用了它——那样的话,得由管理员先允许该服务,这些设置才能生效。
“error connecting to agent: No such file or directory”
这一条来自 Git Bash / MSYS2 的 ssh,不是那个 Windows 服务。它表示那个 ssh 被告知了去哪里找代理套接字——通过 SSH_AUTH_SOCK——但那里什么都没有。两个常见原因:
-
**这个 shell 里没有代理在运行。**Git Bash 不会替你启动一个。在你正用的 shell 里启动它:
eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519eval很重要:ssh-agent -s只会打印变量;eval才会设置它们,好让ssh能找到套接字。光运行ssh-agent、然后纳闷为什么什么都没变,是经典的误操作。 -
**
SSH_AUTH_SOCK过期了。**由上一个会话、某个登录配置文件或别的工具设置的变量,指向了一个已不存在的套接字。用echo $SSH_AUTH_SOCK检查它,然后要么取消设置它并启动一个全新的代理,要么把它修正为指向一个有效的代理。
如果你更想在 Git Bash 里面使用那个 Windows 服务,就直接调用原生二进制文件——/c/Windows/System32/OpenSSH/ssh.exe——而不是你 PATH 上的 MSYS2 ssh。这样 Git Bash 就能访问到你加载进该服务的同一批密钥,SSH_AUTH_SOCK 也不再重要。
WSL 里的密钥
WSL 是一个独立的 Linux 系统,所以它有自己的代理、自己的 ~/.ssh 和自己的 SSH_AUTH_SOCK。在 Windows 一侧加载的密钥,在你的发行版内部看不到,反之亦然。你有两个诚实的选择:
- 在 WSL 内部运行一个代理,用普通的 Linux 方式——
eval "$(ssh-agent -s)"加ssh-add,或者在发行版的~/.ssh/config里写AddKeysToAgent yes。简单、完全隔离,ssh-add 指南(英文)里有讲。 - 桥接到 Windows 代理,让两个世界共用一个密钥库。常见做法是用
npiperelay.exe配合socat,把 Windows 命名管道接到一个 Unix 套接字上,再让 WSL 的SSH_AUTH_SOCK指向它。配好之后用起来很顺,但多了不少活动部件——只有当你确实需要同一把密钥在两边都能用时,才去碰它。
无论选哪种,记住 WSL 里的 ssh-add -l 和 PowerShell 里的 ssh-add -l 是两个不同的问题。调试时问对那一个。
代理没问题,但登录仍然失败时
如果 ssh-add -l 显示了你的密钥,服务器却仍然说 Permission denied (publickey),那么代理已经完成了它的工作——问题转移到了下游:服务器的 authorized_keys、用错了用户,或者提供了错误的密钥。那是另一份清单:Permission denied (publickey),按顺序修复(英文)。而在你用 ssh -A 把代理转发到远程机器之前,先读读为什么代理转发既方便又危险(英文)——它会把你密钥的实时使用权暴露在对端。
Termalin 在 Windows 上如何处理这件事
如果要操心服务、套接字和三个密钥库,超出了你想思考的范围,那正是 Termalin 内建好的工作流。它在一个 Windows 命名管道 \\.\pipe\termalin-ssh-agent 上运行自己的密钥代理,并通过标准的 ssh-agent 协议把你已在应用里解锁的密钥提供出去——于是任何会说这个协议的工具都能通过它签名,无需启用任何操作系统服务,无需 eval 那一行,也无需手动 ssh-add。私钥留在运行中的应用里,从不写入磁盘。这个代理的意义,是让 Termalin 自己的 MCP 服务器能用你的密钥向服务器认证,却从不持有密钥材料本身——代理负责签名,调用方永远看不到秘密。这和 OpenSSH 服务“解锁一次”的思路一样,只是把 Windows 的那套设置从你肩上卸了下来。
Termalin 是一款免费、跨平台的 SSH 客户端,它内置的代理把你的密钥解锁一次,然后代你签名——下载它,或了解它如何安全地处理你的密钥。