Effective WSL

Due to restrictions in many workplaces, a lot of developers are bound to using Microsoft Windows.
While Windows itself is a good and stable operating system, out of all major operating systems I would argue it is the worst for power users and especially programmers, because it lacks a good and well integrated shell and CLI utilities like the ones we can find on Unix based systems.
To mitigate this problem, Microsoft created WSL, now in its second major iteration with the goal to run a fast Linux VM with good interoperability with Windows.
WSL is a solid system, but in order to utilize it to its fullest extent, we need to take a look under the hood and examine its strengths and weaknesses.
Today I want to show you how to tweak WSL to make it much more usable, so that it can truly become the center piece of your software development activities on Windows.

Hyper V

WSL gets managed by the Microsoft-developed Hypervisor Hyper-V. If you have ever set up a VM using VMWare, you probably already have a feeling what a Hypervisor is. A Hypervisor is a software layer that sits between an OS and the hardware and configures the hardware dedicated to the VM, which includes the amount of CPU, RAM and disk space. It also configures things like Networking and access to periphery. Essentially its job is to isolate an operating system so it can run alongside others. Generally we differentiate between two types of Hypervisors - Type 1 and Type 2.

Type 1 hypervisor architecture diagram Type 2 hypervisor architecture diagram

While type 1 Hypervisors run directly on the hardware and manage every OS running on the machine, type 2 Hypervisors run as a child process of one central “Host OS”. Generally this makes Type 1 Hypervisors noticeably faster than their counterpart, because not every hardware interaction needs to be go through the Host OS.

Hyper-V is built around the concept of type 1 virtualization, which is why its performance is way better than something like VMWare VMs (Type 2) allow for. That’s also why big cloud providers like AWS also use Type 1 Hypervisors to provide their high performance VPS instances like EC2.

Windows does a lot of configuration on Hyper-V to make WSL integrate better with Windows, which may even convey the feeling that WSl is managed by a Type-2 Hypervisor.
The important thing to understand is that WSL is a system running independent from Windows - it acts as its own logical host with its own memory, storage, network interfaces and so on. Knowing this makes us understand the seeming quirks of WSL much more.

Cross File System access

Cross file system access is painfully slow. This means it is highly advisable to keep all files inside the same system’s file system you want to work with. Usually we will want to work in WSL, therefore we need to put all of our source code into the WSL file system. But why is cross file system access so slow and how can we still manage to share files efficiently if we need to?

The problem is that WSL and Windows use different file systems. Windows usually uses NTFS, while Linux generally uses ext4 and the major problem is that these are inherently incompatible. For example

  • they have an entirely different permission management methodology
  • ext4 uses case-sensitive file names, while NTFS does not (in Windows you actually can’t create a file named file.txt and File.txt in the same directory in case you haven’t tried before)

When we access the files stored inside the drive of another OS, every operation has to be run through a translation layer, which roughly means that Windows and WSL both run file servers, which serve all their drives’ contents to their counterpart - that’s the part for the slowness. Due to the arising inefficiencies running a simple git status on a medium size repository can take half a minute.

Knowing this limitation, we still have easy access to both filesystems. We can access

  • Windows files inside WSL by accessing the mount point /mnt/<drive>
  • Linux files from Windows at \\wsl$\<distro_name>

Personally, I find it incredibly useful to symlink some of my Windows folders to WSL, for example downloads. Most of the stuff I download from the net or people send me via Teams lands in my downloads folder. Having a symlink to that location in place makes downloaded files better accessible for development related activities. The symlink can be created like so:

ln -s /mnt/c/Users/Steff/Downloads /home/steff/downloads

Afterwards, one can easily reference the downloads folder by ~/downloads. Even though cross file system access is slow, as long as the number of files is low and not all of them have to be accessed at the same time (looking at you git status 👀), this is not a big issue.

Networking Deep Dive

There are 2 networking modes for WSL

  • The default one, which uses Windows as a NAT Gateway
  • mirrored Networking mode - A newer alternative, which replicates the Windows network interfaces directly into WSL

NAT Networking Mode (the default)

Even though Windows and the WSL-OS run independent from one another, we need some way for the two systems to communicate easily. The way this is implemented is through a VLAN, in which both WSL and Windows take part. We can examine the respective network interfaces and their IP addresses in both systems.

In Windows, we can execute ipconfig /all in Powershell to get a list of all the available network interfaces. Among the list, we can find the interface for the WSL VLAN:

Ethernet adapter vEthernet (WSL (Hyper-V firewall)):

    Connection-specific DNS Suffix:
    Link-local IPv6 Address . . . . . : fe80::db54:7294:3c2e:2756%21
    IPv4 Address  . . . . . . . . . . : 172.24.208.1
    Subnet Mask . . . . . . . . . . . : 255.255.240.0

The adapter has the IP 172.24.208.1. The IP ends with 1, which makes this interface the default Gateway of the associated VLAN.
Inside WSL, we can obtain the network interface list by issuing ip addr

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1492 qdisc mq state UP group default qlen 1000
    link/ether 00:15:5d:88:b9:88 brd ff:ff:ff:ff:ff:ff
    altname enx00155d88b988
    inet 172.24.222.66/20 brd 172.24.223.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::215:5dff:fe88:b988/64 scope link
       valid_lft forever preferred_lft forever

By default, there will only be two network interfaces visible from within the WSL - The eth0 interface and a loopback interface. We can observe that the eth0 interface has an IP out of the address range of the VLAN, for which the Windows adapter poses as the default gateway.
This means that WSL has no other means to communicate with the internet, but to go through the Windows host. To completely verify this observation,we can take a look at the routing table using ip route, which gives us the following output

default via 172.24.208.1 dev eth0 proto kernel
[...]

