route WSL 2 traffic through the Windows host so a corporate VPN does not black-hole it.
Problem · Flow · Install · Usage · Updating · Uninstall · Credits
A single static Go binary, packaged as a small Alpine WSL 2 distro, that does one thing: when your corporate VPN client breaks WSL 2 connectivity, it puts your WSL traffic back on the network by tunneling it through the Windows host's network stack via gvisor-tap-vsock.
No Windows-side admin rights. No registry edits. No VPN client config. No Hyper-V switch surgery. You import a tar.gz as a WSL distro, start it, and your other distros work again.
A from-scratch rebuild of the dormant sakai135/wsl-vpnkit (last release 2023-04-04). Same problem, repackaged: shell script replaced by a static Go orchestrator, three base images collapsed to one (Alpine), every input validated, every external binary pinned by SHA-256.
WSL 2 runs in a lightweight VM with its own virtual network. Many corporate VPN clients (Cisco AnyConnect, GlobalProtect, Zscaler, Pulse, FortiClient, etc.) install routes or filters on the Windows side that the WSL VM's NIC cannot reach. When the VPN connects, your Linux distros lose connectivity. When the VPN disconnects, everything works again.
The Microsoft-supported workarounds (mirrored mode, DNS tunneling) only cover some clients on some Windows builds, and they require Windows-side admin to flip on. wsl-vpnfix sidesteps the OS networking question entirely: WSL traffic never tries to leave the WSL VM as IP packets. It gets handed to a user-mode network stack that runs on the Windows host, which then issues outbound connections from the host's network identity. The VPN sees normal host traffic and doesn't filter it.
Windows host │ WSL 2 VM
│
┌─────────────────────────────────────┐ │ ┌──────────────────────────────┐
│ wsl-gvproxy.exe │ │ │ wsl-vpnfix distro │
│ (gvisor-tap-vsock, user-mode │ │ │ /sbin/wsl-vpnfix (orch) │
│ TCP/IP stack, talks to host │ │ │ ├─ wsltap 192.168.127.2 │
│ network as a normal app) │ │ │ ├─ default via .127.1 │
│ │◄──┼──►│ └─ nft NAT: redirect DNS │
│ staged at: │ stdio │ + masquerade out │
│ C:\Users\Public\.wsl-vpnfix\… │ │ │ │
└─────────────────────────────────────┘ │ │ ┌──────────────────────┐ │
│ │ │ sibling distro │ │
│ │ │ (Ubuntu, Debian, …) │ │
│ │ │ inherits the route │ │
│ │ └──────────────────────┘ │
│ └──────────────────────────────┘
- You import
wsl-vpnfix-X.Y.Z.tar.gzas a WSL 2 distro calledwsl-vpnfix. - On distro boot,
wsl.conf [boot] command=starts/sbin/wsl-vpnfixas a child of WSL's/init. - The orchestrator stages
wsl-gvproxy.exeto a Windows NTFS path, validates the SHA-256, and spawns it via WSL interop. wsl-gvforwarderopens a tap device (wsltap) inside the WSL VM, exchanges packets with the .exe over stdio.- nftables rewrites: WSL2 host gateway
→host IP, DNS→gvproxy gateway, masquerade out the tap. - Other WSL distros use the same WSL 2 VM kernel and inherit the route. Their traffic goes out through the .exe, which runs as a normal Windows process, which the VPN does not filter.
The runtime is one static Go binary (/sbin/wsl-vpnfix) that talks to the kernel via netlink (no shelling out to iptables, nft, or ip), and one Windows .exe shipped alongside it. No daemon installer, no Windows service, no persistent Windows-side process. The optional auto-start at logon is a one-shot Task Scheduler entry that kicks the distro and exits in under a second.
You need a Windows 10 22H2+ or Windows 11 host with WSL 2 installed (wsl --install if you don't have it yet). No admin rights are needed for the wsl-vpnfix install itself.
Open PowerShell (regular, not admin) and run:
iwr -UseBasicParsing https://raw.githubusercontent.com/zeroznet/wsl-vpnfix/main/scripts/install-wslvpnfix.ps1 | iexThis pulls the latest release tarball from GitHub Releases, verifies it against SHA256SUMS, imports it as the wsl-vpnfix distro under %LOCALAPPDATA%\wsl-vpnfix\, starts the appliance silently in the background (no visible console window), and registers a per-user Task Scheduler entry named wsl-vpnfix that re-launches it at every logon. Native Windows mechanism, no scripts in shell:startup, auditable from taskschd.msc. After the script returns, your other WSL distros already have working network connectivity through it.
Override the defaults with parameters:
$script = (iwr -UseBasicParsing https://raw.githubusercontent.com/zeroznet/wsl-vpnfix/main/scripts/install-wslvpnfix.ps1).Content
$sb = [scriptblock]::Create($script)
& $sb -Tag v0.2.1 -DistroName wsl-vpnfix -InstallDir "$env:LOCALAPPDATA\wsl-vpnfix"| Parameter | Default | Purpose |
|---|---|---|
-Tag |
latest |
which release to install (latest or vN.N.N) |
-DistroName |
wsl-vpnfix |
name to register under wsl --list |
-InstallDir |
$env:LOCALAPPDATA\wsl-vpnfix |
where the rootfs ext4.vhdx lives |
-Force |
off | unregister an existing distro of the same name without prompting |
-NoAutoStart |
off | skip the silent auto-start launcher; you control startup manually |
If you'd rather not pipe a script from the internet, grab the artifacts and import them by hand:
-
Go to the latest release.
-
Download
wsl-vpnfix-X.Y.Z.tar.gzandSHA256SUMS. -
Verify the tarball:
Get-FileHash wsl-vpnfix-X.Y.Z.tar.gz -Algorithm SHA256 # Compare against the line for that filename in SHA256SUMS.
-
Import it:
$dst = "$env:LOCALAPPDATA\wsl-vpnfix" New-Item -ItemType Directory -Force -Path $dst | Out-Null wsl --import wsl-vpnfix $dst .\wsl-vpnfix-X.Y.Z.tar.gz --version 2
The release artifact is reproducible: building from the same git commit + build/upstream-pins.yaml produces a bit-identical tarball. Verify or rebuild yourself:
git clone https://github.com/zeroznet/wsl-vpnfix && cd wsl-vpnfix
./build/pack.sh X.Y.Z
sha256sum out/wsl-vpnfix-X.Y.Z.tar.gzThe build runs inside a digest-pinned Alpine container via podman (or docker, if podman is missing). Toolchain is fully self-contained: no Go install on the host.
The installer leaves the appliance running, configured to auto-start at every logon. There is nothing to type after install. Your other WSL distros (Ubuntu, Debian, etc.) inherit working network connectivity through it, including while the corporate VPN is connected.
Verify it's up:
wsl -l -v # wsl-vpnfix should show 'Running'
wsl -d Ubuntu -- curl -sI https://1.1.1.1 # expect HTTP/2 301 (Cloudflare redirect); any HTTP response = bridge worksManual control, if you ever need it:
| Action | Command |
|---|---|
| Restart the appliance | wsl --terminate wsl-vpnfix; Start-ScheduledTask -TaskName wsl-vpnfix |
| Stop the appliance | wsl --terminate wsl-vpnfix |
| Audit the auto-start entry | Get-ScheduledTask -TaskName wsl-vpnfix | Select-Object -Property * (or taskschd.msc) |
| Disable auto-start at logon | Disable-ScheduledTask -TaskName wsl-vpnfix |
| Re-enable auto-start at logon | Enable-ScheduledTask -TaskName wsl-vpnfix |
| Remove auto-start entirely | Unregister-ScheduledTask -TaskName wsl-vpnfix -Confirm:$false |
| Inspect the orchestrator interactively | wsl -d wsl-vpnfix (drops you into a root shell; the orchestrator stdout is captured per session) |
The auto-start mechanism is a single per-user Task Scheduler entry. No service, no admin rights, no scripts in shell:startup. The task action is a plain hidden powershell.exe that calls wsl.exe -d wsl-vpnfix --exec /bin/true and exits. The orchestrator stays alive as a child of WSL's /init.
iwr -UseBasicParsing https://raw.githubusercontent.com/zeroznet/wsl-vpnfix/main/scripts/install-wslvpnfix.ps1 | iexThe installer detects an existing wsl-vpnfix distro, prompts before overwriting, and replays the import against the new tarball. Sibling distros are not touched.
Unregister-ScheduledTask -TaskName wsl-vpnfix -Confirm:$false
wsl --terminate wsl-vpnfix
wsl --unregister wsl-vpnfix
Remove-Item -Recurse "$env:LOCALAPPDATA\wsl-vpnfix"Four lines, in order: drop the auto-start task, terminate the distro, drop the WSL registration, drop the rootfs. No Windows service, no registry hive, no leftover %PROGRAMDATA%. The C:\Users\Public\.wsl-vpnfix\ staging directory holds the .exe and is removed by the orchestrator on graceful shutdown; if a crash leaves it behind, delete it by hand.
cmd/wsl-vpnfix/: orchestrator entrypointinternal/{config,netlink,netfilter,process,wsl,healthcheck}/: runtime modulesbuild/Dockerfile.rootfs: three-stage rootfs build (Go builder, upstream fetcher, final Alpine assembly)build/pack.sh: deterministic tarball producerbuild/upstream-pins.yaml: pinnedgvisor-tap-vsockrelease + SHA-256sscripts/install-wslvpnfix.ps1: Windows-side installerdev/: dev container (Alpine + Go) and run wrapperdocs/superpowers/{specs,plans}/: design contract and frozen phase plans
The networking backend is containers/gvisor-tap-vsock. The gvproxy.exe and gvforwarder binaries are pulled verbatim from upstream releases and verified by SHA-256 before bundling. Idea and reference architecture: sakai135/wsl-vpnkit.
BSD-2-Clause. See LICENSE.