An in-depth technical guide to understanding WSL 2 networking architecture, NAT, mirrored mode, port forwarding, and multi-distro scenarios.
- WSL 2 Network Architecture Fundamentals
- NAT Mode In-Depth
- Multiple Linux Distributions
- Localhost Port Forwarding Mechanism
- Port Conflicts and Resolution
- Mirrored Networking Mode
- Practical Scenarios and Troubleshooting
WSL 2 runs a real Linux kernel inside a lightweight Hyper-V virtual machine. This has profound implications for networking:
┌─────────────────────────────────────────────────────────────────────────────┐
│ Physical Hardware │
│ (Your PC's Network Interface) │
│ Physical IP: Assigned by Router │
└─────────────────────────────────────────────┬───────────────────────────────┘
│
┌─────────────────────────────────────────────▼───────────────────────────────┐
│ Hyper-V Virtual Switch │
│ (Default: Internal vSwitch) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐│
│ │ Windows Host Partition │ │ WSL 2 Utility VM ││
│ │ │ │ ││
│ │ vEthernet (WSL) Adapter │ │ eth0 Virtual Adapter ││
│ │ IP: 172.28.16.1 │◄──►│ IP: 172.28.21.105 ││
│ │ (Gateway for WSL VM) │ │ Gateway: 172.28.16.1 ││
│ │ │ │ ││
│ │ Physical Adapter │ │ All distros share this VM ││
│ │ IP: 192.168.1.100 │ │ and this single eth0 ││
│ └─────────────────────────────────┘ └─────────────────────────────────┘│
│ │
└──────────────────────────────────────────────────────────────────────────────┘
Critical concept: All WSL 2 distributions run inside a single shared VM. There is only ONE Linux kernel, ONE eth0 adapter, and ONE IP address for all distributions.
# Run this in Ubuntu
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
inet 172.28.21.105/20 brd 172.28.31.255
# Run this in Debian (same result!)
$ ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>
inet 172.28.21.105/20 brd 172.28.31.255NAT (Network Address Translation) is the default networking mode in WSL 2. The Windows host acts as a router/gateway for the WSL VM.
┌──────────────────────────────────────────────────────────────────────────────────┐
│ Internet / LAN │
│ (External Network: 192.168.1.0/24) │
└─────────────────────────────────────────┬────────────────────────────────────────┘
│
│ Your router sees only Windows
│ (192.168.1.100)
▼
┌──────────────────────────────────────────────────────────────────────────────────┐
│ WINDOWS HOST │
│ External IP: 192.168.1.100 │
│ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ Windows NAT Service (WinNAT) │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ NAT Translation Table │ │ │
│ │ ├─────────────────────────────────────────────────────────────────────┤ │ │
│ │ │ Internal IP:Port │ External IP:Port │ Protocol │ State │ │ │
│ │ ├──────────────────────┼─────────────────────┼───────────┼────────────┤ │ │
│ │ │ 172.28.21.105:44321 │ 192.168.1.100:44321│ TCP │ ESTABLISHED│ │ │
│ │ │ 172.28.21.105:53421 │ 192.168.1.100:53421│ TCP │ ESTABLISHED│ │ │
│ │ │ 172.28.21.105:8080 │ 192.168.1.100:8080 │ TCP │ LISTEN │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ vEthernet (WSL) - Virtual Switch │ │
│ │ IP: 172.28.16.1/20 (Gateway) │ │
│ │ Subnet: 172.28.16.0/20 │ │
│ └──────────────────────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────────────────▼─────────────────────────────────────┐ │
│ │ WSL 2 Virtual Machine │ │
│ │ IP: 172.28.21.105/20 │ │
│ │ Gateway: 172.28.16.1 │ │
│ │ DNS: 172.28.16.1 (Windows) │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
│ │ │ Ubuntu │ │ Debian │ │ Fedora │ │ │
│ │ │ (User Space) │ │ (User Space) │ │ (User Space) │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ nginx :80 │ │ postgres :5432 │ │ redis :6379 │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │
│ │ │ │
│ │ All share: eth0 (172.28.21.105) │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────┘
Step 1: WSL application initiates connection
┌─────────────────────────────────────────────────────────────┐
│ curl https://api.github.com │
│ Source: 172.28.21.105:44321 │
│ Destination: 140.82.121.5:443 │
└─────────────────────────────────────────────────────────────┘
│
▼
Step 2: Packet reaches Windows NAT gateway (172.28.16.1)
┌─────────────────────────────────────────────────────────────┐
│ NAT records the translation: │
│ 172.28.21.105:44321 ←→ 192.168.1.100:44321 │
│ │
│ Rewrites source IP: │
│ Source: 192.168.1.100:44321 │
│ Destination: 140.82.121.5:443 │
└─────────────────────────────────────────────────────────────┘
│
▼
Step 3: Packet sent to internet via physical adapter
┌─────────────────────────────────────────────────────────────┐
│ Router sees: 192.168.1.100:44321 → 140.82.121.5:443 │
│ (WSL's internal IP is completely hidden) │
└─────────────────────────────────────────────────────────────┘
│
▼
Step 4: Response returns to Windows
┌─────────────────────────────────────────────────────────────┐
│ Source: 140.82.121.5:443 │
│ Destination: 192.168.1.100:44321 │
└─────────────────────────────────────────────────────────────┘
│
▼
Step 5: NAT performs reverse translation
┌─────────────────────────────────────────────────────────────┐
│ Looks up: 192.168.1.100:44321 → 172.28.21.105:44321 │
│ │
│ Rewrites destination: │
│ Source: 140.82.121.5:443 │
│ Destination: 172.28.21.105:44321 │
└─────────────────────────────────────────────────────────────┘
│
▼
Step 6: Response delivered to WSL application
┌─────────────────────────────────────────────────────────────┐
│ curl receives response from api.github.com │
└─────────────────────────────────────────────────────────────┘
The NAT subnet changes on every WSL restart:
# Day 1
$ ip addr show eth0
inet 172.28.21.105/20
# After wsl --shutdown and restart
$ ip addr show eth0
inet 172.22.176.42/20 # Different subnet!
# Windows gateway also changes
$ cat /etc/resolv.conf
nameserver 172.22.176.1 # Matches new subnetWhy this matters:
- Scripts that hardcode WSL IP addresses will break
- Firewall rules need dynamic updates
- Use hostname resolution instead of IPs
All WSL 2 distributions share one VM instance:
┌───────────────────────────────────────────────────────────────────────────────┐
│ WSL 2 Lightweight Utility VM │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ Linux Kernel (Microsoft) │ │
│ │ Single Instance │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ Network Stack (Single) │ │
│ │ │ │
│ │ eth0: 172.28.21.105/20 │ │
│ │ All distros see the same network interface │ │
│ │ All distros share the same IP address │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ ┌─────────────┐ │
│ │ Ubuntu │ │ Debian │ │ Fedora │ │ Alpine │ │
│ │ │ │ │ │ │ │ │ │
│ │ /home/user │ │ /home/user │ │ /home/user │ │ /home/user │ │
│ │ (ext4 vhdx) │ │ (ext4 vhdx) │ │ (ext4 vhdx) │ │ (ext4 vhdx)│ │
│ │ │ │ │ │ │ │ │ │
│ │ Port 3000 │ │ Port 5432 │ │ Port 6379 │ │ Port 8080 │ │
│ │ (Node.js) │ │ (PostgreSQL) │ │ (Redis) │ │ (nginx) │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ └─────────────┘ │
│ │
│ ▲ ▲ ▲ ▲ │
│ │ │ │ │ │
│ └────────────────────┴───────────────────┴──────────────────┘ │
│ All bind to eth0 (172.28.21.105) │
│ All accessible via localhost on Windows │
└───────────────────────────────────────────────────────────────────────────────┘
Since all distros share one IP, port conflicts happen at the kernel level:
# Terminal 1: Ubuntu - Start nginx on port 80
ubuntu$ sudo nginx # Binds to 0.0.0.0:80
# Success!
# Terminal 2: Debian - Try to start apache on port 80
debian$ sudo apache2
# ERROR: Address already in use (port 80)
# Why? Both distros share the same eth0 interface
# Port 80 is already bound by Ubuntu's nginx# From any distro, you see ALL listening ports from ALL distros
$ ss -tlnp
State Local Address:Port Process
LISTEN 0.0.0.0:3000 Node.js (Ubuntu)
LISTEN 0.0.0.0:5432 PostgreSQL (Debian)
LISTEN 0.0.0.0:6379 Redis (Fedora)
LISTEN 0.0.0.0:80 nginx (Alpine)Distros can communicate via localhost since they share the network stack:
# Ubuntu is running Node.js on port 3000
# From Debian, you can access it directly:
debian$ curl http://localhost:3000
# Works! Same network namespace
# Or using the shared IP:
debian$ curl http://172.28.21.105:3000
# Also works!┌─────────────────────────────────────────────────────────────────────┐
│ What IS Isolated │
├─────────────────────────────────────────────────────────────────────┤
│ ✓ Filesystem (each distro has its own ext4 virtual disk) │
│ ✓ User accounts (separate /etc/passwd per distro) │
│ ✓ Installed packages (apt vs dnf vs apk) │
│ ✓ Process namespace (ps shows only that distro's processes) │
│ ✓ Init system (each distro has its own init) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ What is SHARED │
├─────────────────────────────────────────────────────────────────────┤
│ ✗ Linux Kernel (one kernel for all distros) │
│ ✗ Network interface (single eth0) │
│ ✗ IP Address (one IP: 172.28.x.x) │
│ ✗ Port space (port 80 can only be used by one service) │
│ ✗ Network namespace │
│ ✗ Memory (VM's allocated RAM is shared) │
└─────────────────────────────────────────────────────────────────────┘
This is one of WSL 2's most elegant features. When you run a server in WSL, it's automatically accessible on localhost from Windows.
┌──────────────────────────────────────────────────────────────────────────────┐
│ WINDOWS HOST │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Windows Application │ │
│ │ (Browser: http://localhost:3000) │ │
│ └───────────────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ WSL Localhost Forwarding Service │ │
│ │ (wslhost.exe) │ │
│ │ │ │
│ │ Monitors: │ │
│ │ • Listens for connections to localhost:* on Windows │ │
│ │ • Detects listening sockets inside WSL VM │ │
│ │ • Automatically creates port forwards │ │
│ │ │ │
│ │ Active Forwards: │ │
│ │ ┌────────────────────────────────────────────────────────────┐ │ │
│ │ │ localhost:3000 (Win) ──────► 172.28.21.105:3000 (WSL) │ │ │
│ │ │ localhost:5432 (Win) ──────► 172.28.21.105:5432 (WSL) │ │ │
│ │ │ localhost:8080 (Win) ──────► 172.28.21.105:8080 (WSL) │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────────▼─────────────────────────────────┐ │
│ │ vEthernet (WSL) │ │
│ │ 172.28.16.1 │ │
│ └───────────────────────────────────┬─────────────────────────────────┘ │
│ │ │
└───────────────────────────────────────┼───────────────────────────────────────┘
│
┌───────────────────────────────────────▼───────────────────────────────────────┐
│ WSL 2 VM │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ eth0: 172.28.21.105 │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Node.js │ │ PostgreSQL │ │ nginx │ │ │
│ │ │ :3000 │ │ :5432 │ │ :8080 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────┘
1. WSL Application Binds to Port
┌─────────────────────────────────────────────────────────────┐
│ # In Ubuntu WSL │
│ $ node server.js │
│ Server listening on port 3000 │
│ │
│ Kernel: Socket bound to 0.0.0.0:3000 on eth0 │
└─────────────────────────────────────────────────────────────┘
│
▼
2. WSL Notifies Windows of New Listening Port
┌─────────────────────────────────────────────────────────────┐
│ wslhost.exe detects new listening socket via: │
│ • Hyper-V socket communication │
│ • Periodic polling of /proc/net/tcp │
│ │
│ Registers: "Port 3000 is now listening in WSL" │
└─────────────────────────────────────────────────────────────┘
│
▼
3. Windows Creates Localhost Listener
┌─────────────────────────────────────────────────────────────┐
│ Windows binds to localhost:3000 │
│ (Only 127.0.0.1, not 0.0.0.0!) │
│ │
│ $ netstat -an | findstr 3000 │
│ TCP 127.0.0.1:3000 0.0.0.0:0 LISTENING │
└─────────────────────────────────────────────────────────────┘
│
▼
4. Browser Connects to localhost:3000
┌─────────────────────────────────────────────────────────────┐
│ Browser → localhost:3000 │
│ → wslhost.exe intercepts │
│ → forwards to 172.28.21.105:3000 │
│ → Node.js receives request │
│ → Response follows reverse path │
└─────────────────────────────────────────────────────────────┘
# Binding to 0.0.0.0 (all interfaces) - WORKS with localhost forwarding
$ python -m http.server 8000 --bind 0.0.0.0
# Accessible from Windows at http://localhost:8000 ✓
# Binding to 127.0.0.1 (loopback only) - DOES NOT forward
$ python -m http.server 8000 --bind 127.0.0.1
# NOT accessible from Windows! ✗
# Because the service only listens on WSL's internal loopbackWhat happens when a WSL application tries to use a port that Windows is already using?
Scenario: SQL Server running on Windows port 1433
PostgreSQL wants to run on WSL port 1433
┌──────────────────────────────────────────────────────────────────────────────┐
│ WINDOWS HOST │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ SQL Server (Windows) │ │
│ │ Bound to: 0.0.0.0:1433 │ │
│ │ (Occupies the port on Windows) │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ WSL Localhost Forwarding Service │ │
│ │ │ │
│ │ Port 1433 forward BLOCKED! │ │
│ │ Cannot bind localhost:1433 - already in use by SQL Server │ │
│ │ │ │
│ │ Result: No automatic forwarding created for port 1433 │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────────────┐
│ WSL 2 VM │
│ │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL │ │
│ │ Bound to: 0.0.0.0:1433 │ │
│ │ (Works fine inside WSL!) │ │
│ │ │ │
│ │ The port binds successfully in WSL because │ │
│ │ WSL has its own network namespace. │ │
│ │ Windows and WSL port spaces are SEPARATE. │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────────────┘
# Windows: SQL Server on port 1433
PS> netstat -an | findstr 1433
TCP 0.0.0.0:1433 0.0.0.0:0 LISTENING
# WSL: PostgreSQL on port 1433
$ sudo -u postgres psql -c "SHOW port"
port
------
1433
# PostgreSQL is running fine in WSL!
# But from Windows:
PS> psql -h localhost -p 1433
# Connects to SQL Server, NOT PostgreSQL!
# Because Windows localhost:1433 goes to SQL ServerOption 1: Use WSL's IP directly (bypasses localhost forwarding)
# Get WSL IP
PS> wsl hostname -I
172.28.21.105
# Connect directly to WSL's PostgreSQL
PS> psql -h 172.28.21.105 -p 1433
# Now connects to PostgreSQL in WSL!Option 2: Use different port in WSL
# Change PostgreSQL to use port 5433 instead
$ sudo sed -i 's/port = 1433/port = 5433/' /etc/postgresql/14/main/postgresql.conf
$ sudo systemctl restart postgresql
# Now accessible via localhost:5433 from WindowsOption 3: Manual port forwarding with different port
# Forward Windows port 5433 to WSL port 1433
PS> netsh interface portproxy add v4tov4 `
listenport=5433 listenaddress=127.0.0.1 `
connectport=1433 connectaddress=172.28.21.105
# Access PostgreSQL via localhost:5433
PS> psql -h localhost -p 5433# Check what's using a port on Windows
PS> netstat -ano | findstr :3000
TCP 0.0.0.0:3000 0.0.0.0:0 LISTENING 1234
# Find the process
PS> Get-Process -Id 1234
Handles NPM(K) PM(K) CPU(s) Id ProcessName
------- ------ ----- ------ -- -----------
500 50 100000 10.50 1234 node# Check what's using a port in WSL
$ ss -tlnp | grep :3000
LISTEN 0 511 0.0.0.0:3000 * users:(("node",pid=1234,fd=18))
$ ps aux | grep 1234
user 1234 0.5 1.0 node /home/user/app/server.js| WSL Port | Windows Port | localhost Access | Direct IP Access |
|---|---|---|---|
| 3000 (free) | Free | ✅ WSL | ✅ WSL |
| 3000 (free) | 3000 (used) | ❌ Windows wins | ✅ WSL |
| 3000 (used) | Free | ✅ WSL | ✅ WSL |
| 3000 (used) | 3000 (used) | ❌ Windows wins | ✅ WSL |
Introduced in Windows 11 22H2, mirrored networking eliminates the NAT layer entirely. WSL uses the same network interfaces as Windows.
Create or edit %USERPROFILE%\.wslconfig:
[wsl2]
networkingMode=mirroredThen restart WSL:
wsl --shutdown
wsl┌──────────────────────────────────────────────────────────────────────────────┐
│ Physical Network │
│ Router: 192.168.1.1 │
└─────────────────────────────────────┬────────────────────────────────────────┘
│
│
┌─────────────────────────────────────▼────────────────────────────────────────┐
│ WINDOWS HOST │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Physical Network Adapter │ │
│ │ IP: 192.168.1.100 │ │
│ │ (Visible to router) │ │
│ └─────────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ │ MIRRORED (not NAT!) │
│ │ │
│ ┌─────────────────────────────────▼───────────────────────────────────┐ │
│ │ WSL 2 VM │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ eth0 (Mirrored) │ │ │
│ │ │ IP: 192.168.1.100 (SAME as Windows!) │ │ │
│ │ │ │ │ │
│ │ │ WSL sees the exact same network as Windows: │ │ │
│ │ │ • Same IP address │ │ │
│ │ │ • Same subnet │ │ │
│ │ │ • Same gateway │ │ │
│ │ │ • Same DNS servers │ │ │
│ │ │ • Same routing table │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Node.js │ │ PostgreSQL │ │ nginx │ │ │
│ │ │ :3000 │ │ :5432 │ │ :80 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────────┘
Network sees one device: 192.168.1.100
Both Windows and WSL answer on this IP
| Feature | NAT Mode | Mirrored Mode |
|---|---|---|
| WSL IP Address | 172.x.x.x (private) | Same as Windows (e.g., 192.168.1.100) |
| IP Changes on Restart | Yes | No |
| Localhost Forwarding | Via wslhost.exe | Native (same loopback) |
| External Access to WSL | Requires port forwarding | Direct (same IP) |
| IPv6 Support | Limited | Full |
| VPN Compatibility | Often problematic | Better |
| Multicast/mDNS | Not supported | Supported |
| Network Discovery | WSL invisible | WSL visible |
# In mirrored mode, localhost truly means the same thing
# in both Windows and WSL
# WSL
$ curl http://localhost:3000 # Reaches service on either Win or WSL
# Windows
PS> curl http://localhost:3000 # Reaches service on either Win or WSL┌─────────────────────────────────────────────────────────────────────────────┐
│ Mirrored Mode Port Binding │
│ │
│ Scenario: Both Windows and WSL try to bind port 3000 │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ Windows: IIS binds to 0.0.0.0:3000 │ │
│ │ Result: SUCCESS │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ WSL: Node.js tries to bind to 0.0.0.0:3000 │ │
│ │ Result: EADDRINUSE - Address already in use │ │
│ │ │ │
│ │ In mirrored mode, WSL CANNOT bind to a port Windows is using! │ │
│ │ (Unlike NAT mode where they have separate port spaces) │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
NAT Mode: External device cannot reach WSL directly
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Other PC │───X───►│ Windows │───────►│ WSL │
│ 192.168.1.50 │ │ 192.168.1.100 │ │ 172.28.21.105 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
Need port forwarding!
Mirrored Mode: External device reaches WSL directly
┌─────────────────┐ ┌─────────────────────────────────────────────┐
│ Other PC │───────►│ Windows + WSL (same IP: 192.168.1.100) │
│ 192.168.1.50 │ │ Both answer on this address │
└─────────────────┘ └─────────────────────────────────────────────┘
Direct access works!
Full .wslconfig example:
[wsl2]
networkingMode=mirrored
# Firewall settings
firewall=true
# DNS settings
dnsTunneling=true
# Hyper-V firewall rules apply to WSL
autoProxy=true
# Allow WSL to use Windows proxy settings
[experimental]
# Ignore specific ports from mirroring (WSL gets separate port space for these)
ignoredPorts=3000,3001,8080
# Host address loopback - allows binding to host IP from WSL
hostAddressLoopback=trueUse Mirrored Mode When:
- ✅ You need WSL accessible from other devices on your network
- ✅ You're using VPNs (better compatibility)
- ✅ You need IPv6 support
- ✅ You want consistent IP addresses (no more dynamic 172.x.x.x)
- ✅ You need multicast/mDNS (Bonjour, etc.)
Stick with NAT Mode When:
- ✅ You need separate port spaces (same port in Windows and WSL)
- ✅ You want WSL network isolated from external devices
- ✅ You're on Windows 10 (mirrored not available)
- ✅ You have complex network setups that break with mirroring
# React app with webpack-dev-server
$ cd ~/projects/my-react-app
$ npm start
# Starts on port 3000
# Automatic localhost forwarding means:
# http://localhost:3000 works in Windows browser
# Hot reload WebSocket also forwards automatically┌─────────────────────────────────────────────────────────────────┐
│ Ubuntu: Running Node.js API on port 3000 │
│ Debian: Running PostgreSQL on port 5432 │
│ │
│ Node.js connects to PostgreSQL via localhost:5432 │
│ (Works because they share the same network namespace) │
│ │
│ Windows browser accesses API at localhost:3000 │
│ (Automatic localhost forwarding) │
└─────────────────────────────────────────────────────────────────┘
# Get current WSL IP
$wslIP = (wsl hostname -I).Trim()
# Forward external port 8080 to WSL port 3000
netsh interface portproxy add v4tov4 `
listenport=8080 `
listenaddress=0.0.0.0 `
connectport=3000 `
connectaddress=$wslIP
# Allow through Windows Firewall
New-NetFirewallRule -DisplayName "WSL Port Forward" `
-Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow
# Now other devices can access: http://192.168.1.100:8080Important: WSL IP changes on restart. Create a script to update:
# update-wsl-forward.ps1
$wslIP = (wsl hostname -I).Trim()
netsh interface portproxy delete v4tov4 listenport=8080 listenaddress=0.0.0.0
netsh interface portproxy add v4tov4 `
listenport=8080 listenaddress=0.0.0.0 `
connectport=3000 connectaddress=$wslIP
Write-Host "Updated forwarding to $wslIP"# Check WSL network configuration
$ ip addr show
$ ip route show
$ cat /etc/resolv.conf
# Test connectivity to Windows host
$ ping $(cat /etc/resolv.conf | grep nameserver | awk '{print $2}')
# Check listening ports in WSL
$ ss -tlnp
# Test if port forwarding is working
$ curl -v http://localhost:3000# Check Windows network adapters
Get-NetAdapter | Where-Object {$_.Name -like "*WSL*"}
# Check port forwarding rules
netsh interface portproxy show all
# Check what's listening on Windows
netstat -ano | findstr LISTENING
# Reset WSL network
wsl --shutdown
Restart-Service LxssManager
# Check WSL network mode
wsl cat /proc/sys/net/ipv4/ip_forward| Issue | Cause | Solution |
|---|---|---|
| localhost not working | Service bound to 127.0.0.1 | Bind to 0.0.0.0 |
| Port works in WSL, not Windows | Windows firewall | Add firewall rule |
| WSL can't reach internet | DNS issues | Check /etc/resolv.conf |
| Slow network performance | VPN interference | Try mirrored mode |
| Port forward stopped working | WSL IP changed | Update port proxy rules |
# WSL Commands
ip addr show eth0 # Show WSL IP
cat /etc/resolv.conf # Show Windows host IP (DNS)
ss -tlnp # Show listening ports
curl http://localhost:3000 # Test local service
ping $(hostname -I | awk '{print $1}') # Test own network
# PowerShell Commands
wsl hostname -I # Get WSL IP from Windows
netstat -ano | findstr :3000 # Check port usage
netsh interface portproxy show all # List port forwards
Get-NetAdapter *WSL* # Show WSL adapter┌─────────────────────────────────────────────────────────────────────────┐
│ NAT Mode (Default) │
├─────────────────────────────────────────────────────────────────────────┤
│ WSL IP: 172.x.x.x (dynamic, changes on restart) │
│ Windows IP: Your network IP (e.g., 192.168.1.100) │
│ Port Space: Separate (same port can be used by both) │
│ localhost: Forwarded via wslhost.exe │
│ External: Requires manual port forwarding │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ Mirrored Mode │
├─────────────────────────────────────────────────────────────────────────┤
│ WSL IP: Same as Windows (e.g., 192.168.1.100) │
│ Windows IP: Same as WSL │
│ Port Space: Shared (port conflict = error) │
│ localhost: Native sharing (same loopback) │
│ External: Direct access (same IP) │
└─────────────────────────────────────────────────────────────────────────┘