The important part is the first line, which starts with default via. This line alone lets us verify our deduction that all traffic is routed through Windows. This means that windows acts as a NAT Gateway (that’s super important to know).

Because we already understand how Type 1 virtualization works, we know that both systems are their own independent hosts. Now with the knowledge how networking between these hosts works, it probably does not come as a surprise that when using localhost in a pure Type 1 virtualized environment, it points to the host of the instance making the request, which in our case would be WSL (and not Windows).
WSL being nicely integrated however, comes with the option localhostforwarding, which is by default set to true. The option configures automatic port forwarding from the Windows localhost to the WSL. Note however, that no matter how you configure your firewall in WSL, this port is only available from Windows. If you want to expose your port over a real network (for testing purposes), you need to configure this on the Windows side.
localhostforwarding also only works in one direction. By default open Windows ports are not forwarded to WSL. To achieve this we would again have to do manual work and configure port forwarding.

Mirrored networking mode

Since Windows 11 22H2, there is a mirrored networking mode available. In this mode, Windows network interfaces are replicated directly into WSL. The idea is that when both windows and WSL use the same network interfaces we get a seamless experience - there is no more distinction between WSL and Windows, they both resolve to the same logical host and we don’t have to think about networking anymore.

We can enable mirrored mode in the global WSL settings file, located at C:\Users\<your-user>\.wslconfig like so:

[wsl2]
networkingMode=mirrored

Afterwards we need to make sure to restart WSL using wsl.exe --shutdown. Upon the next boot, we can observe how this setting has changed networking.

Let’s run ip addr in WSL:

eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1492 qdisc mq state UP group default qlen 1000
    link/ether 04:7c:16:00:55:88 brd ff:ff:ff:ff:ff:ff
    altname enx047c16005588
    inet 192.168.178.73/24 brd 192.168.178.255 scope global noprefixroute eth0
       valid_lft forever preferred_lft forever

and let’s run ipconfig /all in Powershell:

Ethernet-Adapter Ethernet:
   Connection specific DNS-Suffix    : fritz.box
   IPv4-Address  . . . . . . . . . . : 192.168.178.73(Preferred)

Well look at this. While the VLAN interface we observed earlier still exists, the IP address of the WSL eth0 interface is now the same as of the Windows host - amazing.

This is my personally preferred way of doing networking, because it takes away the need to think of WSL and Windows as two separate hosts. You might be thinking why I even explained the NAT mode in so much detail if there is a better option available. The problem is that not every developer is using mirrored and you have to account for that in your software. If you have several services running in your WSL, you should not reference them using localhost, but rather with the IP of your WSL instance to be sure. These kinds of different system configurations make it (annoyingly) necessary to get to terms with all eventualities.

There is one more thing we need to consider when we want to make our applications reachable to other devices in the system - The Hyper-V firewall. It’s just a regular firewall really, which blocks all inbound traffic to the WSL instance in its default configuration. Based on your personal needs and security requirements we have the option

to allow inbound traffic on all ports:

Set-NetFirewallHyperVVMSetting -Name '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -DefaultInboundAction Allow

or to allow inbound traffic on specific ports, e.g. 80:

New-NetFirewallHyperVRule -Name "MyWebServer" -DisplayName "My Web Server" -Direction Inbound -VMCreatorId '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}' -Protocol TCP -LocalPorts 80

The command you decide for needs to be issued in a Powershell instance with Administrator privileges.

Calling Windows programs from WSL

Windows has done a nice job making it possible to call Windows executables from WSL. Actually the entire Windows PATH variable is added to the PATH variable of the WSL instance. We can nicely observe this by printing all WSL path entries to a new line: echo "$PATH" | tr ':' '\n'.

It is even possible to mix Windows and Linux programs. Granted, most of the time this is not useful, but one of the programs I reach for often is clip.exe, which can be used to copy arbitrary data into the Windows system clipboard. For example, we could easily copy the name of your WSL OS to the clipboard like so:

cat /etc/os-release | grep PRETTY_NAME | clip.exe

Bonus: Installing certificates

When custom CA certificates are installed on Windows, for example the certificates from a companies private CA of their intranet, these certificates are not registered in WSL. This is quickly noticeable when we access REST APIs that were legitimated by those CAs. In case one of those APIs is accessed using curl, curl prints this dreaded error:

60: SSL certificate problem: self signed certificate in certificate chain

While we can work around that error by adding --insecure, this is a bad practice security-wise and we likely want to access these APIs through different programs without such flags as well.

Luckily, there is a way to export Windows certificates and then import them into WSL:

  1. Launch the Windows certificate manager certmgr.msc
  2. Under the tab Trusted root CAs, you should find your companys root CA certificate among all other trusted root CAs with trustworthy names like GoDaddy
  3. Right click the cert, select all tasks and then export. Make sure you export it DER encoded
  4. Convert the certificate from CRT to PEM format using sudo openssl x509 -inform der -outform pem -in <your-cert-name>.der -out <your-cert-name>.crt
  5. Import the certificate in PEM format into your Linux OS sudo cp local-ca.crt /usr/local/share/ca-certificates && sudo update-ca-certificates

Summary

WSL uses the type-1 Hypervisor Hyper-V, which means that the OS inside WSL runs with equal rights to Windows. Even though both OSes are logically separate hosts, both systems can access their counterparts’ files through File Servers, which are nicely abstracted away, but painfully slow.
With default networking settings, both systems take part in the same VLAN and Windows acts as a NAT gateway. We can enable mirrored networking mode to replicate the Windows networking interfaces into WSL to make them behave like one network node.
It is possible to call Windows programs from WSL and mix them with Unix commands.

plant
plant
2025-present Stefan Sommer. All Rights Reserved. stefan.sommer.dev@gmail.com
plant
plant
plant