起点
curl 选择代理的每一种方式:从单个 -x 参数到环境变量,以及决定请求能否成功的认证、SOCKS5 和隧道细节。
用 -x 为单个请求设置代理
-x 选项(长形式写作 --proxy)将单个 curl 请求经由代理转发。传给它代理的协议、主机和端口。省略协议时 curl 默认使用 http://,省略端口时默认使用 1080,因此两者都应显式写出。
协议描述的是到代理的连接,而不是目标站点。经普通 HTTP 代理访问 https:// 目标时仍然使用 -x http://host:port;curl 会通过它打开一条 CONNECT 隧道。只有当提供商在代理本身终止 TLS 时才写 -x https://host:port。curl 在此处还接受 socks4://、socks4a://、socks5:// 和 socks5h://。
下面的命令沿用了本指南其余部分的两个习惯:--disable 忽略可能自行添加代理的 curlrc,--noproxy '' 取消任何继承来的 no_proxy 排除项,确保请求真正经过你指定的网关。proxy.example.invalid 是刻意无法解析的;请替换为你提供商的网关。
curl -x · 经代理发送一个请求
curl --disable --noproxy '' \
-x http://proxy.example.invalid:8080 \
https://example.com/代理认证:-U、--proxy-user 与 407
代理凭据与目标站点凭据是分开的。-U 或 --proxy-user 把凭据发给代理;-u 或 --user 把凭据发给目标站点。两者混用会导致代理返回 407 或目标站点返回 401,即使密码本身是正确的。
你也可以把凭据嵌入代理 URL,写成 http://user:password@host:port。先对保留字符做百分号编码:密码中的 @ 必须写成 %40,否则 curl 会把它当作主机部分的开始。Basic 是默认的代理认证方案;当提供商有文档说明时,可用 --proxy-digest、--proxy-ntlm 和 --proxy-anyauth 选择其他方案。
这两种形式都会把密码放进进程参数列表和 shell 历史。应改为从变量读取,或使用下面诊断脚本中展示的 curl 8.3 的 --variable 与 --expand-proxy-user 形式,它在导入密钥时不会将其暴露为参数。
- connect=407 或消息 Received HTTP code 407 from proxy after CONNECT 表示代理拒绝了凭据或根本没有收到凭据。检查 -U 的值、编码以及提供商的用户名格式。
- 隧道已成功但目标站点返回 401,属于目标站点的问题;代理这一步已经正常工作。
- 许多提供商把国家、会话或协议选项编码在用户名中。严格遵循提供商文档中的格式;curl 会原样传递该字符串。
curl --proxy-user · 从环境变量读取凭据
: "${PROXY_USERNAME:?}" "${PROXY_PASSWORD:?}"
curl --disable --noproxy '' \
-x http://proxy.example.invalid:8080 \
--proxy-user "$PROXY_USERNAME:$PROXY_PASSWORD" \
https://example.com/环境变量:http_proxy、https_proxy、no_proxy 与 .curlrc
不带 -x 时,curl 会在环境中查找代理。它只读取小写的 http_proxy,因为大写名称可能被 CGI 请求头设置;它接受 https_proxy 或 HTTPS_PROXY、all_proxy 或 ALL_PROXY,以及 no_proxy 或 NO_PROXY。变量按目标站点的协议选择:https:// 目标使用 https_proxy,因此只导出 http_proxy 会让 HTTPS 流量继续直连。
no_proxy 是以逗号分隔的主机名或域名后缀列表,其中的条目绕过代理;单独一个 * 则完全禁用代理。命令行上的 -x 始终优先于环境变量,命令行上的 --noproxy 始终优先于 no_proxy。
~/.curlrc 文件可以通过 proxy = "http://host:port" 为每次调用设置代理,这在工作站上很方便,但在容器或 CI 任务里会令人意外。当脚本必须忽略它时,运行 curl 时把 --disable(或其短形式 -q)作为第一个参数。
http_proxy 与 https_proxy · shell 会话
export http_proxy='http://proxy.example.invalid:8080'
export https_proxy='http://proxy.example.invalid:8080'
export no_proxy='localhost,127.0.0.1,.internal.example'
curl https://example.com/ # uses https_proxy
curl --noproxy '*' https://example.com/ # bypasses the proxy once在 curl 中使用 SOCKS5:socks5 与 socks5h 的区别
curl 通过同一个 -x 选项使用 SOCKS。socks5:// 在你的机器上解析目标主机名,并向代理发送 IP 地址。socks5h:// 发送主机名并让代理解析,当目标必须从代理所在网络解析、或本地 DNS 完全不应看到目标时,这正是你需要的。长选项 --socks5 和 --socks5-hostname 与之等价。
SOCKS5 的用户名密码认证使用同一个 --proxy-user 选项。SOCKS 没有 CONNECT 状态码,因此握手被拒绝时表现为 curl 退出码 97 并附带简短原因,而不是 407。
curl socks5h · 代理侧 DNS
curl --disable --noproxy '' \
-x socks5h://proxy.example.invalid:1080 \
--proxy-user "$PROXY_USERNAME:$PROXY_PASSWORD" \
https://example.com/HTTPS 目标:CONNECT 隧道与 -v
经 HTTP 代理访问 https:// 目标时,curl 首先向代理发送 CONNECT example.com:443。只有在代理回应 200 之后,curl 才在隧道内与目标站点开始 TLS,因此代理永远看不到解密后的请求。加上 -v 运行可以观察这两个步骤;CONNECT 响应在任何目标站点响应头之前到达。-p 或 --proxytunnel 会对普通 http:// 目标强制使用同样的隧道。
对普通 http:// 目标则没有隧道。curl 把完整 URL 发给代理,由代理去获取,你看到的响应头可能来自任何一方。当需要把某个状态码归属到某一层时,优先使用隧道的情形,并用 %{http_connect} 输出变量单独读取 CONNECT 状态。
使用 https:// 代理时,curl 会同时验证代理和目标站点的证书。--proxy-cacert 为代理提供私有信任包;保持两项验证都开启,而不是求助于 --proxy-insecure 或 -k。
先发送一个有限制的请求作为基线
把下面的内容保存为 proxy-check.sh 并用 sh proxy-check.sh 运行。它需要 curl 8.3 或更高版本以及 HTTP Basic 代理认证。让你的密钥管理器填充 PROXY_USERNAME 和 PROXY_PASSWORD,并把 PROXY_URL 设为提供商网关(含协议和端口);https://proxy.example.invalid:8443 是一个刻意无法连接的示例值。
curl 自行导入凭据,因此展开后的密码永远不会成为 shell 参数。该命令打印目标站点状态、CONNECT 状态和耗时,并丢弃响应体。在加入浏览器、并发或应用代码之前,先保留这个基线。
proxy-check.sh · curl 8.3+
: "${PROXY_URL:?Set your provider proxy URL}"
curl --disable --silent --show-error --fail \
--noproxy '' \
--proxy "$PROXY_URL" \
--variable %PROXY_USERNAME \
--variable %PROXY_PASSWORD \
--expand-proxy-user '{{PROXY_USERNAME}}:{{PROXY_PASSWORD}}' \
--connect-timeout 10 --max-time 30 \
--output /dev/null \
--write-out 'http=%{http_code} connect=%{http_connect} seconds=%{time_total}\n' \
'https://example.com/'解读 curl 退出码并找出出问题的层
一次成功的响应只证明这个请求完成了;它并不能确定具体的出口国家或运营商。要确认这些,请使用提供商文档中的诊断端点,或你自己控制的、能报告观测到的源地址的端点。
在把你的应用与 curl 做对比时,保持相同的目标站点和凭据。一次改动三项设置,会让一次成功的重试难以解释。向支持人员提供退出码、耗时和脱敏后的状态,而不是包含认证数据的详细跟踪。
- 退出码 5,could not resolve proxy:网关主机名错误,或本地 DNS 无法解析它。
- 退出码 7,failed to connect:代理主机已解析,但端口关闭、被过滤或从本网络不可达。
- 退出码 28,operation timed out:先把 --connect-timeout 或 --max-time 调高一次以确认,再次出现则视为路由或容量问题。
- 退出码 56 且附带 Received HTTP code 407 from proxy after CONNECT:代理认证失败;先修正 -U,再去动目标站点的请求头。
- 退出码 97,proxy handshake error:SOCKS 握手被拒绝;检查 socks5 与 socks5h 协议的选择以及凭据。
- 退出码 35 或 60,TLS 失败:检查主机名、信任链和系统时钟。保持证书验证开启。
上线之前
- 使用提供商提供的真实网关,以及其文档中给出的协议和端口。
- 以私密方式注入凭据;不要把密钥粘贴进 shell 历史。
- 在测试并行流量之前,先记录一个有限制的基线。
- 在改动设置之前,先记下故障来自 CONNECT 步骤还是目标站点。
参考来源与延伸阅读
本指南参考的技术资料。请以你所安装版本的文档以及代理服务商支持的配置为准。