Networking Considerations in WSL

July 18, 2025 5 min read Kevin Davis
#WSL #networking

Background

While trying to call a web API running in Windows Subsystem for Linux (WSL), I struggled with intermittent connection timeouts. A quick web search didn’t turn up much, and Chat GTP’s best suggestion was to reboot the PC whenever the problem occurs. Fortunately there is a better solution.

Simplifying the Problem

Because my web API is built on the ASP.NET Core stack, I wanted to simplify the scenario to first rule out any problems in my implementation. GPT-4.1 helpfully wrote the following small HTTP server script in Python, which I’ve modified slightly for usability:

import argparse
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse

class SimpleHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        print(f"Received GET request: Path={self.path}, Headers={self.headers}")
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        self.end_headers()
        self.wfile.write(b"Hello from Python HTTP server!\n")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Simple HTTP server for network testing.")
    parser.add_argument('--url', type=str, default='http://0.0.0.0:8080',
                        help='URL to bind the server to, e.g., http://127.0.0.1:9000')
    args = parser.parse_args()
    parsed = urlparse(args.url)
    host = parsed.hostname or "0.0.0.0"
    port = parsed.port or 8080
    server_address = (host, port)
    httpd = HTTPServer(server_address, SimpleHandler)
    print(f"Serving HTTP on {host}:{port} ...")
    httpd.serve_forever()

This script simply listens for HTTP GET requests, logs them to the console, and sends a “hello” response to the caller.

Launching it to listen on localhost and calling it with curl results in a connection timeout after a few minutes. The top terminal is the server, the bottom is the client: Screenshot of curl connection timeout in WSL.

Wait a minute. Where is “port 9000” coming from? I clearly specified port 5678. No wonder it’s not connecting. Let’s rerun curl with the “-v” option for verbose output:

Curl verbose output showing proxy.

Aha. It’s using a proxy. Indeed, running curl with the “—noproxy” option results in a “Hello from HTTP server!” response message on the client terminal, and a log of the HTTP GET request to the console on the server terminal as expected.

curl --noproxy localhost http://localhost:5678

But where is this http_proxy environment variable coming from? A grep of the .bashrc and .profile files shows nothing.

NAT vs. Mirrored Networking in WSL

According to the WSL networking considerations documentation, there are two networking modes for WSL:

ModeDescription
NAT (default)NAT stands for “Network Address Translation.” This is the default networking mode for WSL. With it, WSL gets its own IP address, which is different than that of the Windows PC host, Windows acts as a sort of a router, translating outgoing and incoming IP addresses to/from the WSL IP as necessary.
MirroredWSL tries to “mirror” the network interfaces used by Windows. (Available on Windows 11 22H2 and higher.)

About a year ago I changed mine to Mirrored mode because I wasn’t able to connect to a server over one of our corporate VPNs otherwise. This solved the problem and I didn’t have time to look into it further, but it seems like it could be the cause of this connectivity issue within WSL.

Mirrored mode can easily be enabled by adding a .wslconfig file to your %USERPROFILE% directory (C:\Users\daviskx16 in my case) with the following lines:

[wsl2]
networkingMode=MIRRORED

After temporarily renaming this file, then shutting down WSL with wsl --shutdown and restarting, it confirmed that it was once again running in the default NAT mode by printing the following message at the top of the terminal:

wsl: A localhost proxy configuration was detected but not mirrored into WSL. WSL in NAT mode does not support localhost proxies.

And indeed, the problem was solved. I was able to curl to the server script without doing any sort of proxy bypass. Since I need to run WSL in Mirrored mode for other things, an acceptable workaround is to implement a proxy bypass in the client code, or simply unset the http_proxy environment variables before running and re-set them later if needed:

unset http_proxy
unset HTTP_PROXY

Answering Remaining Questions

Although this solution is acceptable, it is unsatisfying. The following questions remain:

  1. Why isn’t this corporate proxy also causing host resolution issues in Windows? It has always consistently worked there; the intermittent connection timeouts only happen in WSL.
  2. Why is the connection timeout intermittent in WSL Mirrored mode? For example, wwhen I switched back to Mirrored mode in WSL and tried the curl again for the purpose of this blog post, fully anticipating a connection timeout, it worked just fine even though it went through the proxy:
$ curl -v http://localhost:1234
* Uses proxy env variable http_proxy == 'http://127.0.0.1:9000'
*   Trying 127.0.0.1:9000...
* Connected to (nil) (127.0.0.1) port 9000 (#0)
> GET http://localhost:1234/ HTTP/1.1
...
Hello from Python HTTP server!
* Closing connection 0

To summarize a long conversation with GPT-4.1, I am satisfied with the following answers even though I haven’t taken the time to source them:

  1. Windows doesn’t send localhost requests through the proxy.
  2. In WSL Mirrored mode, the request goes to the proxy (Zscaler, in my case), which then tries to connect to localhost:5678, but from the Windows environment.
    • In Mirrored mode, WSL and Windows share the network stack, so in theory, a server listening on localhost:5678 in WSL should be accessible from Windows as well.
    • However, in practice, there can be timing issues, race conditions, or subtle bugs in Mirrored mode where the port isn’t always immediately or reliably available to Windows after the server starts in WSL.
    • If Zscaler tries to connect before the port is available in Windows, or if there’s a brief desync, you get a timeout. If the port is available, it works.