WSL可能导致别的应用无法正常使用!

_

背景

我的开发机上装着 WSL2(还有依赖它的 Docker Desktop),平时 Windows 宿主机和 WSL 各跑各的,相安无事。

前几天要本地起一个需要监听端口的服务,结果启动直接报错,提示端口绑定失败。第一反应当然是“端口被占了”,但一波排查下来发现:netstat 里干干净净,没有任何进程占用这个端口,应用却死活绑不上。重启电脑有时能好,过几天又突然犯病,非常玄学。

最后定位到:是 WSL2 背后的 WinNAT(Windows NAT Driver) 服务在捣鬼——它会把一批端口登记为“排除端口”。这些端口既不显示被占用、也不出现在任何进程的监听列表里,但普通应用就是绑不上,于是别的应用就“无法正常使用”了。

WinNAT服务最傻逼的一点就是它会为WSL保留端口,并且这个端口,你刚开电脑每次都会变。就算你只是安装了WSL没开它,也会占用这部分端口。

严格来说 winnat 不是 WSL 自带的服务,而是 Windows 系统组件;WSL2 默认用 NAT 模式组网,会驱动它去圈端口。Hyper-V、Docker Desktop 等同样可能触发,但 WSL 用户几乎天天开着它,所以最容易中招。

现象

典型症状三件套:

  1. 应用启动失败,报端口绑定错误。常见报错形式(报错和语言栈无关,本质都是同一个):

    • Node.js:Error: listen EACCES: permission denied 0.0.0.0:xxxx

    • Python:OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。

    • Java:java.net.BindException: Address already in use: bind(然而并没有谁在用它)

    • 通用英文:An attempt was made to access a socket in a way forbidden by its access permissions,错误码 10013

  2. netstat -ano | findstr :<端口> 查不到任何占用,也没有可疑 PID;

  3. 换个端口可能就好了;重启电脑有时“自愈”,过几天又犯——因为每次开机被排除的端口范围都不一样

排查过程

第 1 步:确认端口真的没被占用

netstat -ano | findstr :7800

没有任何输出——没有 LISTENING,也没有 TIME_WAIT。端口看起来是自由的。

第 2 步:排除权限问题

用管理员身份重新运行应用,照样失败——不是权限不够的事。

第 3 步:手动 bind 验证,把锅甩给系统

用 PowerShell 直接试绑这个端口:

powershell

$l = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, 7800)

$l.Start()

$l.Stop()

抛出 10013。连管理员都绑不上,说明不是应用的问题,是系统层面把这个端口“封”了。

不过最后这步也可以不用搞,我其实已经碰到了两三次这个问题,次次都是WSL端口占用的原因,所以现在已经PDST了,一有端口问题就优先定位他。

第 4 步:查看端口排除范围,真凶现身

管理员 CMD 执行:

netsh int ipv4 show excludedportrange protocol=tcp

我机器上的输出如下(这是处理完之后截的;出问题时,这里会有一段把目标端口整个盖进去的范围):

协议 tcp 端口排除范围
开始端口    结束端口

----------    --------

      5357        5357

      7800        7900     *

      8028        8028

      8996        8996

     50000       50059     *

* - 管理的端口排除。

应用要用的端口正好落在其中一段里。拿关键词 excludedportrange winnat 一搜,GitHub 上 microsoft/WSL#5514 里一大片同样的受害者。小丑微软。

原因分析:WinNAT 的“端口排除”机制

WSL2 并不是一个普通进程,而是一台轻量的 Hyper-V 虚拟机。它默认使用 NAT 模式联网:Windows 这一头由 WinNAT(Windows NAT Driver) 服务负责给虚拟机做网络地址转换和端口转发。

为了给 NAT / 端口转发留“弹药”,WinNAT / Hyper-V 启动时会从系统里圈走一段一段的端口,登记为排除端口(excluded port range)

你的应用 ──bind(7800)──✕──> TCP/IP 协议栈
                              ▲

                              │ 查排除表:7800~7900 被排除

                    WinNAT / Hyper-V(为 WSL2 的 NAT 网络服务)

这些端口有三个特点,正好凑成一个完美的坑:

  1. 看不见:没有任何进程“监听”它们,netstat 查不到占用;

  2. 绑不上:内核在 bind 时直接拒绝,返回 WSAEACCES(10013)。即使以管理员身份运行、即使设置了 SO_REUSEADDR 也一样——微软官方文档明确记录了这个行为(见文末参考资料);

  3. 会漂移:圈哪几段是动态决定的,每次开机都可能不同。这就是“重启能好、过几天又犯”的玄学来源。

