WSL 的网络迷宫:localhost、Docker、VPN 和代理到底怎么排查
古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。
WSL 的网络问题有一个特点:它很少直接告诉你哪里错了。
浏览器打不开 localhost:8000,你不知道是服务没监听、端口没映射、Windows 防火墙拦了、Docker 没暴露、WSL NAT 变了,还是公司 VPN 接管了 DNS。更烦的是,换个网络、重启一下、关掉 VPN,又好了。

这篇不写“万能修复命令”。网络问题没有万能命令。我们用一个真实开发场景把路径拆开:一个 FastAPI 服务跑在 WSL 里,前面可能套 Docker,Windows 浏览器要访问它,服务又要访问公司内网 API 和外网包源。每一段都用命令确认,不靠猜。
先画出你到底在访问谁
很多排障一开始就错了,因为大家嘴里都说 localhost,但每个人的 localhost 不是同一个东西。
在 Windows 浏览器里:
http://localhost:8000
这个 localhost 是 Windows 的本机回环地址。
在 WSL 里:
curl http://localhost:8000
这个 localhost 是 Linux 发行版里的回环地址。
在 Docker 容器里:
curl http://localhost:8000
这个 localhost 是容器自己的回环地址。
三句话长得一样,指向完全不同。
所以第一步永远是问清楚:客户端在哪,服务端在哪,中间有没有 Docker,访问的是回环地址、WSL IP、Windows host IP,还是局域网 IP。
我通常先写成这样:
Windows Chrome -> WSL FastAPI
WSL curl -> Windows mock server
Docker api -> WSL PostgreSQL
Docker web -> Docker api
WSL apt -> Internet through VPN/proxy
只要这张小表写出来,很多问题会立刻收敛。
NAT 模式下的默认规则
WSL2 默认是 NAT 网络。Microsoft 的 WSL networking 文档里说得比较清楚:在 NAT 模式下,Windows 通常可以通过 localhost 访问 WSL 里监听的网络服务;反过来,WSL 要访问 Windows 上的服务,经常需要拿 Windows host 在 WSL 视角里的 IP。
假设服务跑在 WSL:
uvicorn app.main:app --host 0.0.0.0 --port 8000
在 WSL 里先确认:
curl -v http://127.0.0.1:8000/health
ss -ltnp | grep 8000
如果这里不通,问题和 Windows 没关系。服务没起来,或者只监听了别的端口。
如果 WSL 内部通,再到 Windows 浏览器访问:
http://localhost:8000/health
大多数情况下它会通。如果不通,再在 Windows PowerShell 里试:
curl.exe -v http://localhost:8000/health
然后查 WSL IP:
wsl.exe hostname -I
注意是大写 -I,不是小写 -i。微软文档特别提醒过,小写 -i 可能拿到的是本机占位诊断地址,不适合做连接目标。
有些需要从局域网其他机器访问 WSL 服务的场景,还会用到 netsh interface portproxy:
netsh interface portproxy add v4tov4 `
listenport=8000 `
listenaddress=0.0.0.0 `
connectport=8000 `
connectaddress=<WSL_IP>
这不是本机浏览器访问 WSL 的首选路径,而是你要把 WSL 服务暴露给局域网时才考虑。用它之前先想清楚防火墙和安全边界。
WSL 访问 Windows 服务
反过来,如果服务跑在 Windows,比如 Windows 上启动了一个 mock server:
python -m http.server 9000
在 WSL 里访问 localhost:9000,在 NAT 模式下未必是 Windows 的服务。更稳的方式是拿 Windows host IP:
ip route show | grep -i default | awk '{ print $3 }'
假设输出是:
172.30.96.1
那么访问:
curl -v http://172.30.96.1:9000
如果不通,看三件事。
第一,Windows 服务是不是只监听 127.0.0.1。有些服务只允许本机回环,不接受来自 WSL 虚拟网卡的连接。
第二,Windows 防火墙有没有拦。
第三,公司 VPN 或安全软件有没有改路由和防火墙规则。
开发 mock server 时,如果你明确要让 WSL 访问,可以让它监听所有地址:
python -m http.server 9000 --bind 0.0.0.0
但这也意味着局域网可能访问到它。别在咖啡馆 Wi-Fi 上随手这么开。
mirrored networking 改变了什么
Windows 11 较新的 WSL 支持 mirrored networking。启用后,WSL 会采用新的网络架构,目标是把 Windows 网络接口“镜像”到 Linux 里,改善 localhost、IPv6、VPN、局域网等场景。Microsoft 文档里也说,在 mirrored 模式下,Windows host 和 WSL2 VM 可以用 localhost 互相访问,过去那套查对方 IP 的技巧很多时候就不需要了。
配置在 Windows 用户目录:
# C:\Users\<you>\.wslconfig
[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
改完:
wsl --shutdown
再进入 WSL。
确认模式可以看:
wslinfo --networking-mode
如果你的 WSL 版本没有 wslinfo,就用:
wsl --version
wsl --status
mirrored 不是“所有网络问题自动消失”。它改变了默认边界,也会让 Windows 防火墙、Hyper-V 防火墙、公司安全策略更直接地影响 WSL。我的建议是:如果你在 Windows 11 新环境里,经常被 VPN、IPv6、localhost 互通困扰,mirrored 值得打开;如果团队里有人还在 Windows 10 或旧 WSL,文档里要写清楚 NAT 和 mirrored 两套排查方式。
DNS 问题不要先改 /etc/resolv.conf
WSL 的 DNS 问题很容易把人带进坑里。典型现象:
ping 1.1.1.1
# 通
curl https://github.com
# Could not resolve host
这说明 IP 网络可能通,DNS 解析不通。
老办法是关掉自动生成 resolv.conf:
[network]
generateResolvConf=false
然后手写 /etc/resolv.conf。这个办法有时能救急,但在公司网络、VPN、代理频繁变化的环境里,它会变成新的坑。今天连办公室 VPN,明天连手机热点,后天换家里 Wi-Fi,固定 DNS 很容易过期或绕不开公司内网域名。
新一点的 WSL 提供 dnsTunneling=true,目标就是改善 DNS 在复杂网络和 VPN 下的兼容性。更推荐先用:
[wsl2]
dnsTunneling=true
排查 DNS 我会按顺序跑:
cat /etc/resolv.conf
getent hosts github.com
nslookup github.com
curl -v https://github.com
如果是公司内网域名:
getent hosts internal.example.corp
再到 Windows PowerShell 跑:
Resolve-DnsName internal.example.corp
如果 Windows 能解析,WSL 不能解析,优先看 dnsTunneling、VPN 客户端策略、WSL 版本,而不是直接把 8.8.8.8 写进 resolv.conf。公共 DNS 可能能解析公网,却解析不了公司内网。
代理:环境变量和 Windows 系统代理是两回事
另一个常见误判是“Windows 浏览器能上网,所以 WSL 也应该能上网”。
不一定。
浏览器可能用了 Windows 系统代理、PAC、公司安全客户端、证书注入。WSL 里的 curl、apt、npm 未必知道这些配置。
先看 WSL 环境变量:
env | grep -i proxy
常见变量:
export HTTP_PROXY=http://127.0.0.1:7890
export HTTPS_PROXY=http://127.0.0.1:7890
export NO_PROXY=localhost,127.0.0.1,.local
注意 NAT 模式下,WSL 里的 127.0.0.1:7890 指的是 WSL 自己,不一定是 Windows 上的代理端口。你可能需要用 Windows host IP:
host_ip=$(ip route show | grep -i default | awk '{ print $3 }')
export HTTPS_PROXY="http://$host_ip:7890"
export HTTP_PROXY="http://$host_ip:7890"
mirrored 模式下,localhost 的表现会更接近直觉,但仍要看代理软件是否允许来自 WSL 的连接。
如果使用新版 WSL,可以试试:
[wsl2]
autoProxy=true
它的目标是让 WSL 自动使用 Windows 代理信息。但公司环境里代理策略千差万别,不能只靠一个开关。排查时还是要实际跑:
curl -v https://api.github.com
curl -v https://pypi.org/simple/
python -m pip index versions requests
npm ping
哪个工具失败,就看哪个工具的代理配置。apt、pip、npm、git 都有自己的配置入口。
Docker 又加了一层 localhost
如果服务跑在 Docker 里,网络图再多一层。
一个 compose:
services:
api:
build: .
command: uvicorn app.main:app --host 0.0.0.0 --port 8000
ports:
- "8000:8000"
容器里必须监听 0.0.0.0,不能只监听 127.0.0.1。否则端口映射了,外面还是访问不到。
排查顺序:
docker compose ps
docker compose logs api --tail=100
docker compose port api 8000
curl -v http://localhost:8000/health
如果 WSL 里 curl localhost:8000 通,Windows 浏览器不通,再看 WSL 与 Windows 边界。
如果 WSL 里也不通,先看 Docker 端口映射和容器监听。
如果容器里通、宿主不通,问题在 Docker bridge 或 ports。
进容器看监听:
docker compose exec api sh
ss -ltnp
curl -v http://127.0.0.1:8000/health
容器访问宿主服务也别写死 localhost。容器里的 localhost 是容器自己。Docker Desktop 环境里常见做法是用:
host.docker.internal
但它在不同 runtime 下支持情况不同。团队文档里最好明确“本项目支持 Docker Desktop WSL 后端”,还是“必须兼容 Linux 原生 Docker”。
一张排障命令清单
我一般按四层排。
第一层:服务本身。
ss -ltnp
curl -v http://127.0.0.1:<port>
journalctl --user -xe 2>/dev/null || true
第二层:WSL 与 Windows。
wsl.exe hostname -I
ip route show
curl -v http://<windows-host-ip>:<port>
Windows 侧:
curl.exe -v http://localhost:<port>
Get-NetTCPConnection -LocalPort <port>
第三层:DNS 和代理。
getent hosts github.com
cat /etc/resolv.conf
env | grep -i proxy
curl -v https://github.com
Windows 侧:
Resolve-DnsName github.com
netsh winhttp show proxy
第四层:Docker。
docker compose ps
docker compose port <service> <port>
docker compose logs <service> --tail=100
docker inspect <container> --format '{{json .NetworkSettings.Ports}}'
这套命令的意义不是一次性全跑,而是防止你跳层。很多网络问题排半天,是因为服务本身都没监听,大家却在改 DNS。
我会怎么写团队文档
团队里的 WSL 网络文档不要写成百科。只要写清楚本项目的入口。
例如:
## 本地服务访问
- 后端在 WSL 中运行,监听 `0.0.0.0:8000`
- Windows 浏览器访问 `http://localhost:8000`
- 前端在 Docker 中运行,端口映射 `5173:5173`
- PostgreSQL 只在 Docker network 内访问,不暴露给 Windows
## 网络模式
推荐 Windows 11 + WSL 最新版,启用:
```ini
[wsl2]
networkingMode=mirrored
dnsTunneling=true
autoProxy=true
如果使用 NAT 模式,WSL 访问 Windows 服务时用:
ip route show | grep -i default | awk '{ print $3 }'
再附一组排障命令。
不要让新人自己在搜索结果里拼旧教程。WSL 网络这几年变化很快,旧的 `resolv.conf`、`DISPLAY`、`netsh portproxy` 教程有时仍然有用,但不应该成为默认路径。
## 最后记住三句话
第一,`localhost` 只对当前网络命名空间成立。Windows、WSL、Docker 容器各有自己的 `localhost`。
第二,NAT 和 mirrored 是两套思路。不要拿 NAT 时代的 IP 查询技巧去解释 mirrored 下的所有现象,也不要要求 Windows 10 用户照抄 Windows 11 mirrored 配置。
第三,DNS 和代理要顺着公司网络走。公共 DNS、手写 resolv.conf、硬编码代理地址,短期能救急,长期会让团队环境越来越不可维护。
WSL 网络最怕混着猜。把客户端、服务端、中间层写出来,一段一段 curl,一段一段看监听,问题通常会变得很朴素。
## 参考资料
- [Accessing network applications with WSL - Microsoft Learn](https://learn.microsoft.com/en-us/windows/wsl/networking)
- [Advanced settings configuration in WSL - Microsoft Learn](https://learn.microsoft.com/en-us/windows/wsl/wsl-config)
- [Windows Subsystem for Linux September 2023 update - Windows Command Line](https://devblogs.microsoft.com/commandline/windows-subsystem-for-linux-september-2023-update/)
- [Docker Desktop WSL 2 backend on Windows - Docker Docs](https://docs.docker.com/desktop/features/wsl/)