Same laptop, two networks: why Clash WSL2 proxy confuses people
You enabled System Proxy on Windows, Clash Verge Rev or another Mihomo client shows a healthy node, and the browser happily loads sites through your rules. Then you open an Ubuntu shell inside Windows Subsystem for Linux (WSL2), run curl https://api.ipify.org, and the answer is still your residential ISP address. sudo apt update crawls or fails on hosts that only behave when the exit matches your subscription. git clone over HTTPS times out even though the same URL works from PowerShell. Nothing is “broken” in the sense of a crash; you are seeing the normal isolation between the Windows host network stack and the lightweight utility VM that backs each WSL2 distro.
This article targets the search cluster Clash WSL2 proxy, WSL2 not using proxy, and Windows Subsystem for Linux proxy bridge with a reproducible sequence: confirm which address inside Linux actually reaches your host inbound, align Clash allow-lan and Windows Defender Firewall with that path, export http_proxy / https_proxy (and friends) for interactive shells, teach apt and git the same hop, then separate true proxy failures from DNS symptoms that look identical when Fake-IP or split resolvers disagree. Along the way we point to our Windows 11 system proxy troubleshooting guide for the host side of the story, and to LAN proxy, allow-lan, and firewall when you intentionally expose the mixed port beyond loopback.
If you have not installed a current client yet, start from our Clash download page; the steps below assume a working profile and a known mixed or HTTP inbound port on the host, commonly 7890.
Why Windows “system proxy” does not fix curl inside WSL2
On Windows, many GUI programs read WinINET or the per-user proxy profile that Clash toggles when you click Set System Proxy. That path never injects http_proxy into a Linux distribution file system. Inside WSL2, your shell starts with a minimal environment inherited from init and your distro’s login scripts. Unless something explicitly exports proxy variables or wraps tools with a proxy-aware launcher, POSIX utilities default to direct outbound TCP. That is the same conceptual split we described for macOS in our Terminal-focused guide, except the boundary is not Cocoa versus bash—it is NT kernel networking versus Linux guest networking.
Another layer of confusion is the address 127.0.0.1 itself. On the Windows host, pointing a browser at 127.0.0.1:7890 reaches Clash. Inside an older mental model, people paste the same tuple into WSL and expect identical semantics. In WSL2, 127.0.0.1 inside Linux usually refers to the Linux loopback interface, not the Windows loopback where Clash listens. Recent WSL builds ship localhost forwarding that can proxy certain localhost connections toward the host, which means curl -x http://127.0.0.1:7890 sometimes works without extra steps—but the behavior depends on build, .wslconfig, and timing. Treat host IP discovery as the reliable baseline and treat localhost forwarding as a convenience that may or may not be active.
Finally, remember that rule routing still applies after traffic reaches Mihomo. A successful TCP hop to the mixed port does not guarantee that the target domain leaves on your airline node; your YAML might still mark the probe host as DIRECT. When the symptom is “wrong country” rather than “connection refused,” inspect logs before you chase firewall ghosts.
Step 1: Discover the Windows host address from inside WSL2
You need a stable way to print the host-side IPv4 address that the Linux guest should use for outbound TCP toward Clash. Two common patterns work across most installs:
- Default gateway of the eth0 route:
ip route show default | awk '{print $3}'prints the Windows host on many default WSL2 NAT topologies. - Resolver-supplied host IP: WSL often writes the host address into
/etc/resolv.confas thenameserverline. You can parse it withgrep -m1 nameserver /etc/resolv.conf | awk '{print $2}'.
Compare the two numbers. If they differ, prefer the address that actually answers a TCP probe to your mixed port in Step 3. Corporate VPNs, custom vSwitch layouts, or advanced .wslconfig networking sections can rearrange routes; the theme stays the same: use the IP that truly forwards to Windows, not whichever snippet looked popular on a forum last year.
On Windows 11 builds with mirrored networking mode (experimental), localhost semantics may converge further. Document what your machine prints today, not what a generic tutorial assumed in 2021.
Step 2: Make Clash reachable from the guest: mixed port, allow-lan, and firewall
Clash’s mixed inbound often binds to 127.0.0.1 only. That is perfect for local browsers on Windows and insufficient for a WSL2 guest that connects as a separate machine on the virtual switch. Open your client or raw YAML and ensure the mixed (or HTTP) listener either binds to all interfaces or explicitly enables LAN access. In Mihomo terms you typically see allow-lan: true paired with a bind address that includes the host vEthernet IP range, or a UI toggle labeled Allow LAN. Without that, packets from WSL never complete the SYN handshake even if your environment variables are perfect.
Windows Defender Firewall must allow inbound TCP on the chosen port from the WSL virtual network profile. The fastest sanity check is a deliberate allow rule scoped to private networks for the Clash or Mihomo executable, rather than turning the firewall off globally. If you already followed our LAN proxy article, reuse the same mental model: you are exposing a local forwarder to another trust zone on the same PC, not opening the world.
Keep the host story coherent with system proxy troubleshooting on Windows 11: UWP loopback and WinINET quirks still matter for Edge, but WSL2 needs the LAN-visible inbound path in addition—not instead.
allow-lan, restart the Mihomo core once and re-check listening sockets from Windows (netstat -ano or the client’s own status view). Stale listeners bound only to loopback are a frequent copy-paste mistake.
Step 3: Prove TCP connectivity before you touch shell exports
Pick your mixed port number—call it 7890 in examples—and assign the host IP you discovered to HOST_IP. From WSL2, run:
export HOST_IP=$(ip route show default | awk '{print $3}')
nc -vz "$HOST_IP" 7890
If nc is not installed, curl -v --proxy "http://${HOST_IP}:7890" https://example.com is an acceptable stand-in: you want to see the TCP connection established and a HTTP/1.1 200 or equivalent TLS tunnel setup, not an immediate timeout to the host address. Failure here means no amount of http_proxy exports will help yet—return to Step 2, verify the port in YAML, and confirm Windows firewall path.
If 127.0.0.1:7890 works on your build thanks to localhost forwarding but the host IP path fails, trust the failing signal: something in bind scope or firewall still treats non-local sources differently. Fix that mismatch first.
Step 4: Export http_proxy, https_proxy, and ALL_PROXY for interactive shells
Assume HOST_IP and mixed port 7890. For bash or zsh:
export HOST_IP=$(ip route show default | awk '{print $3}')
export http_proxy="http://${HOST_IP}:7890"
export https_proxy="http://${HOST_IP}:7890"
export HTTP_PROXY="$http_proxy"
export HTTPS_PROXY="$https_proxy"
export ALL_PROXY="socks5://${HOST_IP}:7890"
Duplicating uppercase variants catches libraries that only read HTTP_PROXY. The SOCKS line helps tools that prefer SOCKS5 for ancillary protocols. If your profile separates HTTP and SOCKS across two ports, split the URLs instead of blindly copying the snippet.
To clear the session:
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY
These exports affect the current shell and its children. Opening a new tab returns to a clean environment unless you persist—exactly what you want while testing Clash WSL2 proxy behavior.
Step 5: Persist responsibly in ~/.bashrc or ~/.zshrc
After interactive tests succeed, wrap persistence in a function pair so a stopped client does not slow every login. One bash-friendly pattern:
export CLASH_WIN_PORT=7890
wsl_proxy_on() {
export HOST_IP=$(ip route show default | awk '{print $3}')
export http_proxy="http://${HOST_IP}:${CLASH_WIN_PORT}"
export https_proxy="$http_proxy"
export HTTP_PROXY="$http_proxy"
export HTTPS_PROXY="$https_proxy"
export ALL_PROXY="socks5://${HOST_IP}:${CLASH_WIN_PORT}"
echo "WSL proxy env -> ${HOST_IP}:${CLASH_WIN_PORT}"
}
wsl_proxy_off() {
unset HOST_IP http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY
echo "WSL proxy env cleared"
}
Reload with source ~/.bashrc and call wsl_proxy_on when needed. Non-interactive sessions used by automation may still skip these files; see the systemd note below if you need broader coverage.
Step 6: Teach apt about the same hop
apt does not always honor http_proxy for every backend depending on suite and Acquire settings. A conservative approach is a dedicated drop-in under /etc/apt/apt.conf.d:
Acquire::http::Proxy "http://HOST_IP:7890/";
Acquire::https::Proxy "http://HOST_IP:7890/";
Replace HOST_IP with the numeric literal or rebuild the file whenever your virtual switch layout changes. Some teams prefer a tiny script that rewrites the file on each boot from ip route; either way, keep permissions root-owned and avoid world-writable paths.
Step 7: git over HTTPS
Git respects http_proxy for many HTTPS remotes, but explicit configuration is easy to audit:
git config --global http.proxy http://HOST_IP:7890
git config --global https.proxy http://HOST_IP:7890
Remove later with git config --global --unset http.proxy and the HTTPS counterpart. SSH remotes ([email protected]:...) ignore HTTP proxies unless you tunnel SSH separately; this section targets HTTPS clones and fetches, which mirror how most package indexes are reached.
Optional: systemd user environment on distros where systemd is enabled inside WSL
Recent Ubuntu images can run systemd as PID 1 inside WSL when the feature is turned on. In that layout, placing exports only in ~/.bashrc may miss services started by systemd units. The portable pattern is a systemd user drop-in for environment.d or a login .conf fragment under ~/.config/environment.d/ that sets the same variables—still guarded so a missing host does not break every unit at boot.
Because WSL’s systemd story evolves quickly, treat this as an enhancement: verify with systemctl --user show-environment after edits, and fall back to shell functions if your distro does not yet expose the full systemd user session you expect.
DNS alignment: when “proxy is broken” is actually name resolution
A large fraction of WSL2 not using proxy threads are DNS-shaped: curl fails before TCP ever reaches Clash, apt cannot resolve mirror hostnames, or GitHub’s CDN answers differently depending on which resolver WSL queries. Mihomo Fake-IP modes can widen that gap because Linux may still ask a resolver that returns answers your routing rules never expect.
Work through a short triage:
- Compare
dig +short example.com(orgetent hosts) inside WSL against the same query on Windows with Clash DNS enabled. - Inspect
/etc/resolv.conf. Auto-generated content often tracks the Windows vEthernet DNS injection; custom corporate resolvers sometimes override it. - If Fake-IP is on, read our dedicated Fake-IP and DNS guide before you randomize
nameserverlines. Misaligned DNS can masquerade as a dead Windows Subsystem for Linux proxy bridge for hours.
Advanced users sometimes disable automatic resolv generation with /etc/wsl.conf and pin a known-good resolver; that is powerful and easy to get wrong. Document the rollback path before you edit.
Step 8: Verification checklist you can paste into notes
- TCP:
nc -vz "$HOST_IP" 7890succeeds from WSL. - Env:
env | grep -i proxyshows lowercase and uppercase HTTP variables. - Exit IP:
curl -s https://api.ipify.orgwith and without--proxydiverges the way your rules predict. - TLS:
curl -vI https://www.google.comshows CONNECT via proxy when expected. - apt:
sudo apt updatesucceeds with Acquire lines matching the same host IP. - git:
git ls-remote https://github.com/torvalds/linux.gitreaches heads without hanging. - DNS: resolution errors disappear when you query the same resolver path Clash uses for real IPs.
When bridging the host port is the wrong long-term bet
If you primarily need “everything in WSL follows the same policy as Windows without per-tool exports,” evaluate TUN mode on the Windows side or future WSL networking modes that shrink the semantic gap. TUN raises privilege and DNS responsibility; read Clash TUN mode explained before you enable it on a work machine. Many developers keep host bridging for WSL while the browser uses system proxy—two explicit hops that are easier to reason about than a half-migrated TUN experiment.
Closing: treat WSL2 as a second computer behind the same glass
Frustration with Clash WSL2 proxy usually comes from unclear ownership: the tray icon is green on Windows, but the Linux utility VM never received a LAN-visible listener or consistent DNS. Once you prove TCP to the host mixed port, export variables deliberately, align apt and git, and separate resolver issues from proxy issues, behavior becomes predictable instead of superstitious.
For subscription hygiene before you tune more edge cases, see our subscription import tutorial. When you are ready to standardize on a maintained client build on the Windows side of the glass, download Clash for free from our official page and experience the difference.