起点
代理变量可能已经设置,而请求仍然直接发往目标站点。在归咎于网关之前,先检查客户端、目标站点的 scheme 和绕过规则。
把目标站点选择与代理连接分开
这些变量是由各个客户端自行解释的约定,而不是操作系统层面能让所有应用都走代理的开关。一条能正常工作的 curl 命令并不能证明另一个库继承或遵守了相同的设置。
变量名通常对应的是目标站点的 scheme。HTTPS_PROXY 中可以填写一个 http:// 代理 URL:目标站点使用 HTTPS,而客户端先连接到一个 HTTP 代理并请求 CONNECT 隧道。代理 URL 的 scheme 描述的是到代理本身的连接。
- http_proxy / HTTP_PROXY:为 HTTP 目标站点选择代理,受客户端的大小写规则约束。
- https_proxy / HTTPS_PROXY:为 HTTPS 目标站点选择代理。
- all_proxy / ALL_PROXY:在 curl 和 Requests 中,当没有适用的目标站点专用代理时作为回退。不要假设每个客户端都实现了它。
- no_proxy / NO_PROXY:当客户端的环境代理选择生效时,为匹配的目标站点请求直连。这是一条路由例外,而不是另一个代理地址。
确认实际发出请求的客户端及其版本
本指南的本地路由检查使用了 curl 8.7.1、运行在 Python 3.14.7 上的 Requests 2.34.2,以及 Node.js 26.8.1。Node.js 24.5.0 的发布说明单独记录了其默认 HTTP/HTTPS agent 引入环境代理支持一事;fetch 更早在 24.0.0 就获得了可选启用的支持。这些历史版本只经过文档审阅,并未在此实际执行。
- curl 接受小写的 http_proxy,但有意忽略大写的 HTTP_PROXY,因为 CGI 环境可能从传入的请求头派生出这个名字。HTTPS_PROXY 和 ALL_PROXY 则会被接受。协议专用变量的优先级高于 ALL_PROXY。
- Requests 通常会读取全部四组变量,包括大写名称。其 Python 代理发现逻辑在大小写两者不一致时优先取小写;设置了 REQUEST_METHOD 的 CGI 环境会忽略大写的 HTTP_PROXY。环境值可以覆盖 session.proxies,所以仅靠一个会话字典并不构成隔离边界。
- Node 内置的环境变量支持需要显式启用:以 NODE_USE_ENV_PROXY=1 启动进程。之后默认的 HTTP/HTTPS agent 和原生 fetch 会使用 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY;小写优先。在这种内置模式下,仅设置 ALL_PROXY 不能作为替代。自定义 agent、dispatcher 和第三方包需要各自单独审查配置。
把 NO_PROXY 当作路由变更来对待
先从一个以逗号分隔、只包含确切授权目标主机的列表开始,然后验证哪些请求真正匹配。不要把完整 URL 或路径粘贴进主机列表。在这里讨论的客户端中,通配符 * 会为所有目标站点请求直连;它不适合作为对必需代理的随手修补。
不存在可以在这些客户端之间通用假设的可移植匹配语法。例如,curl 的文档从 7.86.0 起记录了 CIDR 匹配;Node 的内置文档列出了主机名、域名后缀、地址范围和 host:port 条目。在一个客户端中可用的域名条目、通配符或子网,在另一个客户端中需要单独测试。DNS 别名和 IP 字面量也会改变被匹配的名称。
如果某个内部请求意外走了代理,请检查实际的目标主机名和该客户端特有的匹配规则。如果某个外部请求意外直连,请同时检查 NO_PROXY 的大小写两种形式以及任何显式的 agent 配置。只修改该部署获准的路由规则。
在没有真实网关的情况下观察代理选择
将下面的内容保存为 proxy_env_lab.py,并在安装了 curl 的环境中运行 python3 proxy_env_lab.py。它会在随机的回环端口上启动两个 HTTP 标记服务器:一个代表直连目标站点,一个代表被选中的代理。代理标记服务器只在本地应答;它不会转发流量,也不会测试 CONNECT、TLS、认证或出口 IP。
每个 curl 进程只接收提供给它的测试环境。--disable 是它的第一个选项,这样个人的 curlrc 就无法改变实验。预期结果依次为 proxy、direct、direct、proxy。这说明即使显式指定了 --proxy,仍然受 NO_PROXY 约束,除非在这个本地诊断中显式清空绕过列表。
proxy_env_lab.py · Python 3 标准库 + curl
import shutil
import subprocess
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
curl = shutil.which("curl")
if not curl:
raise SystemExit("Install curl before running this local lab")
def marker(label):
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = label.encode()
self.send_response(200)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, *_):
pass
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
threading.Thread(target=server.serve_forever, daemon=True).start()
return server, "http://127.0.0.1:" + str(server.server_port)
origin, destination = marker("direct")
proxy_server, proxy = marker("proxy")
cases = [
("environment", {"http_proxy": proxy}, [], "proxy"),
("host bypass", {"http_proxy": proxy, "NO_PROXY": "127.0.0.1"}, [], "direct"),
("explicit + bypass", {"NO_PROXY": "127.0.0.1"}, ["--proxy", proxy], "direct"),
("explicit + cleared bypass", {"NO_PROXY": "127.0.0.1"},
["--proxy", proxy, "--noproxy", ""], "proxy"),
]
try:
for label, environment, options, expected in cases:
result = subprocess.run(
[curl, "--disable", "--silent", "--show-error", "--fail",
"--connect-timeout", "2", "--max-time", "3", *options, destination],
env=environment, capture_output=True, text=True, timeout=5,
)
if result.returncode or result.stdout != expected:
raise SystemExit("Local route check failed: " + label)
print(label + ": " + result.stdout)
finally:
for server in (origin, proxy_server):
server.shutdown()
server.server_close()让应用的基线配置成为有意为之
对于必须走代理的 curl 诊断,请同时使用显式的 --proxy 和 --noproxy '',正如现有的 curl 设置指南所示范的那样。对于 Requests,在请求上提供 proxies 参数;当基线必须忽略继承的代理设置时,使用一个 trust_env=False 的专用 Session。这同时也会停用从环境派生的认证和 CA 证书包配置,因此需要时请显式提供获准的自定义信任证书包。
对于 Node,要么选择经过审查的环境变量启用方式,要么选择显式的兼容 agent/dispatcher。不要假设某个库的自定义 agent 会继承全局设置。确认工作进程或服务实际使用的运行时,而不只是交互式终端里安装的版本。
理解了本地规则之后,用一个授权的目标站点和一个服务商文档中给出的网关做对比。保持 TLS 校验开启,设置截止时间,并通过受控的端点或代理侧观测来确认请求路径。仅凭一个成功的 HTTP 状态码本身无法确定走的是哪条路由。
记录决策,但不记录机密
把发出请求的进程的环境与其启动器、容器或服务配置进行比较。记录变量名及其是否已设置、客户端版本、目标站点 scheme 和观测到的路由。避免整个环境的转储、shell 跟踪、详细协议日志和原始异常文本:代理 URL 中可能包含密码。
让获准的密钥管理器私下把所需凭据填充到进程环境中。不要把它们粘贴进命令、提交到环境文件,或把原始配置发给支持团队。环境变量是一种传递机制,而不是保险库。这个本地实验不使用任何凭据,也不涉及 ipvolt 的服务可用性。
上线前检查
- 确认实际使用的客户端、运行时版本和目标站点 scheme。
- 检查大写和小写的变量名,但不要记录它们的值。
- 在授权的目标站点上验证 NO_PROXY 匹配和选中的路由。
- 在添加重试之前,先把受控环境与显式配置进行对比。
参考来源与延伸阅读
本指南参考的技术资料。请以你所安装版本的文档以及代理服务商支持的配置为准。
- Everything curl: proxy environment variables and case rules
- curl manual: --disable, --proxy and --noproxy
- Requests: environment proxy configuration
- Python: proxy discovery and CGI handling
- Node.js 24.5.0: environment proxy support in HTTP clients
- Node.js HTTP documentation: built-in proxy and NO_PROXY rules