You ran a service in WSL, hit http://localhost:3000 in a Windows browser, and it worked. You nodded like a person whose life is under control.
Then you rebooted, connected a VPN, or “optimized” something, and localhost started acting like it never met you.
WSL networking is not mysterious. It’s just layered. Windows networking plus a Linux VM (WSL2), plus helper plumbing that tries to make dev workflows feel native.
When all the layers agree, localhost feels magical. When they don’t, you get timeouts, connection refused, or the worst one: it works on your machine and fails on everyone else’s.
The mental model: what “localhost” really means in WSL
“Localhost” is not a place. It’s an interface name that expands to an IP address—usually 127.0.0.1 for IPv4 and ::1 for IPv6.
The key operational detail: 127.0.0.1 always means “this network namespace.” Not “this machine.” Not “this laptop.” Not “whatever I emotionally meant.”
In plain Linux on bare metal, “this network namespace” maps to the host’s network stack. In WSL2, Linux runs inside a lightweight VM with its own network stack.
That means:
- Inside WSL:
localhostis the WSL VM itself. - Inside Windows:
localhostis Windows itself.
The reason it often still works is because Windows adds glue: automatic port forwarding for “localhost-style” access, plus (in newer builds) a mirrored networking mode that narrows the gap.
Your job as an operator is to stop thinking in “machines” and start thinking in stacks:
Who is the client? Which stack is the server bound to? Which interface is the service listening on? Which firewall is in the way? Which NAT or proxy is doing translation?
Interesting facts and history you can weaponize
- WSL1 had no VM. It translated Linux syscalls into NT kernel calls, so networking behaved much closer to “native Windows with a Linux personality.”
- WSL2 switched to a real Linux kernel. Great for compatibility; networking became VM networking (NAT by default), which is where most “why isn’t localhost working” stories are born.
- 127.0.0.1 is specified as loopback. Packets to it never hit the wire; they loop within the stack. That’s why firewalls and routing can behave differently than you expect.
- Hyper-V isn’t just for servers anymore. WSL2 uses Hyper-V components even on laptops, which is why you’ll see a vEthernet adapter and a virtual switch involved.
- Windows added localhost forwarding for WSL2. Windows can listen on a Windows port and forward traffic into the WSL VM so
localhost:port“just works” from Windows apps. - That forwarding is conditional. It can break due to firewall policy, bind addresses, VPN filters, or services listening only on
127.0.0.1inside WSL. - IPv6 is a silent troublemaker. Many tools prefer IPv6 when available. If your service binds only IPv4 and your client tries
::1, you get confusing failures. - DNS in WSL is often auto-generated. WSL can rewrite
/etc/resolv.conf. “Fixing DNS” by hand can work until the next restart, then you get a déjà vu outage. - Mirrored networking mode exists. In some Windows/WSL versions, you can mirror the host network into WSL, reducing NAT weirdness and making inbound access more predictable.
WSL1 vs WSL2: same terminal, different planet
WSL1: syscall translation, no separate network stack
WSL1 processes share the Windows network stack. When a WSL1 process listens on 127.0.0.1:8000, it’s effectively the same loopback as Windows.
This made “localhost” simple, and also made some Linux networking assumptions subtly wrong (because it’s not really Linux).
WSL2: a VM with NAT by default
WSL2 runs a real Linux kernel in a VM. The VM gets its own IP, typically on a private subnet behind a virtual switch.
Windows is the host. Linux is the guest. NAT is the default.
That implies:
- From WSL, outbound access (to the internet or LAN) usually works fine because NAT is easy.
- From Windows to WSL, inbound access is the tricky bit because you’re crossing stacks.
- From your LAN to WSL, inbound access is trickier still because you’re crossing stacks and then crossing NAT.
Traffic paths: Windows → WSL, WSL → Windows, WSL → LAN
Windows client → WSL server
Typical “dev loop”: you run a web server in WSL (python -m http.server), then you browse to it from Windows.
This may work via localhost forwarding or via the WSL VM IP directly.
Operationally, you need to know which one you’re using:
- Using
localhostfrom Windows: you rely on Windows doing port forwarding into WSL. - Using the WSL VM IP: you bypass some magic, but now firewalling and changing IPs become your problem.
WSL client → Windows server
Common: WSL tools talking to Windows services (SQL Server, local proxies, corporate endpoint agents).
WSL can often reach Windows via a special nameserver or the default gateway. Some setups also use host.docker.internal-style names (not guaranteed everywhere).
WSL server → LAN clients
This is where people build “quick demos” that turn into semi-production. You want colleagues on the LAN to hit a service running inside WSL.
Under NAT, you either:
- Use Windows as the ingress and forward traffic into WSL (portproxy or the built-in forwarding behavior), or
- Switch to mirrored mode (if available and suitable), or
- Stop pretending and run the service on a proper VM/host/container platform designed for LAN exposure.
Why localhost works (when it does)
When you type localhost:PORT in a Windows browser and a WSL service answers, you’re usually benefiting from a Windows feature that detects listening ports in WSL
and forwards connections from Windows loopback into the WSL VM.
The happy-path requirements tend to be:
- The service is listening on a WSL interface reachable from Windows (often
0.0.0.0or the WSL VM IP; sometimes127.0.0.1works depending on forwarding behavior and version). - No Windows firewall rule blocks the inbound connection on that port/interface.
- No VPN/filter driver is intercepting or altering loopback/VM traffic in a way that breaks forwarding.
- The client and server agree on IPv4 vs IPv6.
In mirrored mode, the story changes: WSL can share the host network more directly, so binding to 0.0.0.0 in WSL can behave more like “bind to host.”
It’s closer to what people assume they’re getting.
Joke #1: If you ever feel useless, remember there are people binding services to 127.0.0.1 and wondering why the LAN can’t reach them.
Why localhost sometimes doesn’t
Failures cluster into a few categories. Most are self-inflicted. The rest are “your corporate endpoint stack is doing something clever.”
1) The service is bound to the wrong address
Binding to 127.0.0.1 inside WSL means “only within the WSL network namespace.”
If Windows forwarding isn’t active or doesn’t forward that bind, Windows apps won’t reach it.
Binding to 0.0.0.0 (all interfaces) inside WSL is often the right move for cross-boundary access.
2) IPv6 preference turns “localhost” into ::1
Some clients resolve localhost to both ::1 and 127.0.0.1 and try IPv6 first.
If your service is IPv4-only, you can see instant “connection refused” even though IPv4 would work.
3) Windows Firewall (or corporate endpoint controls) blocks it
A lot of engineers treat loopback as “firewall-free.” That’s mostly true on a single stack. But WSL2 traffic may traverse virtual interfaces where firewall profiles apply.
Domain policies can also tighten loopback and local port exposure in surprising ways.
4) VPN clients and filter drivers break routing/forwarding
VPNs may:
- Override DNS and split-tunnel routes in ways WSL doesn’t automatically follow.
- Install filters that interfere with Hyper-V virtual switch traffic.
- Disable “local LAN access” behaviors or treat vEthernet adapters as untrusted.
5) The WSL IP changed
WSL2 VM IPs can change across restarts. If you hardcode the VM IP in a Windows client, you’re building a time bomb.
Prefer localhost forwarding (when reliable), mirrored mode, or stable Windows-side port forwarding rules.
6) You “optimized” the network stack
Disabling services, tweaking resolv.conf generation, messing with iptables/nftables, or changing WSL config can all produce locally consistent but globally broken behavior.
Debugging it later feels like reading a novel where the villain is your past self.
Fast diagnosis playbook
The goal is not “understand everything.” The goal is to find the bottleneck in under five minutes, with receipts.
First: identify which stack is failing
- From inside WSL: can you reach the service via WSL localhost?
- From Windows: can you reach the service via Windows localhost?
- From Windows: can you reach the service via the WSL VM IP?
This splits the problem into “service isn’t listening” vs “forwarding/firewall/NAT problem.”
Second: confirm the bind address and listener
- Check what address/port the server is bound to in WSL (
ssoutput). - Check whether Windows is listening/forwarding on that port (Windows
netstatoutput).
Third: check the two firewalls and the VPN
- WSL firewall rules (iptables/nftables) if you run them.
- Windows Firewall profile/rules for the port and interface.
- VPN connected? If yes, test with it off (or on a different network) to isolate.
Fourth: DNS only after connectivity
Don’t start with DNS unless the symptom is a name failing. If IP connectivity fails, DNS is irrelevant.
Hands-on tasks: commands, outputs, and the decision you make
The commands below are designed to be runnable and diagnostic. Each includes what the output means and the decision you make from it.
Mix and match. This is SRE work: measure, don’t vibe.
Task 1: Confirm whether you’re on WSL1 or WSL2
cr0x@server:~$ uname -r
5.15.133.1-microsoft-standard-WSL2
Meaning: If the kernel string includes microsoft-standard-WSL2, you’re in WSL2 with VM networking.
Decision: Assume NAT/forwarding behaviors apply. Don’t treat localhost as shared with Windows without verifying.
Task 2: Find the WSL IP address
cr0x@server:~$ ip -brief addr show eth0
eth0 UP 172.24.85.101/20 fe80::215:5dff:fe12:3456/64
Meaning: WSL’s eth0 has a private IPv4 address. This is the address Windows may use to reach WSL directly.
Decision: Use this IP for a direct test from Windows. Do not hardcode it for long-term workflows unless you accept churn.
Task 3: Verify the service is actually listening (and where)
cr0x@server:~$ ss -ltnp | head
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=2123,fd=19))
Meaning: The service listens only on 127.0.0.1:3000 inside WSL. That’s loopback-only within WSL’s network namespace.
Decision: If Windows can’t reach it, reconfigure the service to bind to 0.0.0.0 (or to the WSL IP) when cross-stack access is required.
Task 4: Rebind a dev server to all interfaces (example)
cr0x@server:~$ python3 -m http.server 8000 --bind 0.0.0.0
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
Meaning: The server is listening on all IPv4 interfaces, not just loopback.
Decision: Now test from Windows using WSL IP and (if applicable) Windows localhost forwarding.
Task 5: Test from inside WSL using localhost
cr0x@server:~$ curl -sS -D- http://127.0.0.1:8000/ | head
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.10.12
Date: Tue, 06 Feb 2026 18:22:11 GMT
Content-type: text/html; charset=utf-8
Content-Length: 1093
Meaning: The service works inside WSL. The app is fine.
Decision: If Windows can’t reach it, focus on the boundary: forwarding, firewall, VPN, IPv6.
Task 6: Test from inside WSL to Windows via the default gateway
cr0x@server:~$ ip route | head -n 3
default via 172.24.80.1 dev eth0
172.24.80.0/20 dev eth0 proto kernel scope link src 172.24.85.101
Meaning: The default gateway (often the host-side virtual interface) is 172.24.80.1.
Decision: When WSL needs to reach a Windows service, this gateway IP is a good first attempt.
Task 7: Check DNS configuration inside WSL
cr0x@server:~$ cat /etc/resolv.conf
nameserver 172.24.80.1
search corp.example
Meaning: WSL is pointing DNS at a host-provided resolver. VPN changes can affect whether that resolver can reach corporate DNS.
Decision: If name lookups fail but IP connectivity works, fix DNS (or the VPN integration). Don’t touch it if connectivity itself is broken.
Task 8: Validate name resolution quickly
cr0x@server:~$ getent hosts github.com | head -n 1
140.82.121.4 github.com
Meaning: DNS resolution works at least for this query.
Decision: If your issue is “can’t reach service by name,” try the service IP next. If this fails, focus on DNS/VPN/resolv.conf behavior.
Task 9: Check whether WSL can reach the LAN (routing and NAT sanity)
cr0x@server:~$ ping -c 2 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=12.4 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=11.9 ms
--- 8.8.8.8 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
Meaning: Outbound connectivity works. NAT and routing are not totally broken.
Decision: If Windows-to-WSL inbound fails while outbound works, you’re likely dealing with firewall/forwarding/bind issues.
Task 10: Confirm Windows-side listener state from inside WSL (quick probe)
cr0x@server:~$ nc -vz 172.24.80.1 445
Connection to 172.24.80.1 445 port [tcp/microsoft-ds] succeeded!
Meaning: You can reach a Windows service (SMB in this example) at the gateway IP.
Decision: If WSL can’t reach Windows services at all, suspect VPN/firewall policies on the host or broken virtual switch.
Task 11: Check WSL firewall rules (iptables) if you use them
cr0x@server:~$ sudo iptables -S | head
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
Meaning: Default ACCEPT policies; WSL itself is not blocking inbound.
Decision: If policies are DROP/REJECT, fix them or temporarily flush rules while debugging. If ACCEPT, move on to Windows firewall/forwarding.
Task 12: Check for IPv6-only or IPv4-only listeners
cr0x@server:~$ ss -ltn | grep -E ':3000|:8000'
LISTEN 0 4096 0.0.0.0:8000 0.0.0.0:*
LISTEN 0 4096 [::1]:3000 [::]:*
Meaning: Port 8000 listens on IPv4 all interfaces; port 3000 listens only on IPv6 loopback.
Decision: If a Windows client tries IPv4 and fails, enable IPv4 listening or test explicitly with IPv6 where appropriate.
Task 13: Inspect Windows-to-WSL forwarding using the WSL IP test (from WSL’s perspective)
cr0x@server:~$ curl -sS -D- http://172.24.85.101:8000/ | head
HTTP/1.0 200 OK
Server: SimpleHTTP/0.6 Python/3.10.12
Date: Tue, 06 Feb 2026 18:24:51 GMT
Content-type: text/html; charset=utf-8
Content-Length: 1093
Meaning: The service responds on the WSL VM IP. That’s good for Windows direct-IP access and for any forwarding mechanism.
Decision: If Windows localhost:8000 fails but WSL IP works from Windows, the issue is localhost forwarding specifically.
Task 14: Catch the “works on WSL localhost but not on eth0” mistake
cr0x@server:~$ curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/
200
cr0x@server:~$ curl -sS -o /dev/null -w '%{http_code}\n' http://172.24.85.101:3000/
curl: (7) Failed to connect to 172.24.85.101 port 3000 after 0 ms: Connection refused
Meaning: The service is listening only on loopback. That’s why connecting via the VM IP fails.
Decision: Reconfigure the service bind address. Don’t “fix” firewalls for a service that isn’t listening where you think it is.
Task 15: Identify the interface and MTU (VPN-related weirdness)
cr0x@server:~$ ip link show eth0 | sed -n '1,2p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 00:15:5d:12:34:56 brd ff:ff:ff:ff:ff:ff
Meaning: MTU is 1500. If a corporate VPN clamps MTU and WSL doesn’t match, you can get “some sites work, some hang” behavior.
Decision: If you suspect MTU black holes, test with smaller packet sizes (or adjust MTU) rather than randomly changing DNS.
Task 16: Quick packet-path sanity with traceroute
cr0x@server:~$ traceroute -n -m 3 1.1.1.1
traceroute to 1.1.1.1 (1.1.1.1), 3 hops max, 60 byte packets
1 172.24.80.1 0.295 ms 0.260 ms 0.250 ms
2 192.168.1.1 1.987 ms 1.912 ms 1.901 ms
3 1.1.1.1 9.812 ms 9.766 ms 9.742 ms
Meaning: You see the host gateway, then the LAN gateway, then the destination. That’s expected for NAT-style connectivity.
Decision: If hop 1 fails, your WSL virtual network is busted. If hop 2 fails only on VPN, your VPN routing/policy is the culprit.
Three corporate mini-stories from the trenches
Mini-story 1: The incident caused by a wrong assumption
A product team built an internal demo environment on Windows laptops because procurement was slow and the deadline was not.
The stack was a small API in WSL2, a Windows desktop client, and a browser-based admin UI. It worked beautifully in the office.
Then the team went on-site to a customer. The laptop joined a different network, the corporate VPN came on, and the admin UI died.
The API was still running. curl inside WSL returned 200s. But the Windows browser got timeouts to localhost.
The wrong assumption: “localhost is always local.” They assumed that because Windows could reach WSL via localhost in one environment, it was a stable contract.
In reality, they were relying on forwarding behavior that a VPN filter driver partially broke.
The fix wasn’t fancy. They changed the dev server to bind to 0.0.0.0, validated access via the WSL IP, and added a Windows-side portproxy rule for the demo ports.
They also documented a fallback: if VPN is required, use mirrored mode where supported or keep the browser inside WSL (Linux browser) to avoid crossing the stack boundary.
The real lesson: if your system depends on a convenience feature, treat it like any other dependency. Test it under VPN, firewall policy, and network changes—or it will test you.
Mini-story 2: The optimization that backfired
Another team had slow dependency downloads in WSL. Someone “optimized DNS” by hardcoding /etc/resolv.conf to a public resolver and disabling auto-generation.
Downloads got faster. Everyone celebrated and moved on. That’s how you know it’s going to hurt later.
Two weeks later, engineers couldn’t resolve internal hostnames from WSL while connected to the corporate VPN.
Windows could resolve them fine. WSL could reach IPs fine. But anything that needed internal.service.corp failed.
The optimization backfired because WSL was now bypassing the host/VPN-provided DNS path. The VPN expected internal names to resolve through corporate resolvers.
Windows obeyed. WSL ignored it. The result was a fleet of laptops where internal services “randomly” failed.
The rollback was simple: restore WSL’s DNS auto-generation and teach people to measure rather than cargo-cult.
For performance, they fixed the real problem: caching, artifact mirrors, and moving repeated downloads out of the hot path.
Joke #2: Nothing is more permanent than a temporary DNS tweak that “just for today” made things faster.
Mini-story 3: The boring but correct practice that saved the day
A platform team standardized local dev for a large repo. They didn’t fight WSL; they wrote a tiny “network sanity” script and made it part of onboarding.
It checked listener binds, printed the WSL IP, validated that Windows could hit key ports, and warned about IPv6-only listeners.
Months later, a Windows update rolled through and changed some behavior around virtualization and firewall profiles.
Half the org started reporting “the dev API is down” because localhost from Windows stopped reaching WSL for some people.
The other half had no issue, because of course it was inconsistent.
The sanity script turned chaos into triage. Engineers could answer, quickly and consistently:
“The service is listening on WSL loopback only.” Or “Windows firewall is blocking the forwarded port.” Or “Your VPN client is intercepting the vEthernet adapter.”
The platform team didn’t need heroics. They needed comparability: same checks, same outputs, same next decisions.
The fix was rolled out as a config change (bind address defaults, plus documented portproxy fallback) and a one-time Windows firewall rule for dev ports.
“Boring but correct” scales. “Works on my machine” is not a strategy; it’s a confession.
Common mistakes: symptoms → root cause → fix
1) Symptom: Windows browser can’t reach WSL service at localhost, but WSL curl works
- Root cause: Service bound to
127.0.0.1inside WSL, and Windows-to-WSL localhost forwarding isn’t handling it (or is blocked). - Fix: Bind to
0.0.0.0(or WSLeth0IP). Retest via WSL IP from Windows. If you need localhost, set up Windows-side port forwarding (and firewall rule) explicitly.
2) Symptom: curl http://localhost:PORT fails on Windows, but curl http://WSL_IP:PORT works
- Root cause: Localhost forwarding path is broken (Windows update, firewall, VPN filter driver, or disabled integration behavior).
- Fix: Use WSL IP for local testing, or configure a stable Windows listener that forwards to WSL. If mirrored mode is available and compatible, consider enabling it.
3) Symptom: Works until VPN connects, then WSL loses name resolution
- Root cause: WSL DNS pointing somewhere that can’t resolve internal zones under VPN policy, or
resolv.confcustomization bypassing corporate DNS. - Fix: Restore WSL-managed
/etc/resolv.conf. If policy requires it, use the VPN-provided resolver or configure per-interface DNS in a supported way.
4) Symptom: Random timeouts, especially to large downloads, only on certain networks
- Root cause: MTU mismatch or path MTU discovery issues due to VPN, tunnels, or filtering.
- Fix: Test with smaller packets; align MTU settings where appropriate. If you can’t control it, route around it (different network, split tunnel policy, or mirrored mode if it changes the path).
5) Symptom: LAN devices can’t reach a service in WSL, but Windows can
- Root cause: NAT boundary. The WSL VM is not directly reachable from the LAN, and Windows isn’t forwarding the port externally.
- Fix: Expose the port on Windows (firewall rule + port forwarding into WSL). Or stop using WSL for LAN-exposed services and deploy to a real environment.
6) Symptom: “Connection refused” instead of timeout
- Root cause: No listener on that IP:port (wrong bind address, wrong port, IPv6/IPv4 mismatch).
- Fix: Check listeners with
ss -ltnp. Fix the bind. If the listener exists only on IPv6, test[::1]or enable IPv4.
7) Symptom: “Timeout” instead of refused, but service is listening
- Root cause: Firewall drop (Windows Firewall, endpoint protection, VPN policy), or routing path broken.
- Fix: Test from Windows to WSL IP; check firewall rules; temporarily disconnect VPN to isolate. Don’t change the app until you can prove packets reach the VM.
Checklists / step-by-step plan
Checklist A: You want Windows apps to reach a WSL service reliably
- Bind sanely: configure the WSL service to listen on
0.0.0.0(or the WSL IP), not only127.0.0.1. - Prove it locally: from WSL,
curl http://127.0.0.1:PORTandcurl http://WSL_IP:PORT. - Test from Windows using WSL IP: if this fails, you have a firewall or routing problem. Fix that before worrying about localhost magic.
- Decide on your access method:
- Prefer Windows localhost forwarding when it’s stable in your environment.
- If it isn’t stable, use Windows-side explicit forwarding and document it.
- Harden for corporate reality: test with VPN connected and disconnected; test on a guest Wi-Fi network once.
Checklist B: You want LAN devices to reach something running in WSL
- Ask “should we?” If this is more than a demo, deploy it properly.
- If you proceed anyway: expose a port on Windows (inbound firewall rule + forwarding into WSL).
- Pick a stable port: don’t use ephemeral high ports that collide with dev tools.
- Log and monitor: if the service matters, add at least access logs and a health endpoint.
Checklist C: DNS problems under VPN
- Test IP connectivity first. If you can’t ping an IP, DNS is not your main issue.
- Check
/etc/resolv.confgeneration. If it’s manually pinned, expect drift and pain. - Test resolution using
getent. It exercises system resolver behavior, not just a single tool. - Align with policy. If corporate VPN requires corporate DNS, don’t “fix” it with public resolvers and hope.
FAQ
1) Is WSL2 basically a VM? Should I treat it like one?
Yes. It’s a lightweight VM with a real Linux kernel. Treat networking like VM networking: separate stack, NAT by default, and a boundary that needs explicit handling.
2) Why can Windows sometimes reach WSL services via localhost?
Windows can forward connections from Windows loopback into WSL. When it works, it feels like a shared localhost. When it doesn’t, you’re reminded it’s not.
3) Why does binding to 127.0.0.1 inside WSL break access from Windows?
Because 127.0.0.1 is loopback for the WSL network namespace, not Windows. If forwarding isn’t translating that bind, Windows can’t reach it. Bind to 0.0.0.0 for cross-stack access.
4) Should I always bind dev servers to 0.0.0.0 in WSL?
If you need Windows (or LAN) access, yes. If you only need WSL-internal access, bind to loopback for tighter exposure. Decide intentionally, not by habit.
5) Why does it fail only when I connect the VPN?
VPNs change routes, DNS, and sometimes install filter drivers that interfere with virtual adapters. If the failure correlates with VPN, isolate by testing with VPN off and comparing DNS and routes.
6) Why does localhost resolve to IPv6 sometimes and break my service?
Many clients prefer IPv6 and try ::1 first. If your service is IPv4-only, you’ll see failures even though IPv4 would work. Ensure your service listens on both or test explicitly with 127.0.0.1.
7) Can I make WSL’s IP static?
In the default NAT model, the WSL VM IP can change. You can build automation around discovery, use Windows-side forwarding, or use mirrored mode where appropriate. “Static WSL IP” is usually a trap.
8) Why can WSL reach the internet but Windows can’t reach WSL?
NAT makes outbound easy. Inbound requires listeners, correct binds, and firewall rules. Different direction, different rules. Diagnose inbound separately.
9) Is mirrored mode always better?
It can reduce confusion and make inbound access simpler, but it also changes exposure and interacts differently with corporate network policy. Test it in your environment before standardizing.
10) What’s the one operational habit that prevents most WSL networking failures?
Always verify “where am I listening?” using ss -ltnp and always test from both sides (WSL and Windows) before declaring victory.
Conclusion: practical next steps
WSL networking is predictable once you stop anthropomorphizing localhost. In WSL2, you’re crossing a VM boundary.
Sometimes Windows smooths it over. Sometimes the VPN, firewall policy, or an innocent bind address brings reality back.
Next steps that actually hold up in production-adjacent dev:
- Standardize listener binds: for services that must be reachable from Windows, bind to
0.0.0.0in WSL and document it. - Adopt the fast diagnosis playbook: WSL localhost, Windows localhost, WSL IP. Don’t freestyle.
- Test under VPN: if your org uses it, it’s part of your “normal,” not an edge case.
- Prefer boring tooling: a small sanity script that prints listeners, IPs, and DNS state prevents hours of Slack archaeology.
“Hope is not a strategy.” — General Gordon R. Sullivan