Local Lightning and Upgrading to WSL2

If you’re a web developer and using Windows, then WSL2 is an exciting upgrade. It’s Microsoft’s completely new Windows subsystem for Linux and comes with some pretty fantastic performance improvements. This week I upgraded from WSL v1 to v2 and hit a few roadblocks with Local Lightning. WSL v2 switched to a virtualized environment which introduces some networking complexities. Here is how I worked around them.

Local Lightning with WSL v1 worked with a minor tweak.

Before I talk about WSL v2, let’s look at v1 and Local Lightning. Local Lightning is my go to for hosting local WordPress websites. It’s cross platform, fast and super easy to use. However attempting to use WP-CLI over WSL would give the following error message.

Error: Error establishing a database connection.

With WSL v1 this could easily be worked around by modifying the wp-config.php file to use define( 'DB_HOST', "127.0.0.1:<db-port>" ); rather then the default define( 'DB_HOST', 'localhost' );. This allowed WSL to talk directly to Local Lightning’s database which runs on the Windows side.

WSL v2 networking makes talking to Local Lightning really tricky.

Since WSL v2 is running within a virtual environment, this adds a layer of separation between Windows and WSL. The above workaround fix for WSL v1 will not work with v2. This appears to be a hot topic of discussions as it prevents a wide range of services: https://github.com/microsoft/WSL/issues/4619. Rather then downgrading to WSL v1, I came up with the following workaround hack to get WSL v2 working with Local Lightning. This method could also be used to allow WSL v2 talk to other localhost ports served from Windows.

Steps to allow WSL v2 to talk directly to Window’s localhost ports.

The IP address that WSL uses changes whenever the computer is restarted. We can grab that new IP using some fancy windows commands within linux and store it as an shell environment variable. Add the following code to your .zshrc or alternative startup bash profile.

export WSL_HOST_IP=$( cmd.exe /C netsh interface ip show addresses "vEthernet (WSL)" | grep "IP Address" | sed -e "s/\s*IP Address:\s*//g" )

This means $WSL_HOST_IP will be the IP address that we’ll need to talk to instead of 127.0.0.1. Now let’s adjust our wp-config.php to using this IP address, but only if the shell environment variable exists. Otherwise we can default back to Local Lightning’s default. Be sure to change <db-port> to the port number listed within Local Lightning.

$wsl_host_ip = getenv('WSL_HOST_IP');
if ( ! empty( $wsl_host_ip ) ) {
    define( 'DB_HOST', "$wsl_host_ip:<db-port>" );
} else {
	define( 'DB_HOST', 'localhost' );
}

This still won’t work for the following reasons:

  1. Windows’ firewall will block connections from WSL.
  2. Local Lightning isn’t serving the database over the virtual network IP address, it’s using 127.0.0.1.

Both of these roadblocks can be overcome using PowerShell. I adapted my script from this user contribution. You may need to extend the ports section to cover your Local Lightning database ports. Create this as wsl-startup.ps1 and store somewhere like C:\tools\startup\.

# Ports to forward. Includes common Local Lightning ports.
$ports=@(10000,10001,10002,10003,10004,10005,10006,10007,10008,10009,10010);
$ports_all = $ports -join ",";

# Fetch WSL IP address
$windows_address = netsh interface ip show addresses "vEthernet (WSL)" |  findstr "IP Address"
$windows_address = $windows_address -replace " *IP Address: *", ""

# Remove Firewall Exception Rules
iex "Remove-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' ";

# Add Exception Rules for inbound and outbound Rules
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Outbound -LocalPort $ports_all -Action Allow -Protocol TCP";
iex "New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort $ports_all -Action Allow -Protocol TCP";

# Remove all existing proxy rules
netsh interface portproxy reset

# Add new proxy rules
for( $i = 0; $i -lt $ports.length; $i++ ){
  $port = $ports[$i];
  iex "netsh interface portproxy add v4tov4 listenport=$port listenaddress=$windows_address connectport=$port connectaddress=127.0.0.1";
}

This can be manually run within PowerShell like this.

Powershell.exe -executionpolicy bypass -File C:\tools\startup\wsl-startup.ps1

This script will allow connections from WSL to proxy through using the WSL IP address to the Window’s localhost. Running netsh interface portproxy show all within PowerShell will reveal the mappings. Here is an example of what that looks like.

PS C:\tools\startup> netsh interface portproxy show all

Listen on ipv4:             Connect to ipv4:

Address         Port        Address         Port
--------------- ----------  --------------- ----------
172.29.16.1     10000       127.0.0.1       10000
172.29.16.1     10001       127.0.0.1       10001
172.29.16.1     10002       127.0.0.1       10002
172.29.16.1     10003       127.0.0.1       10003
172.29.16.1     10004       127.0.0.1       10004
172.29.16.1     10005       127.0.0.1       10005
172.29.16.1     10006       127.0.0.1       10006
172.29.16.1     10007       127.0.0.1       10007
172.29.16.1     10008       127.0.0.1       10008
172.29.16.1     10009       127.0.0.1       10009
172.29.16.1     10010       127.0.0.1       10010

Last, schedule this script to run each time the computer is restarted. This will recreate firewall and network proxy rules. Enjoy!

References: