SSH into Windows 11 WSL2 with Agent Forwarding
You’ll SSH into WSL2 through Windows with ProxyJump, forward your SSH agent end-to-end, and keep your GitHub SSH key on your laptop (or somewhere even safer!).
I have a confession: I’m a Windows noob. I want to keep my PC’s Windows 11 installation as “stock” as possible. I’ve worked with enough computers to know that fixing computers as an engineer is the optimal method by which to break them, and I don’t trust myself to know what I broke (or how to un-break it).
More concretely, I want to avoid leaving private keys on the Windows machine (which I don’t know how to secure properly), and I’d rather push the configuration and moving parts to the places I understand—my macOS client and the Linux installation inside WSL—instead of learning Windows internals the hard way.
This is also why agent forwarding matters to me. The screen I actually look at most of the time is a laptop terminal, and I don’t keep valuable credentials on random secondary machines*. So, how do I SSH into my Linux box on a Windows gaming PC, use my laptop’s credentials when needed (e.g., cloning private repos), and keep the Windows host as unconfigured as possible?

Standard Practices (don’t use them) §
1. Expose WSL’s sshd to LAN with port forwarding §
The archetypal advice for this usually points to something similar to Scott Hanselman’s guide. It’s pretty straightforward: Linux is running sshd, so expose it to LAN by forwarding an inbound port on the host to the open port on the Linux VM. However, as noted in the link, this is actually difficult and brittle. The underlying issue is how WSL2 connects to the network: through a virtual subnet between the host and the WSL distro.
This means that the Linux box has its own IP address, different from the Windows host, which changes every time WSL is restarted. WSL2 does a lot of magic—NAT’ing packets and tunneling DNS—in order to hide this complexity and make your machine appear as a seamless unit running two OSes simultaneously, but it will not automatically forward traffic from an open LAN port to its WSL instance†.
But to me (and to that author), setting up Windows Firewall rules and Service Control Manager entries to detect and forward ports to a changing IP address: not just fragile, but also a baroque labyrinth (again, to my noob eyes).
2. Change the default OpenSSH shell to wsl.exe §
Another approach involves changing the default OpenSSH shell to wsl.exe, which faithfully replicates opening up a TTY in WSL as if you were typing into Windows Terminal. However, that’s not what I want: I want the Linux shell to know it’s being SSH’d into so that agent forwarding works properly. More importantly, Windows OpenSSH doesn’t support agent forwarding at all, which is pretty understandable given the interop nightmare that would be required to forward a Unix socket over Windows named pipes.
3. Use ProxyCommand to hop through Windows §
This SuperUser answer is getting much warmer: ProxyCommand allows you to hop through an intermediate SSH server, in this case the Windows machine, before connecting to the final destination, the Linux box inside WSL. This is effectively the same as nesting the Linux SSH session inside the Windows one: it still requires setting up client keys on the jump host (Windows) because the agent forwarding still stops before the Windows machine.
In my case, my private keys live on either a FIDO2 hardware key plugged into my laptop or held in custody by a password manager, so I couldn’t and wouldn’t just copy them over to another computer.
Solution: ProxyJump §
It turns out what I need is ProxyJump. This transparently forwards the protocol-level connection, rather than a shell TTY from a new ssh client instance, through the Windows OpenSSH server, with the intent that it’s like SSHing into the destination server with invisible hops.
Here’s the whole setup, assuming you already installed a systemd- and apt-based distro WSL (I used Debian).
1. Set up SSH Server in WSL §
From the WSL terminal, install the OpenSSH server:
$ sudo apt install openssh-serverConfigure it to listen on a different port (e.g., 2222) to avoid conflicts with Windows’s own OpenSSH server on port 22. Edit /etc/ssh/sshd_config and change the Port directive, as well as accept clients’ agent forwarding (off by default):
$ sudo sed -i 's/#Port 22/Port 2222/' /etc/ssh/sshd_config
$ sudo sed -i 's/#AllowAgentForwarding yes/AllowAgentForwarding yes/' /etc/ssh/sshd_configStart the sshd service and enable it to start on boot:
$ sudo systemctl enable --now ssh2. Set up OpenSSH Server in Windows §
Make sure the Windows OpenSSH server is installed and running with the Microsoft steps.
Then it’ll need your public key in its authorized_keys file, but it’s not the usual song and dance. Windows’s OpenSSH server expects the authorized_keys file to be in a different location depending on whether your user is an administrator or not. See Microsoft’s documentation for details. TL;DR:
- for non-admin users, it’s in
%USERPROFILE%\.ssh\authorized_keysAKAC:\Users\username\.ssh\authorized_keys. - for admin users, it’s in
%PROGRAMDATA%\ssh\administrators_authorized_keysAKAC:\ProgramData\ssh\administrators_authorized_keys.
By the way, if you try to be cute and use ssh-copy-id from your SSH client, you’ll get the usual Windows “command not found” error, because the other side isn’t running a proper Unix shell:
$ ssh-copy-id <Windows-user>@<Windows-host>
...
'exec' is not recognized as an internal or external command,
operable program or batch file.
...
From Windows, try SSHing into the WSL VM:
PS C:\> ssh -p 2222 <wsl-user>@localhost
<wsl-user>@<wsl-host>'s password:
<wsl-user>@<wsl-host>:~$
3. Try SSH Client with ProxyJump §
From your MacBook (or other SSH client), put it all together with -J for ProxyJump and -A for ForwardAgent:
$ ssh -A -J <Windows-user>@<Windows-host>:22 -p 2222 <wsl-user>@localhost
<wsl-user>@<wsl-host>'s password: # Enter password for WSL user
<wsl-user>@<wsl-host>:~$
Breaking this down:
-Aenables agent forwarding from your MacBook to the final destination.-J <Windows-user>@<Windows-host>:22tells SSH to first connect to the Windows host asUseron port 22‡, then transparently forward the connection to the final destination.-p 2222 <wsl-user>@localhostconnects to the jump host’slocalhoston port 2222 asuser.
If this is working, the first thing you should do is copy over your keys to the Linux box:
$ ssh-copy-id -o ProxyJump=<Windows-user>@<Windows-host>:22 -p 2222 <wsl-user>@localhostTips if it’s not working:
- If you’re like me, your Windows username is capitalized, but your Linux username is lowercase because that’s what the WSL installer automatically did. Make sure you’re explicitly specifying the right usernames in both places.
- The password prompt you see is for the Linux user, not the Windows one, unless you skimmed this entire post and already set up your Linux
authorized_keys. - Windows 11 is equipped with mDNS broadcast, so I was able to use
<Windows-host>.localinstead of the IP address. However, the hostname it broadcasts was also true to its name’s case§, in my case upper-, so I was looking at a very jarringXo@Windows-PC.local, which just looks wrong as a URI. - The
localhostis literal, not a placeholder for some other hostname. It’s how the Windows host refers to its own tunneled WSL network endpoint.
4. Configure ~/.ssh/config for convenience §
To avoid typing the long command every time, add an entry to your ~/.ssh/config file on your MacBook:
Host
HostName localhost
User
Port 2222
ProxyJump @:22
ForwardAgent yes Now you can just do:
$ ssh <wsl-host>
<wsl-user>@<wsl-host>:~$
The Payoff §
And this is the part that makes the whole setup worth it: once agent forwarding works end-to-end, the Linux box can use my laptop’s SSH agent to authenticate to anything that my laptop has access to. At this point, I can clone a private repo on GitHub from inside WSL without my private SSH keys ever leaving my password manager or hardware security key.
For example (from inside the WSL session):
<wsl-user>@<wsl-host>:~$ ssh -T git@github.com
Hi GHF! You've successfully authenticated, but GitHub does not provide shell access.
<wsl-user>@<wsl-host>:~$ git clone git@github.com:<GitHub-user>/<private-repo>.gitMy actual motivation here is GPU development. I’m working on a WebGPU project where it’s still dramatically easier (for me) to prototype with CUDA than with the compute shaders it’ll eventually be written in: coherent (“Unified”) memory, simple adapter/context abstractions, and tooling that feels much closer to CPU debugging¶, not to mention nostalgia for the tools I grew up with (we don't talk about OpenCL 💔). But this is 2025 and one CUDA-compatible NVIDIA GPU-equipped computer needs to serve double duty as a Windows gaming box and Linux C++ development box, so I ended up with WSL.
As an aside, there are entire tomes of documentation for this setup on both Microsoft’s and NVIDIA’s sites, presumably for machine learning folks who have similar needs, but I have to keep in mind that I’m significantly more likely to blow up my computer than a data scientist would theirs.
That’s the tension this post tries to resolve: keep Windows boring, keep keys off Windows, keep my day-to-day workflow in Unix tools, and still make it easy♠ to hop in from my laptop and pull down the same dotfiles and repos I use everywhere else.