API 502 Bad Gateway 排错:区分上游错误响应与 504 超时
502 表示上游确实给了回复,但代理认为这个回复无效。和 504 正好相反:504 是没回复,502 是有回复但不能用。重试之前先看清日志到底叫的是哪种失败,因为 502、503、504 各自的修法并不一样。
HTTP 502 的真实含义
RFC 9110 对 502 Bad Gateway 的定义是:作为网关或代理的服务器从上游服务器收到了一个无效响应。状态码标识的是把失败抛给客户端的那一层代理,而不是真正出错的上游。这里的"上游"可以是模型服务商、其前方的 CDN、内部服务,或者公网路由前方的 OpenAI 兼容中转层。
API 504 Gateway Timeout 排错 覆盖的是"没回复"的情形。本文覆盖的是"回复无效"的情形。二者属于同一族代理错误状态码,但需要不同的诊断路径。
502 vs 504 vs 503:先看 latency
通常可以用客户端 latency_ms 与响应体把它们区分开。数字取自网关主机上 journalctl -u sub2api --since '24 hours ago' 的真实扫描,已匿名化为模型 gpt-5.6-sol 与账号 id 16,并非人为构造。
| 状态码 | 延迟区间(24 小时) | 次数 | 延迟区间透露的信息 |
|---|---|---|---|
| 502 | 110-1232 毫秒 | 47 | 中转层确实联系了上游,上游确实回复了,但回复内容无效。中转层在切换若干次后放弃。 |
| 503 no available accounts | 13-25 毫秒 | 235 | 中转层根本没联系到上游。要么账号池空了,要么该模型下所有账号都被排除。 |
| 200(其余流量) | 不固定 | 1457+ | 同一模型下的健康路径。 |
503 配 30 毫秒以内的延迟,说明中转层根本没走到上游 —— 这是运营侧的账号池耗尽,不是网络问题。502 配 100-1500 毫秒的延迟,说明中转层确实联系了上游,且上游回了无效响应。把二者混在一起处理会得出错误的修法。
先跑一次最小的有效探测
在假设上游有问题之前,先单独验证鉴权。模型列表路由很轻,不会调用推理模型。
: "${MONKEYAI_API_KEY:?请先设置 MONKEYAI_API_KEY}"
curl -i https://monkeytheme.com/v1/models \
-H "Authorization: Bearer ***"200 的 JSON 模型列表说明 Key 能到达模型列表路由。401 是鉴权失败,不是 502。404 说明路由不对,也不是上游问题。
如果 /v1/models 返回 200,但具体聊天调用仍然返回 502,问题在推理路径,不在中转层或你的凭据。把测试请求压成一个短 prompt 加小的输出上限,模型 id 保持不变,这样每次只动一个变量。
一条命令同时捕获状态码、响应头与时延
把响应头和响应体分别写到临时文件,并打印连接耗时、首字节耗时、总耗时。一个带响应体的 502 远远比一个只有状态码的 502 更有用。
: "${MONKEYAI_API_KEY:?请先设置 MONKEYAI_API_KEY}"
workdir=$(mktemp -d)
curl -sS \
-D "$workdir/headers.txt" \
-o "$workdir/body.txt" \
-w 'status=%{http_code}\nconnect=%{time_connect}s\nfirst_byte=%{time_starttransfer}s\ntotal=%{time_total}s\n' \
https://monkeytheme.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ***" \
-d '{"model":"gpt-5.6","messages":[{"role":"user","content":"hi"}],"max_tokens":16}'
printf 'Saved to %s\n' "$workdir"如果 body.txt 是 JSON,先去掉隐私字段再保留 error type 和 message。如果 body.txt 是 HTML 网关页面,也要记下来 —— CDN 边缘的 HTML 502 和中转层结构化 JSON 502 描述的是不同的故障层。发布 headers.txt 前先检查是否包含 cookie、trace 数据等敏感信息。
502 在生产日志里长什么样
下面的日志形态来自与上面延迟表同一个 24 小时窗口。这个窗口内每一条 502 之前都有一条 upstream_failover_switching 警告,upstream_status 是 401,且字段中只出现单个账号 id(16)。这一形态就是判断"上游回复无效"与"上游完全不回复"的分界。
# (1) 上游返回 401,中转层切换账号
... openai_chat_completions.upstream_failover_switching {
"model": "gpt-5.6-sol", "stream": true,
"account_id": 16, "upstream_status": 401,
"switch_count": 1, "max_switches": 10
}
# (2) 中转层账号池耗尽,没有备用账号
... openai_chat_completions.account_select_failed {
"model": "gpt-5.6-sol", "stream": true,
"error": "no available accounts", "excluded_account_count": 1
}
# (3) 最终在大约 448 毫秒后向客户端返回 502
... http request completed {
"path": "/v1/chat/completions", "method": "POST",
"status_code": 502, "latency_ms": 448,
"account_id": 16, "platform": "openai", "model": "gpt-5.6-sol"
}三个信号一眼就能区分"上游回复无效"的 502 与"无可用账号"的 503:
| 日志里的信号 | 含义 |
|---|---|
upstream_failover_switching 且 switch_count 不为零 | 中转层确实联系了上游,且回复触发了账号切换 |
upstream_status 字段存在且不是 200 | 上游回了一个具体的 HTTP 状态码,此处中转层认为不可重试 |
最终 502 行的 latency_ms 落在 100-1500 毫秒 | 整条上游往返都跑完了,这不是"无回复超时" |
重试之前先确认不会重蹈覆辙
在坏上游是瞬时故障且中转层可以切到健康账号时,对 502 做有限次数的重试是合理的。如果每次重试都打到同一个账号或同一条上游路径,就不要再重试了。
- 确认响应里没有
Retry-After头再继续;如果有,先尊重它。 - 使用带抖动的指数退避;一次重试通常就足以区分"真故障"与"偶发抖动"。
- 如果两次重试的响应体完全一致,停下来 —— 第三次重试只会重复同样的失败。
- 把上面捕获的证据发给支持。一条脱敏后的
request_id加上模型 id 通常足够中转层运营方找到对应的上游日志。
max_retries=0 调成 0;之后把刻意的重试策略恢复回来。运营侧的逐层排查
运营方应把客户端时间戳与反向代理、应用日志对齐。重点核对上游地址、连接耗时、首字节耗时、响应状态、TLS 握手,以及客户端是否收到了任何字节。
| 层 | 要看的信号 | 去哪看 |
|---|---|---|
| 公网 CDN 边缘(Cloudflare 等) | HTML 502 页面,或响应头里带 cf-ray 与边缘状态码的 5xx | 边缘访问日志或公网路由前方的 Worker |
| 反向代理(Nginx、Caddy 等) | upstream prematurely closed connection、来自 proxy_intercept_errors 的 502 | Nginx error.log;核对 proxy_next_upstream 配置 |
| 应用中转层 | 502 行之前的 upstream_failover_switching 警告,紧接着的 account_select_failed | 应用的结构化日志,用 request_id 做关联 |
| 上游模型服务商 | 5xx、空响应体,或上游连接上的 TCP RST | 服务商的状态页;若可用,看单账号配额视图 |
把每一处超时都无脑调大是粗暴的修法。改生产配置前先确认 502 出自哪一层,CDN 层的 502 不会因为把应用读超时调大而好转。
参考资料
常见问题
502 表示我的 API Key 失效了吗?
多数情况下不是。鉴权失败对应的状态码是 401。502 表示上游返回了一个无效或为空的响应,而中转层已经接受了你的请求。
502 和 504 有什么区别?
502 Bad Gateway 指上游回复了内容,但中转层认为该内容无效。504 Gateway Timeout 指上游在代理读超时内完全没有回复。同族错误,反向失败模式。
怎么和 503 "no available accounts" 区分?
503 配合 30 毫秒以内的延迟与 account_select_failed 日志,说明中转层根本没走到上游。502 配合 100-1500 毫秒的延迟与 upstream_failover_switching 日志,说明中转层确实走到了上游,且上游回了无效响应。
重试能修好 502 吗?
有时可以,前提是坏上游是瞬时的,且中转层能切到不同的账号或路由。如果每次重试都打同一账号,重试只会重复同样的失败,并烧掉限速预算。
看到 502 时应该切换 Base URL 还是模型?
如果有替代模型,先切换模型,502 可能只发生在某一个上游账号上。在确认 502 出在公网端点之前,不要修改 Base URL。