Fix WSL2 Traffic Through Clash on Windows: Mirrored Networking and Port Forwarding
If Edge already loads pages through Clash but WSL2 shells, apt, git, or Docker builds stall, you are not imagining a second internet. Windows and the Linux guest simply disagree about where “localhost” lives and which address reaches the host’s HTTP or SOCKS listener. This guide walks the fix in a stable order: recognize NAT versus mirrored networking, aim Ubuntu at the right host IP or loopback, open mixed-port access when needed, set proxy environment variables that real tools honor, and fall back to Windows port forwarding only when the stack still refuses to cooperate.
Why Windows “works” while WSL2 does not
Clash on Windows usually binds a local HTTP and SOCKS port—often surfaced together as a mixed port in modern clients. Browsers and many Win32 programs follow the system proxy or explicit PAC settings, so traffic lands on Clash without you thinking about addresses. Inside WSL2, the default Linux distribution is another machine on a virtual network. Unless you bridge that world deliberately, 127.0.0.1 inside Ubuntu points at Ubuntu itself, not at the Windows loopback where Clash is listening.
That distinction is the entire drama behind “Edge is fine, terminal is not.” Windows applications talk to 127.0.0.1:7890 on the Windows TCP stack. Ubuntu talks to its own loopback device. There is no automatic wormhole between those two unless Microsoft’s networking mode or your own port forwarding creates one. Once you internalize that mental picture, the rest of the article is just mechanics: discover the working address, open the listener, propagate env vars, and only then worry about Clash rules or node quality.
The failure modes look repetitive: curl https://example.com hangs, git clone times out, apt update cannot reach mirrors, Docker Hub pulls never start, yet the same URL opens instantly in a Windows browser. People then crank Clash to “global” or duplicate subscriptions inside Linux—both are expensive detours. The cheaper fix is to give WSL2 a repeatable path to the host listener and to make sure that listener is actually reachable from the guest.
Another subtle trap is assuming that because Clash’s dashboard opens in a browser, the HTTP proxy port is the same integer you remember from an old screenshot. Clients differ: some expose separate HTTP, SOCKS5, and mixed listeners; some remap ports after an update. Always read the active runtime panel rather than a three-year-old blog comment. A five-second sanity check on Windows with Test-NetConnection 127.0.0.1 -Port … in PowerShell saves an hour of Linux-side rabbit holes.
- Mis-aimed loopback: treating
127.0.0.1:7890in WSL2 as if it were Windows. - Closed bind address: Clash only listening on
127.0.0.1on the Windows side while the guest needs a forwarded path. - Missing env vars: shells without
HTTP_PROXYwhile GUI apps on Windows used a different mechanism. - DNS or rule side effects: rare, but if fake-ip expectations disagree, symptoms resemble “nothing resolves”—see the DNS and fake-ip walkthrough after the network path is proven.
NAT WSL2 versus mirrored networking mode
Classic WSL2 uses a lightweight NAT: the guest gets a private address, and Windows acts as the router. In that layout, the Windows host has an IP on the virtual switch that Ubuntu can reach. Many tutorials tell you to read the address from /etc/resolv.conf’s nameserver line; that value is often the host-side resolver stub and has helped generations of developers reach the host—but it is a heuristic, not a promise across every build, firewall, or corporate profile.
Another reliable pattern is to inspect the default route inside the guest: ip -4 route show default often prints a via gateway that corresponds to the host side of the virtual switch. Some distributions also ship hostname.exe helpers or Windows interop paths, but routing output is usually enough when you only need a stable next hop for manual curl tests. Document whichever address wins in a tiny note file; future you will not remember whether last month’s fix depended on mirrored mode or on a resolv.conf trick.
On newer Windows 11 releases, mirrored networking mode (configured in .wslconfig) changes the story: the intent is to align guest and host views so that localhost-style semantics become far closer, reducing the need to chase a moving host IP for everyday tools. Mirrored mode is attractive for Clash users because it shrinks the mental model: if both sides agree on loopback forwarding, fewer bespoke scripts are required. It is not magic—your Clash client must still listen on an interface the guest can reach, and firewalls must allow the path—but it removes an entire class of “wrong IP after reboot” issues.
To try mirrored mode, create or edit %UserProfile%\.wslconfig on Windows with a minimal [wsl2] section that sets networkingMode=mirrored, then run wsl --shutdown from PowerShell or CMD and reopen your distribution. Validate with the same curl probes you used before; do not assume success until traffic actually flows. If your organization pins an older WSL kernel or blocks optional components, mirrored mode may be unavailable—fall back to NAT instructions without shame.
Pick one story and verify it
Do not mix instructions from 2019 NAT posts with 2026 mirrored-mode behavior without testing. Run a single curl probe against both 127.0.0.1 and your discovered host IP on the mixed port; whichever succeeds becomes your canonical address until you change WSL networking mode again.
Clash on Windows: mixed port, bind address, and allow-lan
Before Ubuntu can talk to Clash, Windows must expose the port on a reachable interface. Many beginners leave the HTTP proxy bound to loopback only; that is fine for Edge, insufficient for a guest VM. Open your profile or GUI and confirm the mixed (or separate HTTP and SOCKS) ports. If there is an option to allow LAN access, enable it deliberately—this is the same family of settings discussed in the LAN mixed-port and allow-lan guide, which targets phones and other PCs but applies equally to WSL2’s virtual NIC.
In YAML-oriented clients, the mixed-port field (alongside or instead of discrete port and socks-port) is the usual choke point: verify it matches what you type into HTTP_PROXY. If your UI shows “only bind 127.0.0.1,” translate that mentally to “WSL2 cannot see this listener.” Toggling allow-lan or adjusting bind address is the mechanical fix; the rest of this article assumes you can complete a TCP handshake from Linux to that port on Windows.
Windows Defender Firewall prompts appear when a binary starts listening widely. If you accidentally clicked “block” during first launch, Clash may still work for loopback while WSL2 fails silently. Revisit “Allow an app through firewall” and align the rule with the executable you actually run—third-party skins sometimes ship multiple binaries with similar names. After any change, restart Clash so the socket is recreated with the intended bind.
If you have not finished a baseline Windows install yet, do that first with the Clash for Windows setup article so subscription import, TUN versus system proxy, and logging are already stable. Returning readers can skip reinstallation and focus on the WSL2 boundary alone.
Security note
Opening the mixed port beyond loopback increases exposure on your LAN. On trusted home networks the trade-off is usually acceptable; on public Wi-Fi, tighten firewall rules or revert after debugging.
Reaching the host from Ubuntu: addresses that actually work
After Clash listens on the correct interface, test from WSL2 with a tiny HTTP probe. Start with connectivity, not TLS: an HTTP HEAD or GET to the proxy port should fail quickly with a proxy-shaped error rather than hanging—timeouts usually mean the SYN packet never arrived. Only after raw TCP works should you graduate to curl -x tests through the proxy toward a real origin.
Replace the port with your real mixed or HTTP port:
# Try loopback first when mirrored mode is enabled and forwarding works
curl -I --max-time 5 http://127.0.0.1:7890
# If that fails, try the Windows host IP from the guest’s perspective
# (discover with ip route, resolv.conf nameserver, or `ip -4 route show default`)
curl -I --max-time 5 http://<HOST_IP>:7890
When you find a working tuple, write it down in your shell profile as variables rather than hard-coding dozens of tools. Typical exports look like this (SOCKS example uses all_proxy; adjust scheme and port to match your client):
export HOST_IP=<working-host-ip> # or rely on 127.0.0.1 in mirrored mode
export HTTP_PROXY="http://${HOST_IP}:7890"
export HTTPS_PROXY="http://${HOST_IP}:7890"
export ALL_PROXY="socks5://${HOST_IP}:7891"
export NO_PROXY="localhost,127.0.0.1,::1"
Tools differ: apt honors http_proxy in lower case as well; git reads http.proxy; Docker has its own JSON configuration. The unifying idea is that every stack must aim at the same working host address; otherwise you fix curl and still wonder why builds fail.
When you script this, avoid embedding secrets in public dotfiles if your machine is shared. A common compromise is a ~/.config/proxy.env file with mode 600 that your shell sources only when an explicit alias runs, e.g. proxy_on / proxy_off. That keeps local registry mirrors and internal git remotes on NO_PROXY lists without exporting commercial exit details to every subprocess by default.
Proxy environment variables that survive real workflows
Developers often export variables in ~/.bashrc and call it done, then discover sudo apt ignores them. Remember that sudo sanitizes the environment unless configured otherwise. For one-off diagnostics, prefer sudo -E when appropriate, or configure apt Acquire::http::Proxy in /etc/apt/apt.conf.d/ if you need persistence without touching every shell.
Systemd user services and cron jobs inherit a minimal environment. If you run long-lived workers under systemctl --user, add Environment=HTTP_PROXY=... units or use drop-in fragments. CI-style scripts invoked from Windows via wsl.exe may not load interactive shells at all; pass variables explicitly on the command line or source a dedicated file at the top of the script.
Language-specific tooling adds its own layers: Python’s pip respects HTTP_PROXY but some corporate indexes want trusted custom CAs; Go modules honor HTTPS_PROXY and also respect GOPROXY; Rust’s cargo may need HTTP_PROXY for git-based dependencies. When a tool insists it “does not use the shell proxy,” read its documentation once instead of tunneling everything through SOCKS by default—selective clarity keeps breakages diagnosable.
For git, set:
git config --global http.proxy http://HOST_IP:7890
git config --global https.proxy http://HOST_IP:7890
Clear those entries when you are back on a network that does not need them, or use conditional includes. Keep NO_PROXY broad enough for corporate registries and local Kubernetes endpoints so you do not leak internal names through a commercial exit.
| Tooling | What to set | Common pitfall |
|---|---|---|
| curl / wget | HTTP_PROXY, HTTPS_PROXY |
Using guest loopback when NAT mode is on |
| apt | Lower-case env vars or apt.conf proxy lines | sudo dropping environment |
| git | http.proxy / https.proxy |
SSH remotes bypass HTTP proxy |
| npm / pnpm / yarn | Registry-specific config or env | Corporate SSL inspection needs CA trust |
When to add Windows port forwarding (netsh)
Some combinations of firewall rules, VPN software, or hypervisor updates block direct guest-to-host access even though Clash is running. Instead of fighting opaque filters for a week, you can forward a guest-facing port on Windows into the loopback listener where Clash already sits. The pattern uses netsh interface portproxy to map 0.0.0.0:FORWARD_PORT to 127.0.0.1:CLASH_PORT, plus a one-time firewall rule to admit WSL2 traffic.
Third-party VPN clients sometimes install filters that reorder WSL2 virtual adapter precedence; symptoms include “ping to the host works, TCP to the proxy port does not.” Portproxy sidesteps some of those edge cases by terminating the forward on a Windows-owned listener that the filter already understands. Document the forward ports you choose so they do not collide with another developer tool next week.
Exact commands depend on your interface aliases and security policy; treat online snippets as templates and verify with netsh interface portproxy show all. Remove stale mappings when you upgrade networking modes, or you will chase ghosts on reboot. Portproxy is a scalpel—use it when simpler bind-address fixes fail, not as the first knob.
Docker Desktop and Docker inside WSL2
If you run Docker Desktop with the WSL2 backend, pulls and builds may execute in a utility VM that does not inherit your interactive shell’s exports. Configure the Docker daemon proxy in the JSON settings so engine traffic uses the same host path as your terminal. When Docker runs natively inside WSL2 without Desktop, the daemon still needs explicit proxy settings; the CLI environment alone is not enough for background buildkit workers.
Container base image builds that call apt during Dockerfile steps need ARG HTTP_PROXY patterns or buildkit secrets; otherwise the intermediate layers silently revert to direct connections and fail only on your least favorite base image. Keep a docker build snippet that passes the same HOST_IP you validated from an interactive shell.
Also watch MTU and bridge quirks: a mis-sized MTU looks like “HTTPS works for tiny pages but git lfs hangs,” which sends people to the wrong forum threads. After proxy routing is correct, if large transfers still stall, temporarily compare MTU settings between Windows and WSL2 interfaces before you blame the exit node.
TUN mode and “just proxy everything” on Windows
TUN on Windows can steer Win32 traffic elegantly, but WSL2 packets are not guaranteed to ride the same path unless your overall routing design says so. Some users enable TUN and still need explicit proxy variables inside Linux because the guest default route does not traverse the Windows adapter you expect. Use logs: if Clash sees no flows from WSL2 while TUN is on, you still need the guest-side proxy hop. Conversely, if TUN captures too much and breaks corporate split tunnels, revert to application-level proxying inside WSL2 for stability.
Enterprise Zero Trust clients that insert their own virtual adapters can fight Clash TUN for precedence. The symptom pattern is identical to naive WSL2 misconfiguration—timeouts with no obvious log line—so always isolate variables: disable TUN temporarily, prove HTTP proxy connectivity from WSL2, then re-enable TUN and observe which interface owns the default route on Windows. Your employer’s security stack wins when policies conflict; Clash tuning cannot override contractual MDM restrictions.
DNS inside WSL2 while Clash runs on Windows
Linux in WSL2 resolves names through mechanisms that may or may not pass through Clash’s DNS listeners on Windows. If browsing works on Windows but curl inside Ubuntu reports NXDOMAIN or slow lookups, split the problem: first ensure IP connectivity to the proxy port, then test dig or resolvectl against a public resolver. Some users point WSL2’s /etc/resolv.conf at the Windows host DNS stub; others prefer immutable files and manual nameservers. Align with your fake-ip strategy—if Clash uses fake-ip for domains, clients that bypass Clash DNS may see inconsistent answers. When in doubt, mirror the Windows-side behavior you already validated in the DNS troubleshooting article.
Troubleshooting matrix (symptom → likely cause)
Use this table when you are tired and tempted to reinstall Ubuntu. It keeps you on the network boundary before you touch subscription URLs or node latency.
| What you see | First suspicion | Quick test |
|---|---|---|
curl to proxy port times out from WSL2 |
Listener bound to Windows loopback only, or firewall block | Enable allow-lan / wider bind; retest from Ubuntu with nc -vz HOST PORT |
| TCP connects but HTTPS through proxy fails | Wrong scheme (socks5 vs http) or Clash rules dropping the domain |
curl -v -x http://HOST:PORT https://example.com and read Clash logs |
| Interactive shell works; CI scripts inside WSL2 do not | Non-interactive shells skip .bashrc |
Source a proxy file at script top or export in the scheduler |
| IPv6-only endpoints misbehave | Split IPv4/IPv6 paths through different interfaces | Force -4 in curl; align Clash IPv6 settings with your network |
| Worked yesterday, dead after sleep/resume | Host IP moved or WSL virtual adapter stale | wsl --shutdown; rerun host discovery; check VPN reconnect |
Logging discipline matters: when you open a ticket with your future self, paste the exact export lines, the output of ip route, your Clash listener list, and one redacted curl -v trace. Guessing which layer failed is how people conclude “Clash is broken” when the SYN packet never left the guest.
Practical checklist
- Confirm Clash mixed (or HTTP) port and that Windows browsers work.
- Decide NAT versus mirrored networking; reboot WSL after changing
.wslconfig. - From Ubuntu,
curlthe host using loopback and then discovered host IP until one succeeds. - Enable allow-lan or bind settings so the guest NIC can reach the listener.
- Export consistent
HTTP_PROXY,HTTPS_PROXY,ALL_PROXY, andNO_PROXY; configure apt/git/docker separately where needed. - Optional: add
netsh portproxyand firewall allow rules if direct access is blocked. - If name resolution looks wrong after routing works, revisit DNS and fake-ip behavior.
After the checklist passes, capture a minimal “known good” snippet in your notes: WSL mode (NAT or mirrored), host address form (127.0.0.1 versus dotted quad), ports, and whether portproxy is in play. The next Windows feature update will thank you.
FAQ
Should I use 127.0.0.1 inside WSL2?
Only when mirrored networking (or an equivalent loopback integration) is active and your tests prove the port answers. Otherwise use the host IP from the guest’s routing table or resolver metadata.
curl is fast but apt is still slow
Check sudo environment stripping and apt’s own proxy configuration; also verify mirror geography—some Ubuntu mirrors penalize certain exit ASNs.
It broke after a Windows update
Re-run the curl matrix. Updates sometimes reset Windows Defender Firewall rules or WSL networking flags; re-apply allow-lan and any portproxy entries you rely on.
Keep Windows and WSL2 on the same page
Clash is most pleasant when one mental model covers every workload: a known listener, explicit rules, readable logs, and no duplicate secret configs per subsystem. Once WSL2 reaches that listener reliably, terminals and containers stop feeling like a separate internet.
One proxy, two kernels
Align WSL2 with your Windows Clash listener: mirrored or NAT, mixed-port, env vars, and port forwarding only if needed.
Download Clash