SSH Into WSL in 2023
Updates for an old hack.

It has been a long time since my post on using Windows as an SSH server. One of the example use cases for such a twisted setup was to access WSL2 shell and services. It no longer works because “store WSL currently isn’t accessible from session 0 contexts”1.

When you run wsl command in a local PowerShell session, it takes you to the default WSL distribution. But if you are in a remote PowerShell session through SSH, running any wsl commands will only give you an error:

The file cannot be accessed by the system.

The error persists even when you change the default SSH shell to WSL.

This was not an issue for me, until one day I suddenly had to remotely launch and access my Jupyter inside WSL through SSH. The solution for that day involved Remote Desktop, which basically is cheating (and requires stable and fast internet connection).

In case I run into a similar situation under worse connection, I implemented the following workaround.

The Minimal Workaround

The default solution for remote access of Linux in general is SSH. In the past, setting up an SSH server in WSL2 requires some hacky customized scripts because there used to be no systemd. But official support for systemd on WSL2 has been added since not long ago. Now we can simply install openssh-server in WSL2, and use the Windows host as a bastion (or a jump host) to get a remote shell inside WSL2:

ssh -J <Windows username>@<Windows IP> <WSL2 username>@<internal WSL2 IP>

The tricky part is to find out the <internal WSL2 IP> (which changes every boot) without using any WSL commands2. The answer is (get-hnsendpoint).ipaddress3, which hopefully will print at least one IP address, and one of them should be the address assigned to your WSL2 instance.

Detailed Steps

Assuming you already have SSH server enabled on Windows, and your WSL2 is already running.

  1. Enable systemd in WSL2 (do this when the computer is physically available, or when you can use some sort of Remote Desktop):

    1. Inside your WSL2 instance, Create or edit the file /etc/wsl.conf to include this:

      [boot]
      systemd=true
      
    2. Restart WSL with wsl.exe --shutdown from PowerShell and then enter WSL as usual.

  2. Install, config, enable, and start SSH server just like a regular Linux server:

    1. For example, sudo apt-get install openssh-server

    2. Edit /etc/ssh/sshd_config if you don’t like the defaults

    3. Use systemd to manage the SSH server, for example:

      sudo systemctl enable ssh
      sudo systemctl start ssh
      
  3. Find out the internal address for WSL:

    1. SSH into the Windows host
    2. Run (get-hnsendpoint).ipaddress in PowerShell. If there are more than one address, you’ll need to try them one-by-one
  4. Now you can SSH into it from PowerShell with:

    ssh <WSL2 username>@<internal WSL2 IP>
    

    Or you can exit and reconnect with

    ssh -J <Windows username>@<Windows IP> <WSL2 username>@<internal WSL2 IP>
    

Steps 1 and 2 are set-and-forget, while step 3 is required every time you (re)boot the Windows host.

Additional Hacks

The main advantage of the minimal workaround above is that it needs no customized scripts. The disadvantages are:

  1. WSL must be already running. You won’t be able to start WSL instances because you cannot use the wsl commands from remote PowerShell4. This may be a problem if you have to remotely restart the computer.
  2. IP addresses needs to be identified manually. Sometimes (get-hnsendpoint).ipaddress will print multiple addresses, and only one of them is for WSL. This makes it difficult to get an fully automatic solution.

For 1, a simple hack is to use Windows Terminal: set a WSL distribution as the default profile, and enable ’launch on machine startup’.

For 2, this is leading us to the classic problem of assigning a static IP for WSL2. The issue is full of creative hacks.

Alternatives

Here are some alternatives that I have considered but not implemented. Theoretically they should work.

Since the original problem was to start and access my Jupyter Lab inside WSL2, all I need to do is to have Jupyter auto-started. Now that we have systemd, it’s as easy as providing a config file. Then I can remotely access it with an SSH tunnel5, and use the included web shell for command line stuff.

You can also use systemd to manage a remote SSH tunnel, and forward the SSH port on WSL2 to an empty port on Windows. While the IP address of the Windows host relative to WSL2 also changes with every boot, it can be deterministically extracted6. I suspect that simply using a ZeroTier or Tailscale IP address will also work, if you are already using those.


  1. When you SSH into Windows, you enter a PowerShell (or Command Prompt) session that is ‘session 0’, from which access to apps installed from Microsoft Store is restricted. But the Microsoft Store is the official release channel for WSL. ↩︎

  2. Because WSL commands are not accessible when you are on SSH PowerShell. ↩︎

  3. It seems to be a Host Network Service (HNS) command which indeed sounds very relevant. Using it to look up WSL addresses was mentioned in this GitHub issue↩︎

  4. But if you are in a remote WSL shell, you can use the wsl.exe command to run most of the wsl commands in PowerShell. For example, you can run wsl.exe --shutdown to lock yourself out of WSL until you have physical access to the machine. ↩︎

  5. By default, Windows will forward services on WSL2 localhost to Windows localhost. So if Jupyter Lab is already running on port 8888 in WSL, remote access can be achieved with ssh -L 8888:127.0.0.1:8888 <Windows user name>@<Windows IP> then open http://127.0.0.1:8888 in the browser. ↩︎

  6. The command is cat /etc/resolv.conf | grep nameserver | awk '{print $2; exit;}'↩︎


Last modified on 2023-01-31