WSL 的网络迷宫:localhost、Docker、VPN 和代理到底怎么排查
原创 · 约 25 分钟阅读 · 阅读 --

WSL 的网络迷宫:localhost、Docker、VPN 和代理到底怎么排查

作者: Alex Xiang


古董级程序员,大厂出来后一直在创业公司,现在仍活跃在一线做 AI 相关的开发。更完整的更新写在微信公众号「字与码」:工作经历、对新技术的想法,以及这些年走弯路的记录,会不定期发在那里。若觉得博客对你有用,欢迎顺手关注。

WSL 的网络问题有一个特点:它很少直接告诉你哪里错了。

浏览器打不开 localhost:8000,你不知道是服务没监听、端口没映射、Windows 防火墙拦了、Docker 没暴露、WSL NAT 变了,还是公司 VPN 接管了 DNS。更烦的是,换个网络、重启一下、关掉 VPN,又好了。

WSL 网络、localhost、Docker、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 里的 curlaptnpm 未必知道这些配置。

先看 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

哪个工具失败,就看哪个工具的代理配置。aptpipnpmgit 都有自己的配置入口。

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/)