hardware/windows/wsl-lan-access.md

WSL: LAN Access for Services

Current machine state (2026-09-19)

  • Windows WSL version: 2.7.14.0
  • Ubuntu is a WSL 2 distro.
  • WSL's default distro was docker-desktop; run wsl --set-default Ubuntu once so plain wsl opens Ubuntu.

Starting Ubuntu

wsl

This starts Ubuntu if it is stopped. exit returns to PowerShell. Use wsl -d Ubuntu when an explicit distro selection is useful.

If WSL returns Catastrophic failure / Wsl/Service/E_UNEXPECTED, the successful recovery sequence was:

wsl --shutdown
Restart-Service WslService -Force
wsl --update
wsl -d Ubuntu

Run the service restart from an elevated PowerShell. wsl --shutdown stops all WSL distros, including Docker Desktop's WSL backend.

Exposing a WSL service on the LAN

Recommended: mirrored networking

Create C:\Users\domin\.wslconfig:

[wsl2]
networkingMode=mirrored

Apply it with:

wsl --shutdown

This also stops WSL/Docker temporarily. Start WSL again afterward with wsl.

Mirrored mode is not broad, automatic port forwarding. It lets WSL participate directly in the host's networking, but the Hyper-V firewall still blocks unsolicited inbound LAN traffic by default.

For each service intended for LAN use:

  1. Make the service listen on 0.0.0.0:<port> in WSLโ€”not just 127.0.0.1.
  2. Add a narrowly scoped Hyper-V firewall allow rule for the required TCP or UDP port from elevated PowerShell.
  3. Reach it from another device at http://<Windows-LAN-IP>:<port>.

Example: allow TCP port 3000 for WSL:

New-NetFirewallHyperVRule `
  -Name "WSL-TCP-3000" `
  -DisplayName "WSL TCP 3000" `
  -Direction Inbound `
  -VMCreatorId "{40E0AC32-46A3-4E02-9C4E-9C6F1CEFAF82}" `
  -Protocol TCP `
  -LocalPorts 3000

For a stricter LAN-only rule, add the local subnet (adjust it to the actual LAN):

  -RemoteAddresses "192.168.1.0/24"

Create separate rules for every intentionally exposed TCP/UDP port. Do not disable the WSL/Hyper-V firewall just to make a service reachable.

Alternative: NAT + portproxy

Mirrored mode is not required. In the default NAT mode, a WSL listener is already reachable on Windows at 127.0.0.1:<port> (WSL localhost forwarding). Forward the LAN in from there with a Windows portproxy pointing at loopback โ€” not the WSL IP โ€” so it survives the dynamic WSL address across reboots:

New-NetFirewallRule -DisplayName "<svc> (TCP <port>)" `
  -Direction Inbound -Action Allow -Protocol TCP -LocalPort <port> -Profile Private

netsh interface portproxy add v4tov4 `
  listenaddress=0.0.0.0 listenport=<port> connectaddress=127.0.0.1 connectport=<port>

This needs an elevated shell; the rule and portproxy are persistent. It avoids the global .wslconfig change (and its WSL restart) that mirrored mode requires.

Still used on this machine for: sshd (port 2204) โ€” works fine either way, left alone. No longer used for: pi-web (port 30142) โ€” retired 2026-09-21 when the whole machine switched to mirrored mode; a leftover portproxy rule for it had to be deleted because it conflicted with WSL's own direct bind under mirrored mode. See ../ubuntu-wsl/services/pi-web.md ยง4/ยง9.

When mixing the two approaches on one machine (as here): once mirrored mode is on globally, any new NAT-style loopback portproxy you add for a service that WSL now binds directly under mirrored networking will conflict (EADDRINUSE) rather than coexist. Portproxy rules only make sense here for cases where you deliberately still want the old relay-via-loopback behavior (e.g. if a service's WSL-side bind address is finicky) โ€” check for and remove stale ones first.

Local-only services

For ordinary development, access WSL services from Windows at http://localhost:<port>. No LAN rule is needed unless another device must connect.