# Tunnel connection failed: 502 排查

Source: https://ipvolt.com/zh/guides/fix-proxy-tunnel-502-503
Markdown: https://ipvolt.com/zh/guides/fix-proxy-tunnel-502-503.md
Language: zh-CN

[ipvolt 首页](https://ipvolt.com/zh.md) / [指南](https://ipvolt.com/zh/guides.md) / Tunnel connection failed: 502 排查

故障排查
发布于: 2026-11-05
阅读约 7 分钟
作者： ipvolt

CONNECT 阶段的 502、503、504 来自代理；隧道建立后的 502 来自网站。本文说明如何在 curl、Python、Node.js 和 Playwright 中区分两者并分别修复。

在客户端建立代理隧道的过程中收到的 `502 Bad Gateway`、`503 Service Unavailable` 或 `504 Gateway Timeout` 来自代理：隧道从未建立，网站也从未看到你的请求。隧道建立之后收到的 `502` 来自网站，或者来自网站前面的某个组件。同一个数字，不同的责任方，不同的修复方法。下文中的每一条错误信息和每一个数字都来自 2026 年 11 月 5 日在本机回环地址上运行的实验，链接见文末。

## 502 是哪一方发出的

对于 `https://` URL，客户端会先用 `CONNECT host:443` 请求代理建立隧道（[RFC 9110](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)）。如果代理返回的不是 2xx，就没有隧道，大多数客户端会抛出异常。如果隧道建立后网站返回 502，你得到的是一个普通响应。

| 客户端 | 代理拒绝建立隧道 | 网站返回 502 |
| --- | --- | --- |
| curl 8.5.0 | 退出码 56，`curl: (56) CONNECT tunnel failed, response 502` | 退出码 0，`%{http_code}` 为 502，`%{http_connect}` 为 200 |
| curl 8.18.0 | 退出码 7，`curl: (7) CONNECT tunnel failed, response 502` | 退出码 0，`%{http_code}` 为 502，`%{http_connect}` 为 200 |
| Requests 2.34 | `requests.exceptions.ProxyError`，消息中包含 `Tunnel connection failed: 502 Bad Gateway` | `status_code == 502` 的 `Response` |
| HTTPX 0.28 | `httpx.ProxyError: 502 Bad Gateway` | `status_code == 502` 的 `Response` |
| aiohttp 3.14 | `ClientHttpProxyError: 502, message='Bad Gateway'` | `status == 502` 的响应 |
| Playwright 1.63 | `page.goto: net::ERR_TUNNEL_CONNECTION_FAILED` | `page.goto` 正常返回，`status()` 为 502 |
| Node.js 24 fetch | `TypeError: fetch failed`，`err.cause.cause.message` 为 `Proxy response (502) !== 200 when HTTP Tunneling` | `response.status` 为 502 |

503 和 504 的表现相同，只是数字不同，例如 `Tunnel connection failed: 503 Service Unavailable` 或 `curl: (7) CONNECT tunnel failed, response 504`。Chromium 在这三种情况下都报告 `net::ERR_TUNNEL_CONNECTION_FAILED`，不显示状态码；这一类错误见 [ERR_TUNNEL_CONNECTION_FAILED 指南](/zh/guides/fix-err-tunnel-connection-failed)。

Requests 显示 `Max retries exceeded`，但它默认并不重试：实验代理对每个请求只收到一次 CONNECT。Node 把状态码藏在两层 `cause` 之下，所以要记录完整的 cause 链。

有两种情况不符合这条规则。对于普通的 `http://` URL 没有隧道，因此 curl、Requests、HTTPX、aiohttp 和 Playwright 会把代理自己的 502 当作普通响应返回；请把它的 `Server` 头和响应体与网站平时的响应进行对比。Node 的 fetch 对 `http://` URL 也会建立隧道，所以在 Node 中它仍然是异常。而如果代理不返回任何状态就关闭连接，那就根本没有数字：curl 输出 `curl: (56) Proxy CONNECT aborted`，Requests 抛出带 `RemoteDisconnected('Remote end closed connection without response')` 的 `ConnectionError`，HTTPX 抛出 `RemoteProtocolError: Server disconnected without sending a response.`，Chromium 报告 `net::ERR_EMPTY_RESPONSE`。

## 用一条 curl 请求检查

通过同一个代理，打印出错 URL 的两个状态码：

```sh
curl --disable -sS -o /dev/null -x "$PROXY_URL" \
  -w 'connect=%{http_connect} http=%{http_code} exit=%{exitcode}\n' \
  https://example.com/
```

- `connect=502 http=000`：代理拒绝建立隧道；见下文的原因。
- `connect=200 http=502`：网站返回了 502；见[当 502 来自网站时](#当-502-来自网站时)。
- `connect=000` 且出现 `Proxy CONNECT aborted`：代理没有返回状态就关闭了连接。先检查端口。

## 代理一侧的原因

**从出口无法访问网站（502）。** 代理向目标发起的连接失败了；在实验中，被拒绝的连接在所有客户端里都变成了 `502 Bad Gateway`。请分别不经代理、以及经由其他出口或国家请求该 URL。如果只有一个出口失败，说明网站在那里不可用或屏蔽了那个网络，换一个出口即可。如果所有出口都失败，请检查 CONNECT 行中的主机名和端口。

**出口离线或正在轮换（503，有时是 502）。** 网关无处转发请求。按下文的方法对幂等请求做带退避的重试，并在服务商的控制面板中检查出口状态。

**代理等待超时（504）。** 代理连接网站的期限到了。实验代理等待 3 秒，所有客户端都在 3.0 到 3.5 秒后失败。客户端的超时要比代理的期限长，否则你看到的是自己的超时而不是 504；[代理超时排查指南](/zh/guides/proxy-timeout-troubleshooting)讲解了如何区分两者。

**端口或协议不对。** 我们把所有客户端当作 HTTP 代理，指向一个只支持 SOCKS5 的端口（pproxy 2.7.9）。服务器没有回复就关闭了连接，所以根本没有 502：curl 8.5.0 和 8.18.0 输出 `curl: (56) Proxy CONNECT aborted`，aiohttp 抛出 `ServerDisconnectedError`，Node 的 fetch 直到 10 秒超时触发才结束。请从服务商的配置页面复制协议、主机和端口。在普通 HTTP 端口上使用 `https://` 代理 URL 会以另一种方式失败；见[代理下的 SSL WRONG_VERSION_NUMBER](/zh/guides/fix-ssl-wrong-version-number-proxy)。

**服务商容量不足。** 容量耗尽的网关可能对新隧道返回 502 或 503。降低并发、等待一段时间，并把失败的 UTC 时间发给服务商。

在同一步骤中，407 是认证问题（[修复代理 407 错误](/zh/guides/fix-proxy-error-407)），403 是有意拒绝（[CONNECT tunnel failed, response 403](/zh/blog/connect-tunnel-failed-403)）。

## 移动代理与 IP 轮换

移动代理的出口是运营商网络中的一台设备。它更换 IP 时，可能会有片刻没有可用出口；视网关而定，客户端此时会在 CONNECT 阶段看到 502 或 503，或者连接被关闭，报告方式与上文相同。我们没有测量真实的轮换，所以本文不给出时长。如果轮换由你发起，请暂停请求，直到一个低成本的测试请求成功。

实验模拟了一个离线 4 秒的出口，每秒用 Requests 发送一次请求。第 0 到第 3 秒都以 `Tunnel connection failed: 503 Service Unavailable` 失败；从第 4 秒开始每个请求都返回 200。4 秒是实验自己的设置：它展示的是轮换的形态，而不是时长。

内置的重试对两侧的处理不同。`curl --retry 2` 只向出错的代理发送了一次 CONNECT 就停止了，却向网站请求了三次 502；加上 `--retry-all-errors` 后它也会重复 CONNECT。urllib3 的 `Retry(total=2, status_forcelist=[502, 503, 504])` 两种都会重复。Node 的 fetch 在收到状态码后不做任何重试，但当代理不返回状态就关闭连接时，它在 10 秒内重新连接了 160,357 次，直到 `AbortSignal.timeout` 将其停止。请始终传入 signal。

只重试 GET 请求。失败的 CONNECT 从未到达网站，但网站返回的 502 可能出现在网站已经完成工作之后，重试的 POST 就会执行两次；见[丢失一个响应，创建两个任务](/zh/blog/proxy-retries-duplicate-jobs)。下面的函数在代理或网站返回 502、503 或 504 后，以有上限、带随机抖动的退避重试 GET，遇到其他情况（例如 407）就停止：

```python
import random, re, time
import requests

RETRYABLE = {502, 503, 504}
TUNNEL_STATUS = re.compile(r"Tunnel connection failed: (\d{3})")

def get_with_retry(session, url, attempts=5, base_delay=1.0, max_delay=8.0, **kwargs):
    for attempt in range(1, attempts + 1):
        try:
            response = session.get(url, **kwargs)
        except requests.exceptions.ProxyError as exc:
            match = TUNNEL_STATUS.search(str(exc))  # Requests reports the CONNECT status only as text
            if match is None or int(match.group(1)) not in RETRYABLE or attempt == attempts:
                raise
        else:
            if response.status_code not in RETRYABLE or attempt == attempts:
                return response
        time.sleep(min(max_delay, base_delay * 2 ** (attempt - 1)) * random.uniform(0.5, 1.0))
```

在记录的运行中，它在第四次尝试时、5.7 秒后度过了 4 秒的离线窗口。面对持续返回 502 的网站，它在五次尝试、9.9 秒后放弃，并返回该 502。

## 当 502 来自网站时

如果隧道已经建立，代理就完成了它的工作：502 来自网站的网关、负载均衡器或 CDN，它们没有从后面的应用拿到可用的响应。更换代理或轮换 IP 都解决不了。请不经代理请求该 URL；如果同样失败，就等待并用上面的函数重试 GET。如果只在经过代理时失败，网站可能在向那个网络返回错误；在更换出口之前，把响应体和响应头与直接请求进行对比。

## 复现实验

[下载目录](/downloads/fix-proxy-tunnel-502-503/README.md)中有 `lab.mjs`（一个在 `127.0.0.1` 上运行、对 CONNECT 返回 502、503 或 504 或直接断开连接的代理，以及一个自己返回 502 的 TLS 源站）、每个客户端各一个脚本、`wrong_port.mjs`、`node_checks.mjs`，以及包含上面那个函数的 `retry_get.py`。[results.json](/downloads/fix-proxy-tunnel-502-503/results.json) 记录了版本：curl 8.5.0 和 8.18.0、Python 3.14.4、Requests 2.34.2、urllib3 2.8.0、HTTPX 0.28.1、aiohttp 3.14.5、Node.js 24.22.1（undici 7.30.1）以及 Playwright 1.63.0（Chromium 153）。未测试：macOS 和 Windows、SOCKS 和 HTTPS 代理，以及真实服务商的网关。

在编程智能体中，[Proxy Toolkit MCP](/mcp) 提供错误诊断；把客户端和脱敏后的错误信息交给它，切勿提供凭据。

ipvolt 的[移动代理](/pricing)是运营商网络上的专用设备；你可以按需或按计划轮换 IP，因此轮换不会让你措手不及。

## 参考来源与延伸阅读

- [RFC 9110: CONNECT](https://www.rfc-editor.org/rfc/rfc9110.html#name-connect)
- [RFC 9110: 502 Bad Gateway](https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway)
- [RFC 9110: 503 Service Unavailable](https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable)
- [RFC 9110: 504 Gateway Timeout](https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout)
- [curl manual: --retry](https://curl.se/docs/manpage.html#--retry)
- [curl manual: --retry-all-errors](https://curl.se/docs/manpage.html#--retry-all-errors)
- [urllib3 documentation: Retry](https://urllib3.readthedocs.io/en/stable/reference/urllib3.util.html#urllib3.util.Retry)
- [Requests documentation: proxies](https://requests.readthedocs.io/en/latest/user/advanced/#proxies)
- [HTTPX documentation: proxies](https://www.python-httpx.org/advanced/proxies/)
- [aiohttp documentation: proxy support](https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support)
- [Playwright: HTTP proxy](https://playwright.dev/docs/network#http-proxy)
- [Node.js CLI documentation: NODE_USE_ENV_PROXY](https://nodejs.org/api/cli.html#node_use_env_proxy1)
- [pproxy on PyPI](https://pypi.org/project/pproxy/)

## 相关指南

- [不靠猜测修复代理错误 407](https://ipvolt.com/zh/guides/fix-proxy-error-407.md)
- [修复 ERR_TUNNEL_CONNECTION_FAILED 错误](https://ipvolt.com/zh/guides/fix-err-tunnel-connection-failed.md)
- [一次一个阶段地排查代理超时](https://ipvolt.com/zh/guides/proxy-timeout-troubleshooting.md)

## 关于 ipvolt

示例使用通用的代理设置，并附上原始技术文档链接。具体产品的行为请向你的服务商确认。ipvolt 仍在开发中。

[阅读英文原文](https://ipvolt.com/guides/fix-proxy-tunnel-502-503.md)

## 了解何时开放体验。

ipvolt · 开发中

我们正在为开发者和数据团队打造代理基础设施。加入意向名单，ipvolt 就绪时第一时间收到通知。

开放体验时仅发一封通知邮件，不发送其他邮件。

[申请抢先体验](https://ipvolt.com/zh/guides/fix-proxy-tunnel-502-503#waitlist-closing)

[隐私政策](https://ipvolt.com/privacy)