关于上面输出的补充解读:

  • * 的是“管理的端口排除”(Administered port exclusions),即通过命令或接口显式登记的持久排除。Hyper-V / WSL 启动时圈的地,大多数也以这种形态出现(比如 50000-50059、我机器上的 7800-7900);

  • 不带 * 的单个端口,通常是某个系统服务正在监听或保留的(比如 5357 是 Windows 的 WSD 服务经 http.sys 占用的);

  • 不管哪一种,对普通应用的效果都一样:bind 直接被拒。UDP 有同样的机制,可用 netsh int ipv4 show excludedportrange protocol=udp 查看。

解决方案

临时方案

原理很简单:把 WinNAT 停掉,动态圈走的排除范围就被清掉了;再启动应用把端口抢回来,最后恢复 NAT。管理员 CMD:

:: 1. 关闭 WSL,结束虚拟机,释放它占用的网络资源

wsl --shutdown

:: 2. 停止 NAT 驱动,清空动态端口排除

net stop winnat

:: 3.(建议)这里先把你的应用启动,让它正常 bind 端口

:: 4. 重新启动 NAT 驱动,WSL 的网络恢复正常

net start winnat

:: 5. 复查排除范围

netsh int ipv4 show excludedportrange protocol=tcp

执行完再看排除范围,盖住端口的那一段已经消失,应用恢复正常。

两个注意点:

  • wsl --shutdown 会关掉所有发行版里正在运行的一切(后台服务、tmux 会话、数据库……),注意先保存工作;

  • 这只是应急手段:下次开机 WinNAT 换个地方圈地,问题可能复发。要根治看下一节。

根治:三个长期方案

方案一:给你的端口“上户口”(推荐)

WinNAT 动态圈地只圈没登记的端口。用 netsh 给应用要用的端口登记一个持久的管理排除,以后每次开机 Hyper-V 都会绕开它,你的应用则可以正常监听:

net stop winnat

netsh int ipv4 add excludedportrange protocol=tcp startport=7800 numberofports=1

net start winnat

  • 7800 换成你自己的端口;连续多个端口可以用 numberofports=n 一次登记一段;

  • 执行 add 时该端口不能正处在排除范围内(所以要先 net stop winnat);

  • 撤销登记:netsh int ipv4 delete excludedportrange protocol=tcp startport=7800 numberofports=1。如果某段带 * 的范围已经盖住了你的端口,也可以直接用它删掉(必要时先 net stop winnat)。

方案二:检查动态端口范围有没有被改小

netsh int ipv4 show dynamicport tcp

Windows 默认的动态(临时)端口范围是 49152 ~ 65535。如果这个起点被某些软件或“优化教程”改小了(比如从 1024 开始),Hyper-V 每次开机就会在低位随机圈地,很容易撞上 3000、8080 这类常用开发端口。复位回默认:

netsh int ipv4 set dynamic tcp start=49152 num=16384 store=persistent

重启后生效。

方案三:WSL 换用 mirrored 网络模式(Windows 11 + 新版 WSL)

WSL 2.0.0 起(需要 Windows 11 22H2+)支持镜像网络模式:WSL 不再走 NAT,从根源上绕开了 WinNAT 圈端口这回事。编辑(没有就新建)%UserProfile%\.wslconfig

[wsl2]
networkingMode=mirrored

然后先 wsl --update,再 wsl --shutdown 重启 WSL。镜像模式还附带 localhost 双向互通、IPv6 支持等好处;个别 VPN / 代理环境可能不兼容,遇到问题把这行删掉即可回退。

总结

WSL2 依赖 Hyper-V + WinNAT 提供 NAT 网络,WinNAT 启动时会动态圈走一批端口做“排除”;这些端口在 netstat 里看不见,普通应用却绑不上,于是“别的应用无法正常使用”。 应急用 wsl --shutdown + net stop/start winnat;根治用 netsh ... add excludedportrange 给端口上户口,或者干脆切到 mirrored 网络模式。

这个坑最阴险的地方在于“动态漂移”:同一台机器、同一个端口,这次开机绑得上,下次开机绑不上。希望这篇踩坑记录能帮到正对着 netstat 一脸懵的你。


AI 眼镜最实际的用途,可能不是显示屏,而是帮我记住刚刚发生了什么 2026-06-28

评论区