Use the SSH keys stored in your Windows Bitwarden Desktop vault from inside
WSL2 — ssh
, git
, ssh-add
, etc. — managed by a systemd user service
instead of a fragile .zshrc
snippet.
This bridges the Windows OpenSSH-agent named pipe that Bitwarden exposes
(\\.\pipe\openssh-ssh-agent
) to a Unix socket inside WSL using npiperelay +
socat
.It is a hardened version of the excellent walkthrough at
https://www.rebelpeon.com/bitwarden-ssh-agent-on-wsl2/, with fixes for the
problems that make ssh-add -l
hang.
Windows │ WSL2
│
Bitwarden Desktop (standalone build) │
└─ \\.\pipe\openssh-ssh-agent ◀────────┼── npiperelay.exe ◀── socat ◀── ~/.ssh/agent.sock
(one pipe instance) │ (per connection) (listener) ▲
│ │
│ ssh / git / ssh-add (SSH_AUTH_SOCK)
A systemd user service runs one persistent socat
listener. socat
forks a
short-lived npiperelay.exe
for each incoming connection, which relays bytes to Bitwarden's named pipe.
The Microsoft Store / Appx build of Bitwarden Desktop runs in a UWP
AppContainer sandbox and does not serve the SSH agent to external clients.
The pipe appears and connections open, but Bitwarden never replies — so
everything hangs, including the native Windows ssh-add.exe
.
Check what you have (PowerShell):
Get-AppxPackage *Bitwarden* # if this prints anything, you have the Store build — uninstall it
Fix — remove the Store/Appx build, then install the standalone build:
Get-AppxPackage *Bitwarden* | Remove-AppxPackage
winget install Bitwarden.Bitwarden
Or download the desktop installer from https://bitwarden.com/download/.
Bitwarden → Settings → SSH agent → Enable SSH agent. Have at least one SSH key item in your vault, and keep the vault unlocked (the agent only answers while unlocked).
It would fight Bitwarden for the same pipe name. PowerShell (admin):
Stop-Service ssh-agent
Set-Service ssh-agent -StartupType Disabled
- WSL2 with
systemd enabled—
/etc/wsl.conf
:(then
[boot]
systemd=true
wsl --shutdown
from Windows once) socat
installed:sudo apt install socat
npiperelay.exe
on the Windows side. Easiest:then find the path (it lives under
winget install albertony.npiperelay
C:\Users\<you>\AppData\Local\Microsoft\WinGet\Packages\albertony.npiperelay_*\npiperelay.exe
).Enable lingering so the service runs without an interactive login:
loginctl enable-linger "$USER"
Find your npiperelay path and put it inbw-ssh-relay
(edit theNPIPERELAY=
line). From WSL:
ls /mnt/c/Users/*/AppData/Local/Microsoft/WinGet/Packages/albertony.npiperelay_*/npiperelay.exe
Install the relay script:
mkdir -p ~/.local/bin
cp bw-ssh-relay ~/.local/bin/bw-ssh-relay
chmod +x ~/.local/bin/bw-ssh-relay
Install the service:
mkdir -p ~/.config/systemd/user
cp bitwarden-ssh-agent.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now bitwarden-ssh-agent.service
Point your shell at the socket. Add to~/.bashrc
/~/.zshrc
:
export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
Test(open a new shell):
ssh-add -l # should list your Bitwarden SSH key(s)
| File | Goes to | Purpose |
|---|---|---|
bw-ssh-relay |
||
~/.local/bin/bw-ssh-relay |
||
Launches the socat ↔ npiperelay bridge (the service's ExecStart ). |
||
bitwarden-ssh-agent.service |
||
~/.config/systemd/user/ |
||
| systemd user unit that keeps one relay listener running and restarts it on failure. |
This is the upstream-article command: ** -ei -s** — nothing more.
| Flag | Meaning |
|---|---|
-ei |
|
| Terminate when the client (stdin) closes — clean teardown per connection. This is what prevents process leaks, so it must be able to fire. | |
-s |
|
| Send a 0-byte message after EOF (part of the original recipe). |
⚠️
Do NOT addIt seems helpful ("poll until the pipe is free") but it is a trap with Bitwarden. Bitwarden exposes only-p
.onepipe instance; when it is busy or wedged,-p
makes npiperelay loop forever inDialPipe
. While polling it never reads stdin, so-ei
can never fire on disconnect — and the Windowsnpiperelay.exe
leaks underwslhost
,one orphan per ssh/git operation, until you kill them by hand. Without-p
, a busy pipe just makes the connection fail fast (retryable). See the troubleshooting note below.
The common .zshrc
approach has every shell try to start/manage its own socat
. That causes:
Races between shells spawning competing listeners.Hangs at shell startup if the block probes the agent withssh-add -l
while Bitwarden's single pipe instance is busy — the new terminal blocks forever.Orphaned processes left behind by closed terminals.npiperelay.exe
A single systemd user service owns exactly one listener, survives across all terminals, restarts on failure, and starts on boot (with linger).
Figure out which side is broken. The native Windows client talks straight to the pipe — no WSL, socat, or npiperelay involved:
/mnt/c/Windows/System32/OpenSSH/ssh-add.exe -l
It also hangs/fails → the problem is Bitwarden, not WSL. See below.** It works but WSL doesn't → the problem is the relay**(socat/npiperelay path, flags, or socket). Checkjournalctl --user -u bitwarden-ssh-agent
.
Bitwarden exposes a single pipe instance. If a client connection is
interrupted or force-killed — a closed terminal mid-operation, a Ctrl-C'd
ssh-add
, or Stop-Process -Force
on npiperelay.exe
— Bitwarden may fail to
re-arm the listener. After that, every client (including native
ssh-add.exe
) hangs until you kick the agent:
- Bitwarden → Settings → SSH agent → toggle off, then on; or Fully quit Bitwarden (tray → Quit) and reopen + unlock.
This is why this unit deliberately
does notforce-kill orphanednpiperelay.exe
— abruptly killing an in-flight pipe client is one of the things that wedges the agent.
If orphaned npiperelay.exe
ever genuinely pile up, clear them by hand (PowerShell):
Get-Process npiperelay -ErrorAction SilentlyContinue | Stop-Process -Force
This means another client momentarily holds Bitwarden's single pipe instance, or the agent is wedged. Do not "fix" it with -p — that trades a fast, retryable error for an unbounded orphan leak (see the flags note above). Instead:
- If it's transient (two ssh ops at once), just retry.
- If it persists with nothing connected, the agent is wedged — kick Bitwarden (toggle the SSH agent setting, or fully quit + reopen).
- If you see orphaned
npiperelay.exe
piling up, you almost certainly have-p
somewhere — remove it.
systemd user services can normally launch Windows .exe
files in recent WSL. Verify:
systemd-run --user --wait --pipe /path/to/npiperelay.exe -h
If that fails, your WSL build may not expose interop to systemd services; update
WSL (wsl --update
).
systemctl --user status bitwarden-ssh-agent
systemctl --user restart bitwarden-ssh-agent
systemctl --user stop bitwarden-ssh-agent
journalctl --user -u bitwarden-ssh-agent -f
- Original walkthrough:
https://www.rebelpeon.com/bitwarden-ssh-agent-on-wsl2/
npiperelay
:https://github.com/albertony/npiperelay(fork of jstarks/npiperelay)