Skip to main content
geeks have feelings

SSH into Windows 11 WSL2 with Agent Forwarding

Jump to Recipe ↓

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!).

SSH
SSH

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?

SSH into WSL demo screencast

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.

ProxyCommand: agent forwarding stops at Windows host
ProxyCommand: agent forwarding stops at Windows host

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.

ProxyJump: end-to-end agent forwarding (GitHub auth from WSL)
ProxyJump: end-to-end agent forwarding (GitHub auth from WSL)

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-server

Configure 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_config

Start the sshd service and enable it to start on boot:

$ sudo systemctl enable --now ssh

2. 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:

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:

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>@localhost

Tips if it’s not working:

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>.git

My 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.

Every Post by Year

  1. 2025
    1. SSH into Windows 11 WSL2 with Agent Forwarding
  2. 2023
    1. Ducati Timing Belt Replacement
    2. C++ Corrections
  3. 2016
    1. Liftlord
    2. Sensorless Brushless Can’t Even
  4. 2015
    1. Big Data: Test & Refresh
  5. 2014
    1. The Orange Involute
    2. Big Data EVT
  6. 2013
    1. Integer Arithmetic Continued
    2. Real Talk: Integer Arithmetic
    3. Why Microsoft’s 3D Printing Rocks
    4. Flapjack Stator Thoughts
    5. Delicious Axial Flux Flapjack
  7. 2012
    1. How to teach how to PCB?
    2. Fixed-point atan2
    3. It Was Never About the Mileage
    4. Trayrace
    5. BabyCorntrolling
    6. Conkers
    7. BabyCorntroller
    8. Templated numerical integrators in C++
  8. 2011
    1. Bringing up Corntroller
    2. Assembly-izing Tassel
    3. Corn-Troller: Tassel
    4. 5 V to 3.3 V with Preferred Resistors
  9. 2010
    1. HÄRDBÖRD: Interesting Bits
    2. HÄRDBÖRD: Hardcore Electric Longboard
    3. Mistakes to Make on a Raytracer
    4. US International Dvorak
  10. 2009
    1. Raxo
    2. Better Spheres, Fewer Triangles
    3. Donald Knuth Finally Sells Out
    4. Harpy – Sumo Bots 2009