NO_PROXY 没有标准语法,读取它的客户端在大多数细节上意见不一。在 2026 年 9 月 23 日一次回环实验的主矩阵中,15 个客户端收到了完全相同的 no_proxy 和 NO_PROXY 值:两个 curl 构建、wget、Go、五个 Python 客户端和六种 Node.js 配置。主矩阵中的 49 种「值 + 请求」组合里,21 种在所有客户端中路由一致,28 种不一致。每个单元格都在已发布的 results.csv 中,每个客户端和请求各占一行。
如果一个值必须同时服务于所有这些客户端,请只使用在各处表现完全一致的写法,并给两个变量名赋相同的值:
# example.com and 192.0.2.10 stand for your own domain and address
export no_proxy="localhost,127.0.0.1,example.com,.example.com,192.0.2.10"
export NO_PROXY="$no_proxy"example.com,.example.com这一对(测试时为example.test,.example.test)是唯一在全部 15 个客户端中同时覆盖一个域名及其所有子域名的写法。它从未匹配notexample.test。- 精确的 IPv4 地址在每个客户端中都只匹配自身。IPv4 CIDR 范围只在 15 个客户端中的 4 个里有效。
localhost,127.0.0.1在每个客户端中都让127.0.0.1和localhost绕过了代理。不列出回环条目时,只有 Go 会为回环地址跳过代理。- wget 只读取
no_proxy。两者都设置时,Go 1.27.1 的net/http优先取NO_PROXY,而 Go 1.28 预计将改为优先取no_proxy。保持两个名称的值一致。
已发布的数据集是在各自独立的用例中测试这些条目的。在一次对上面这行原样运行的一次性检查中(用 example.test 代替 example.com),它的九个测试请求在全部 15 个客户端中也都路由一致。
有两类写法会带来麻烦。一类会让客户端出错,或让整个列表失效。带方括号的 IPv6 条目会让 httpx 和 httpx2 在创建客户端时失败,httpx 0.28.1 中的 IPv6 CIDR 条目也是如此。在设置了 NO_PROXY 的同时把小写 no_proxy 设为空,会让 9 个客户端关闭整个列表。另一类会被某些客户端静默忽略:*.example.com 被 8 个忽略,列表内的 * 被 9 个忽略,IPv4 CIDR 范围被 11 个忽略,尾随点被 11 个忽略,空格分隔的列表被 9 个忽略,host:port 条目视其形式被 4 到 6 个忽略。就连单独的 * 也会被 wget 忽略。
这些结果是合成的。它们来自一台 Mac、一个位于 127.0.0.1 的代理、保留的 .test 域名和文档用 IP 地址,描述的是固定版本的客户端如何选择路由,而不是任何代理服务的行为。关于各客户端最初是如何读取代理变量的,见客户端如何读取 HTTP_PROXY 和 NO_PROXY。如果 Node 的 fetch() 完全忽略了代理,请先阅读修复 Node.js 代理后的「TypeError: fetch failed」。
可移植的子集与应避免的写法
表格使用实验中请求的名称:example.test、sub.example.test、a.b.example.test 和 notexample.test。这里没有任何客户端会在决定路由之前解析主机名:它们的源码把名称当作文本比较,而 curl、Go 和 Requests 会解析 URL 中的 IP 地址用于 CIDR 检查。662 个经代理的请求中,没有一个触发了沙箱记录在案的 DNS 查询或连接拒绝,尽管内核日志会丢弃部分拒绝记录。因此真实域名的行为应当与这些名称一致。
这 15 个客户端各自以所示设置从环境中读取代理变量,它们是:
- curl 8.7.1(macOS)和 curl 8.22.0;
- GNU Wget 1.25.0;
- Go 1.27.1
net/http,使用http.ProxyFromEnvironment; - Python 3.14.7
urllib、requests 2.34.2、httpx 0.28.1、httpx2 2.13.1 和 aiohttp 3.14.3,均设置trust_env=True; - Node.js 26.10.0 和 24.21.0 的内置
fetch()与http.get(),各自设置NODE_USE_ENV_PROXY=1(下文称为「Node 26 fetch」「Node 26 http」等); - npm undici 7.29.1 和 8.11.0 的
fetch(),使用dispatcher: new EnvHttpProxyAgent(),均运行在 Node 26.10.0 上。
对于所示请求,这些条目在全部 15 个客户端中给出了相同的结果:
| 条目 | 在每个客户端中的结果 |
|---|---|
example.test | 绕过了 example.test;notexample.test 仍走代理(子域名在 Node 的 http 客户端中有差异) |
.example.test | 绕过了 sub.example.test 和 a.b.example.test(apex 域名在 4 个客户端中有差异) |
example.test,.example.test | 绕过了 apex 域名和两个子域名;notexample.test 仍走代理 |
192.0.2.10 | 绕过了该地址;192.0.2.11 仍走代理 |
EXAMPLE.TEST,或对 EXAMPLE.TEST 的请求 | 大小写没有影响 |
other.test , example.test | 逗号后的空格没有影响 |
other.test,,example.test 或 other.test,, | 空项不匹配任何内容 |
localhost,127.0.0.1,::1 | 绕过了 127.0.0.1 和 localhost([::1] 有差异;见回环一节) |
只设置 no_proxy,不设置 NO_PROXY | 每个客户端都读取了小写变量 |
上文答案中每一种应避免的写法在下面都有单独的一节,并列出了行为不一致的客户端。
NO_PROXY 通配符:*.example.com 是否匹配 example.com?
这取决于客户端,而且 15 个客户端中有 8 个根本不把它当作通配符。设置 NO_PROXY=*.example.test 时:
| 请求 | 绕过了代理 | 走了代理 |
|---|---|---|
example.test | Node 26 fetch、Node 24 fetch、undici 7.29.1 | 其余 12 个 |
sub.example.test、a.b.example.test | Go、Node 26 和 24 的 fetch 与 http、undici 7.29.1 和 8.11.0 | curl(两个)、wget、urllib、requests、httpx、httpx2、aiohttp |
notexample.test | 无 | 全部 15 个 |
curl、wget 和全部五个 Python 客户端用这个条目什么也没匹配到。Go、Node 的 http 客户端和 undici 8.11.0 把它理解为「仅子域名」,而 Node 内置的 fetch() 和 undici 7.29.1 还绕过了 apex 域名。
Node 26 内置 fetch() 与 npm undici 8.11.0 在 apex 域名上的分歧是新出现的。undici PR #5777 于 2026 年 9 月 22 日随 undici 8.11.0 发布,其中说明 *.domain 条目「必须只匹配 sub.example.com / a.b.example.com,而不匹配 apex 域名」。Node 26.10.0 仍然捆绑 undici 8.10.2。因此在同一个 Node 26.10.0 运行时上,内置 fetch() 绕过了 example.test,而 npm undici 8.11.0 把它发往了代理。同一个 pull request 还让 * 在列表内或前后带空格时也能生效,这解释了这两个客户端之间仅有的其他几行差异。
要在每个客户端中都匹配子域名,请写 .example.com。如果 apex 域名也应绕过代理,再加上 example.com。没有任何一种测试过的写法能在全部 15 个客户端中既让子域名绕过代理,又让 apex 域名继续走代理。
前导点与不带点的域名:.example.com、example.com 与子域名
| 条目与请求 | 绕过了代理 | 走了代理 |
|---|---|---|
example.test,请求 sub.example.test | 13 个客户端 | Node 26 http、Node 24 http |
.example.test,请求 example.test | 11 个客户端 | wget、Go、httpx、httpx2 |
不带前导点的域名在所有客户端中都匹配其子域名,唯独 Node 内置的 http 客户端例外,它只精确匹配 example.test。实验在 26.10.0 和 24.21.0 上复现了最初在 Node 24.18.1 上报告的 node#65616。Node 自己的 NO_PROXY 格式列表把 example.com 称为「Exact host name match」(精确主机名匹配),这描述的是 http.request(),而不是 fetch()。修复 node#65617 截至 2026 年 9 月 23 日仍未合并。
前导点在每个客户端中都匹配所有子域名,但 wget、Go、httpx 和 httpx2 不把它应用于 apex 域名。Go 的文档明确了这一点:带前导点的域名「仅匹配子域名」(httpproxy)。
两种条目在任何客户端中都没有匹配 notexample.test。Requests 直到 2.34.0 才开始强制这条标签边界;其发布说明称它「不再对 no_proxy 域名做贪婪匹配」。更早的 Requests 版本未经测试。
NO_PROXY=* 与列表内的 *
| 值 | 绕过了代理 | 走了代理 |
|---|---|---|
* | 14 个客户端 | wget |
other.test,* 或 " * " | Go、httpx、httpx2、Node 26 http、Node 24 http、undici 8.11.0 | curl(两个)、wget、urllib、requests、aiohttp、Node 26 fetch、Node 24 fetch、undici 7.29.1 |
* 作为整个值、不带空格时,在除 wget 之外的每个客户端中都有效。curl 的文档就是这样写的:「唯一可用的通配符是单个 * 字符」(CURLOPT_NOPROXY)。wget 1.25.0 没有通配符。要让某一条 wget 命令不走代理,请使用它的 --no-proxy 选项(wget 手册)。
NO_PROXY 中的 CIDR、IP 范围与子网
设置 NO_PROXY=192.0.2.0/24 时,对 192.0.2.10 的请求在 curl 8.7.1、curl 8.22.0、Go 和 requests 中绕过了代理。其余 11 个把它发往了代理:wget、urllib、httpx、httpx2、aiohttp 和全部六个 Node 客户端。范围之外的 198.51.100.10 在每个客户端中都走了代理。没有客户端因 IPv4 范围而报错;11 个不支持它的客户端直接忽略了它。
- curl 在 7.86.0 中加入了 CIDR 支持(变更日志)。
- Requests 在回退到 urllib 之前先用自己的匹配器检查 CIDR,这就是两个 Python 客户端结果不同的原因。
- httpx 和 httpx2 接受了该条目,但没有匹配范围。将会加入 CIDR 范围支持的 httpx2 PR #1165 截至 9 月 23 日仍未合并。
- Node 的 NO_PROXY 格式列表记录的是
192.168.1.1-192.168.1.100这样的连字符范围,而不是 CIDR。实验没有测试连字符范围。node#57872 的一条评论报告,在 v27 预发布版本上,连字符范围在node:http中有效,但在fetch()中无效。
要在混合技术栈中让一个子网不走代理:
- 列出你的客户端实际调用的确切地址。精确的 IPv4 地址是唯一在全部 15 个客户端中都匹配的形式。
- 可以在它们旁边为 curl、Go 和 Requests 加上 IPv4 范围。设置
192.0.2.10,192.0.2.0/24时,一次不在已发布数据集内的一次性运行在全部 15 个客户端中绕过了192.0.2.10,而192.0.2.11只在那 4 个客户端中绕过;没有客户端报错。不要加入 IPv6 范围,因为 httpx 0.28.1 会因此失败(见下文)。 - 范围只适用于 URL 中直接写出的 IP 地址。curl、Go 和 Requests 不会先解析主机名,所以
192.0.2.0/24不覆盖一个解析到该范围内的名称。这一点来自它们的源码;实验只使用了 IP 地址。
NO_PROXY 中的 IPv6:不带方括号、带方括号与 CIDR
对于访问 http://[2001:db8::10]/ 的请求:
| 条目 | 绕过了代理 | 未绕过 |
|---|---|---|
2001:db8::10 | 12 个客户端 | urllib、Node 24 fetch、undici 7.29.1 走了代理 |
[2001:db8::10] | urllib、Node 26 fetch、Node 24 fetch、undici 7.29.1、undici 8.11.0 | 8 个走了代理;httpx 和 httpx2 报错 |
2001:db8::/48 | curl 8.22.0、Go | 12 个走了代理;httpx 报错 |
[2001:db8::10]:8080,请求端口 8080 | Go、urllib、Node 26 fetch、Node 24 fetch、undici 7.29.1、undici 8.11.0 | 7 个走了代理;httpx 和 httpx2 报错 |
不带方括号的形式表现最好:它在 12 个客户端中匹配,且没有让任何客户端出错。curl 就要求这样写:「在主机名列表中输入 IPv6 数字地址时不要加方括号」(CURLOPT_NOPROXY)。undici 在 8.10.0 中加入了不带方括号的 IPv6 匹配(PR #5623);Node 24.21.0 捆绑的 undici 7.29.1 没有这项功能。
两个 curl 构建中,只有 8.22.0 匹配了 IPv6 范围。在 8.18.0 之前(curl#19828),curl 的匹配器只在主机带着方括号到达时才把它当作 IPv6,而这种情况从不会发生,所以 IPv6 CIDR 条目无法匹配。
带方括号的 IPv6 条目导致 httpx 抛出 InvalidURL: Invalid port
在 httpx 0.28.1 和 httpx2 2.13.1 中,带方括号的条目比被忽略更糟。以默认的环境处理方式创建客户端时,在发送任何请求之前就抛出了 InvalidURL: Invalid port: 'db8::10]'。设置 NO_PROXY=localhost,127.0.0.1,[::1] 时,错误是 Invalid port: ':1]',而且对 127.0.0.1 和 localhost 的请求也同样失败,因为客户端根本没有创建成功。httpx 0.28.1 对 2001:db8::/48 也以同样的方式报错(Invalid port: 'db8::')。httpx2 2.5.0 消除了这个崩溃(「Allow IPv6 CIDR notation in no_proxy」,变更日志),但如表所示,它仍然没有匹配该范围。
要修复它,请把 IPv6 条目写成不带方括号的形式,例如 ::1 或 2001:db8::10;这样两个客户端都能正常创建。不要在 httpx 0.28.1 会读取的任何值中放入 IPv6 范围。如果你无法修改这个变量,就用 trust_env=False 创建该客户端(这样也能正常创建),并像 HTTPX 代理指南那样在代码中传入代理。这样创建的客户端会忽略所有代理变量。
NO_PROXY 中的端口:host:port
对于访问所列端口 8080 的请求:
| 条目 | 绕过了代理 | 走了代理 |
|---|---|---|
example.test:8080 | 11 个客户端 | curl(两个)、wget、aiohttp |
192.0.2.10:8080 | 10 个客户端 | curl(两个)、wget、requests、aiohttp |
.example.test:8080 | 9 个客户端 | curl(两个)、wget、aiohttp、Node 26 http、Node 24 http |
在端口 80 上,三个条目在每个客户端中都走了代理,因此没有客户端把端口条目应用到不同的端口。问题出在相反的方向:有些客户端根本从不匹配这类条目。curl 的文档没有描述条目的端口语法,wget 只比较主机名。aiohttp 把不带端口的主机传给 urllib 的匹配器(源码)。Requests 从不匹配带端口的 IPv4 条目:设置 192.0.2.10:8080 时,它在端口 8080 和端口 80 上都走了代理。据 PR #7586 所述,它的 IPv4 检查只比较主机;该修复截至 9 月 23 日仍未合并。
不要在共享的值中写端口。如果某个端口必须绕过代理,请在需要它的那个客户端中配置该绕过规则。
空格、空项、尾随点与大写
- **逗号前的空格:**设置
example.test ,other.test时,wget 把尾随空格保留为条目的一部分,并把example.test发往了代理。其余 14 个绕过了它。 - **用空格代替逗号:**设置
other.test example.test时,curl 8.7.1、Node 26 fetch、Node 24 fetch、undici 7.29.1 和 undici 8.11.0 绕过了两个主机。curl 8.22.0 只绕过了other.test。其余 9 个两个都没有绕过。curl 8.9.0 做出了这一改动:「noproxy: patterns need to be comma separated」(变更日志)。 - 空项:
other.test,,在任何客户端中都没有绕过example.test,因此没有客户端把空项当作「匹配一切」。 - 尾随点:
example.test.条目,或对http://example.test./的请求,只在 curl 8.7.1、curl 8.22.0、Node 26 fetch 和 undici 8.11.0 中匹配。undici 在 8.10.1 中加入了这一点(PR #5637),所以 Node 24 fetch 和 undici 7.29.1 没有它。 - **大写:**大写的条目和主机在全部 15 个客户端中都匹配。
NO_PROXY 与 no_proxy:谁优先,以及空变量陷阱
对于访问 example.test 的请求:
| 环境 | 绕过了代理 | 走了代理 |
|---|---|---|
只设置 NO_PROXY=example.test | 14 个客户端 | wget |
只设置 no_proxy=example.test | 全部 15 个 | 无 |
NO_PROXY=example.test,no_proxy=other.test | Go 1.27.1 | 其余 14 个 |
NO_PROXY=example.test,no_proxy="" | curl(两个)、Go、requests、Node 26 http、Node 24 http | wget、urllib、httpx、httpx2、aiohttp、Node 26 fetch、Node 24 fetch、undici 7.29.1、undici 8.11.0 |
当两个名称的值不一致时,Go 1.27.1 的 net/http 使用 NO_PROXY,其余 14 个使用 no_proxy。空的小写变量把客户端分成了 6 比 9 两组。curl、Go、requests 和 Node 的 http 客户端把它当作未设置,并回退到 NO_PROXY。其余九个把它当作空列表。2026 年 9 月 22 日针对预发布版本提交的 node#66202 描述了 fetch() 与 http.request() 之间的这一分歧。实验在正式发布的 Node 26.10.0 和 24.21.0 上复现了它。
Go 的优先级正在改变。golang.org/x/net 的提交 a02ddfa7ea 为 golang/go#79656 而作,并随 x/net v0.58.0 发布,它让 httpproxy 包优先取小写名称,而 Go 1.28 发布说明草案为 ProxyFromEnvironment 列出了同样的改动。在一次单独的检查中,x/net v0.58.0 和 v0.59.0 在冲突情形下已经遵循 no_proxy。Go 仍然跳过空值,因此它在空变量上的结果不会改变。
代理变量也有同样的陷阱。设置 http_proxy="" 并设置 HTTP_PROXY 时,只有 Go、Node 26 http 和 Node 24 http 走了代理;其余 12 个直连。curl 只读取小写的 http_proxy。
给两个名称赋相同的值,并用 unset 移除变量,而不是把它导出为空值。
localhost、127.0.0.1 与 ::1:只有 Go 会自行跳过代理
完全不设置 NO_PROXY 时,Go 对 127.0.0.1、localhost 和 [::1] 都直连。它的 httpproxy 包记录了 localhost 和回环地址从不使用代理。其余 14 个客户端把这三个都发往了代理。因此在配置了远程代理时,除非列出回环地址,这些客户端会把发给本地开发服务器的请求发往那个代理。
| 值 | 127.0.0.1 和 localhost | [::1] |
|---|---|---|
localhost,127.0.0.1,::1 | 全部 15 个绕过(Go:内置回环规则) | 12 个绕过(Go:内置回环规则);urllib、Node 24 fetch 和 undici 7.29.1 走了代理 |
localhost,127.0.0.1,[::1] | httpx 和 httpx2 报错;13 个绕过(Go:内置回环规则) | urllib、Node 26 fetch、Node 24 fetch、undici 7.29.1 和 8.11.0 绕过,Go 凭其内置回环规则绕过;httpx 和 httpx2 报错;7 个走了代理 |
这张表中 Go 的绕过来自其内置规则,而不是来自列表,因此它们不能说明 [::1] 作为条目在 Go 中有效;在主矩阵中,Go 对带方括号的条目 [2001:db8::10] 走了代理。
请列出 localhost,127.0.0.1。如果你使用 IPv6 回环,再加上不带方括号的 ::1,绝不要写 [::1]。
经 CONNECT 的 HTTPS 是否得到同样的绕过决定?
在这次运行中,是的。对于 8 个 HTTPS URL 中的每一个,每个客户端都做出了与对应 HTTP URL 相同的选择,120 次比较全部一致。每个经代理的 HTTPS 请求都以 CONNECT 的形式到达代理。
Node.js:NO_PROXY 在 fetch 或 http.request 中不起作用
有四种互不相关的情况会让 NO_PROXY 在 Node 中看起来失效。显式 ProxyAgent 的情形来自 undici 的文档,未经测试;其余三种是实验结果。
**需要显式启用。**没有 NODE_USE_ENV_PROXY=1 或 --use-env-proxy(文档)时,Node 26 和 24 的 fetch() 与 http.get() 在全部 36 个对照单元格中都忽略了代理变量并直连。Node fetch 指南覆盖了这种故障。
**显式的 ProxyAgent。**undici 的文档把 ProxyAgent 描述为将每个请求都经其代理路由,并且没有提到绕过列表;读取 no_proxy 和 NO_PROXY 是 EnvHttpProxyAgent 额外提供的能力(undici 8.11.0 的 ProxyAgent 和 EnvHttpProxyAgent 文档)。因此,像 Node.js fetch 代理配置指南那样把 ProxyAgent 作为 dispatcher 传入的代码会忽略 NO_PROXY。要保留绕过列表,请使用 new EnvHttpProxyAgent(),它会读取这些变量,或者接受 httpProxy、httpsProxy 和 noProxy 选项。
同一运行时中的两个匹配器。http.request() 使用 Node 自己的匹配器,而内置 fetch() 使用 undici 的 EnvHttpProxyAgent。在 Node 26.10.0 上,它们在 72 行非基线数据中有 20 行不一致;在 Node 24.21.0 上是 72 行中的 19 行。Node 的匹配器带有注释 TODO(joyeecheung): share code with undici.(源码),而共享这部分代码在跟踪 issue node#57872 中仍是一个未完成事项。
**捆绑的 undici 与 npm undici。**在 Node 26.10.0 上,内置 fetch()(undici 8.10.2)与 npm undici 8.11.0 在 4 行上不一致,全部是通配符用例。Node 24.21.0 的 fetch() 与 npm undici 7.29.1 完全没有差异。undici 7.29.1 与 8.11.0 在 11 行上不一致。
下表针对 Node 26.10.0 上的每一类差异各展示一行,使用上文各节中的值。20 行差异中的其余 12 行是这些类型在其他主机和请求上的重复,外加 http_proxy="" 的情形。
| 差异 | fetch | http | undici 8.11.0 |
|---|---|---|---|
| 不带点的域名,请求子域名 | 绕过 | 走代理 | 绕过 |
*.example.test,请求 apex 域名 | 绕过 | 走代理 | 走代理 |
列表内的 * | 走代理 | 绕过 | 绕过 |
| 带方括号的 IPv6 地址 | 绕过 | 走代理 | 绕过 |
| 带端口的前导点条目 | 绕过 | 走代理 | 绕过 |
| 尾随点条目 | 绕过 | 走代理 | 绕过 |
| 用空格代替逗号 | 绕过 | 走代理 | 绕过 |
no_proxy 为空,NO_PROXY 已设置 | 走代理 | 绕过 | 走代理 |
Node 24.21.0 表现出同样的模式,只有两处例外。对于尾随点,它的两个客户端都走了代理。对于不带方括号的 IPv6 条目,只有 http 绕过了它。
node#57872 上 2026 年 9 月 1 日的一条评论已经在 v27.0.0 预发布版本上比较了 node:http 与 fetch()。实验的 Node 数据行与那张表中可以比较的全部 8 行一致。
GitLab 在 2021 年的发现,以及此后的变化
GitLab 的 Stan Hu 2021 年的文章(研究由 Nourdin el Bacha 完成)根据源码和文档比较了 curl、wget、Ruby、Python、Go 和 Java。它推荐了一个最小公分母,并提出了一项标准。
它的 no_proxy 表格中,本实验能够核对的 32 个单元格(curl、wget、经 urllib 的 Python 和 Go 各 8 行)里,31 个仍然吻合。例外是 curl 与 CIDR:2021 年的表格说不支持,但两个 curl 构建在这里都遵守了它。Go 的「Uppercase」优先级单元格仍与 Go 1.27.1 相符,并预计在 Go 1.28 中改变。实验无法核对「Supports regexes?」和「Resolves IP addresses?」两行;后者把 Go 标为「Yes」,但文章正文自己说只有 Ruby 会解析主机名,而 Go 1.27.1 的源码也不会。Ruby 和 Java 未经测试。
它的最小公分母建议只有一部分仍然成立。只用小写的 no_proxy、逗号和精确 IPv4 地址在全部 15 个客户端中都有效;前导点如文章所警告的那样在 4 个客户端中仍不匹配 apex 域名;只有 4 个客户端遵守 IPv4 CIDR,所以避免 CIDR 仍然是合理的。「后缀总是会被匹配」在 Node 的 http 客户端中不成立,「以逗号分隔的 hostname:port 值」每个条目只在 9 到 11 个客户端中匹配(8 个客户端三个条目都匹配),而没有任何一种 IPv6 形式取得一致。
其中几种行为是最近才出现的。Requests 2.34.0(2026 年 5 月 11 日)加入了标签边界。httpx2 2.5.0(2026 年 6 月 25 日)不再因 IPv6 CIDR 条目崩溃,但也不匹配它们。undici 8.10.0、8.10.1 和 8.11.0(2026 年 8 月和 9 月)加入了不带方括号的 IPv6、尾随点和新的通配符规则。
测试如何进行,以及如何重新运行
一个位于 127.0.0.1 的小型 HTTP 代理记录了每个绝对形式的请求和每个 CONNECT,自行应答,不转发任何内容。每个请求都在 macOS sandbox-exec 下的独立进程中运行,其配置文件拒绝除回环之外的所有出站流量,因此直连尝试在本机就会失败,不会到达任何真实主机。只有当代理记录到其请求时,一个单元格才计为经代理;客户端是否得到响应从不影响判定。不设置 NO_PROXY 时,全部 15 个客户端对每种 URL 形式都走了代理。
已发布的运行于 2026 年 9 月 23 日 19:13 至 19:15 UTC 进行,共有 1,266 个单元格。一次从已发布归档按下面步骤进行的干净重跑,对全部 1,266 个单元格给出了完全相同的分类。
要在不运行任何测试的情况下阅读已发布的矩阵,解压归档并运行 python3 no_proxy_lab.py table results.json。它不需要任何安装或网络,而且在 Python 3.9.6 和 3.14.7 上打印的输出相同。完整重跑需要 macOS、Python 3.14 和 Go、约 700 MB 磁盘空间,以及在 setup 期间访问 nodejs.org、pypi.org 和 registry.npmjs.org;setup 不会在系统范围内安装任何东西:
curl -fLO https://ipvolt.com/downloads/no-proxy-matching-tested/no-proxy-matching-tested.zip
unzip no-proxy-matching-tested.zip
cd no-proxy-matching-tested
python3.14 no_proxy_lab.py setup --work ./work
python3.14 no_proxy_lab.py run --work ./work --out ./out
python3.14 no_proxy_lab.py compare results.json out/results.json
python3.14 no_proxy_lab.py table out/results.json --section main只有当每个单元格的分类都相同时,compare 才以 0 退出。README 解释了使用其他客户端构建时它的输出、如何测试其他版本或你自己的 NO_PROXY 值、面向其他系统的 --isolation none 模式,以及 results.json 的每个字段。
下载:
- no-proxy-matching-tested.zip:下面的全部文件,并已包含
clients/目录(如果逐个下载文件,请自行重建该目录) - README.md:方法、要求、命令以及如何阅读结果
- results.json 和 results.csv:已发布运行的每个单元格
- no_proxy_lab.py、clients/py_client.py、clients/node_client.cjs、clients/go_client.go 和 nonet.sb:测试框架、单请求客户端和仅允许回环的沙箱配置文件
- requirements.txt、package.json 和 package-lock.json:精确的 Python 和 npm 版本锁定
局限
- 一台 macOS 15.7.4 arm64 机器、一个日期和上述版本。任何一次发布都可能改变某一行:Node 26.10.0 尚未捆绑 undici 8.11.0,而 Go 1.28 预计将改变变量冲突那一行。
- 未测试 Linux。每个客户端都在自己的代码或其语言的标准库中匹配 NO_PROXY,而在设置了代理变量的情况下,Python 的 urllib 在 macOS 上走的是与 Linux 相同的环境路径。因此在 Linux 上,各行应当取决于客户端版本而不是操作系统。这是从源码得出的推断,不是 Linux 上的结果;README 给出了示例。
- 矩阵只测试了环境变量的处理(采用上面列出的设置),并且只测试了 HTTP 正向代理。它没有测试显式代理选项、其他 agent 或 dispatcher、系统代理设置、Windows、SOCKS、PAC 文件、重定向或代理认证。关于 407 响应,见如何修复代理错误 407。
- 未测试连字符范围、
192.168.*这样的部分地址、依赖 DNS 解析的条目,以及 Ruby、Java、.NET、Deno 和 Bun;README 列出了其余未测试项。 - 上文的两次一次性检查于 2026 年 9 月 24 日使用相同的测试框架和客户端运行。它们不在
results.json中;README 说明了如何重新运行它们。
ipvolt 于 2026 年 9 月 23 日和 24 日使用上面列出的客户端版本在本地运行了这些检查。没有测试任何外部服务商、代理服务或网络,本文内容也不描述 ipvolt 自己的服务。
如果你想在 ipvolt 开放访问时收到通知,请加入早期访问名单。访问开放时发送一封邮件。仅此而已。
参考来源
- GitLab (2021): We need to talk: Can we standardize NO_PROXY?
- nodejs/node#57872: node:http vs fetch() NO_PROXY table (comment, 2026-09-01)
- nodejs/node#65616: NO_PROXY=example.com and http.request() subdomains
- nodejs/node#65617: http: match subdomains for plain NO_PROXY entries
- nodejs/node#66202: fetch() and http.request() with an empty lowercase variable
- Node.js 26.10.0 docs: NO_PROXY format
- Node.js 26.10.0 docs: NODE_USE_ENV_PROXY
- Node.js 26.10.0 source: lib/internal/http.js (http.request matcher)
- Node.js 26.10.0: bundled undici version
- undici PR #5777: NO_PROXY wildcard semantics
- undici v8.11.0 release notes
- undici PR #5623: match bare IPv6 addresses in no_proxy
- undici PR #5637: ignore trailing dots when matching no_proxy
- undici v8.11.0 docs: ProxyAgent
- undici v8.11.0 docs: EnvHttpProxyAgent
- libcurl: CURLOPT_NOPROXY
- curl changelog
- curl#19828: IPv6 CIDR notation in NO_PROXY (fixed in 8.18.0)
- Go: golang.org/x/net/http/httpproxy Config
- Go: httpproxy Config.ProxyFunc (localhost and loopback)
- golang.org/x/net commit a02ddfa7ea: httpproxy prefers lowercase proxy variables (x/net v0.58.0)
- golang/go#79656: inconsistent HTTP_PROXY precedence (milestone Go 1.28)
- Go 1.28 release-note draft: ProxyFromEnvironment prefers lowercase variables
- GNU Wget manual: Proxies
- Python 3.14 docs: urllib.request
- CPython 3.14.7 source: Lib/urllib/request.py (proxy_bypass by platform)
- Requests v2.34.0 release notes
- Requests v2.34.2 source: utils.py (should_bypass_proxies)
- Requests PR #7586: honor ports in IPv4 no_proxy entries
- httpx2 changelog (v2.13.1)
- httpx2 PR #1165: support IP CIDR ranges in NO_PROXY
- aiohttp docs: proxy support and trust_env
- aiohttp v3.14.3 source: helpers.py