自建 LiteLLM Proxy 时,我遇到了一次稳定复现的 502:同一个模型,本机直连 LiteLLM 能正常返回流式响应,经过域名和 Cloudflare 却失败。排查期间还出现了大量异常 API 请求,一度让我怀疑服务被扫描流量拖垮。
最终定位到的原因是 Nginx 响应头缓冲区不足。绕过 Cloudflare 后,Nginx 仍返回 502;错误日志中的 upstream sent too big header while reading response header from upstream 则给出了直接证据。扫描流量是另一个独立问题,放在本文后半部分记录。
部署结构与故障现象
请求链路为:Client → Cloudflare → Nginx :443 → LiteLLM → OpenAI / Codex Backend。
LiteLLM 运行在 Docker 中,宿主机的 6111 端口映射到容器的 4000 端口。Nginx 将 https://one.fullstar.tech 转发到 http://127.0.0.1:6111。
通过公网域名请求 Responses API 时,返回的状态与响应头如下:
HTTP/2 502
server: cloudflare
Cloudflare 页面显示 “Browser Working / Cloudflare Working / Host Error”。这只能作为继续检查源站的线索,不能仅凭页面或 server: cloudflare 就认定故障出在 Cloudflare。
分层排查:从本机服务到反向代理
我先保持请求内容一致,逐次改变请求经过的链路。各次测试的结果如下:
| 测试路径 | 结果 | 定位意义 |
|---|---|---|
| 本机直连 LiteLLM Responses API | 200,SSE 正常 | 本次请求的应用与上游链路正常 |
| 本机直连 Chat Completions API | 正常 | 未复现接口兼容性问题 |
| 通过 Cloudflare 域名请求 | 502 | 故障出现在加入代理后的链路中 |
| 使用 curl –resolve 直连源站 Nginx | 502,server: nginx | 绕过 Cloudflare 仍可复现 |
| 检查 Nginx error.log | upstream sent too big header | 定位到上游响应头缓冲区 |
1. 确认 Docker 端口映射
先查看实际监听端口,避免把 LibreChat 和 LiteLLM 的端口混用:
sudo docker ps --format "table {{.Names}}\t{{.Ports}}"
NAMES PORTS
LibreChat 0.0.0.0:3080->3080/tcp
litellm 0.0.0.0:6111->4000/tcp
这里 3080 对应 LibreChat,6111 才是 LiteLLM 的宿主机端口。
2. 绕过两层代理,直接请求 LiteLLM
在服务器本机测试 Responses API。下文命令中的 YOUR_LITELLM_KEY 和 YOUR_SERVER_IP 均为占位符;模型名沿用本次部署中的配置。
curl -v http://127.0.0.1:6111/v1/responses \
-H "Authorization: Bearer YOUR_LITELLM_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6-terra",
"input": [
{
"role": "user",
"content": "Reply with exactly OK."
}
],
"reasoning": {
"effort": "medium"
},
"stream": true
}'
响应状态为 200,内容类型为 SSE:
HTTP/1.1 200 OK
server: uvicorn
content-type: text/event-stream
流中返回了文本 OK。以下是截取并省略部分字段的事件示意:
data: {"type":"response.output_text.delta", ... "delta":"OK" ...}
这说明本次测试中的 Docker 端口映射、LiteLLM、模型调用和流式返回都能工作,排查范围可以先缩小到 Cloudflare 与 Nginx 所在的代理链路。
3. 交叉测试 Chat Completions
为判断问题是否仅发生在 /v1/responses,我又直接测试了 /v1/chat/completions:
curl -v http://127.0.0.1:6111/v1/chat/completions \
-H "Authorization: Bearer YOUR_LITELLM_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6-terra",
"messages": [
{
"role": "user",
"content": "Reply with exactly OK."
}
],
"stream": true
}'
结果同样正常。至少在这两次本机请求中,没有复现应用接口故障。
4. 对比 Cloudflare 与源站直连
将同一份 Responses 请求改为访问公网域名后,仍得到 HTTP/2 502。此时还无法区分是 Cloudflare 回源失败,还是 Nginx 处理上游响应失败。
接着使用 curl --resolve,把域名临时解析到真实源站 IP,在请求仍使用该域名的情况下绕过 Cloudflare:
curl -vk \
--resolve one.fullstar.tech:443:YOUR_SERVER_IP \
https://one.fullstar.tech/v1/responses \
-H "Authorization: Bearer YOUR_LITELLM_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6-terra",
"input": [
{
"role": "user",
"content": "Reply with exactly OK."
}
],
"reasoning": {
"effort": "medium"
},
"stream": true
}'
这里保留了排查时使用的 -k,它会跳过 TLS 证书校验;证书校验正常时应去掉该选项。测试结果仍为:
HTTP/2 502
server: nginx/1.18.0 (Ubuntu)
绕过 Cloudflare 后,Nginx 自己仍返回 502。因此这次故障可以在 Nginx → LiteLLM 这一段继续定位。
5. 检查 proxy_pass 与错误日志
查看当前域名对应的 Nginx 配置:
sudo nginx -T | grep -A 40 -B 10 "one.fullstar.tech"
当时的配置如下:
server {
listen 443 ssl;
server_name one.fullstar.tech;
client_max_body_size 128m;
client_body_timeout 300;
ssl_certificate /etc/nginx/cert/one/one.crt;
ssl_certificate_key /etc/nginx/cert/one/one.key;
location / {
add_header Access-Control-Allow-Origin *;
proxy_pass http://127.0.0.1:6111;
}
}
proxy_pass 指向已测试成功的 127.0.0.1:6111。随后检查错误日志:
sudo tail -n 100 /var/log/nginx/error.log
日志中反复出现以下记录:
upstream sent too big header while reading response header from upstream,
server: one.fullstar.tech,
request: "POST /v1/responses HTTP/2.0",
upstream: "http://127.0.0.1:6111/v1/responses"
这条错误比 502 页面更具体:Nginx 在读取 upstream 响应头时遇到了超出缓冲区容量的头部。
根因:上游响应头超过 Nginx 缓冲区
直连 LiteLLM 时,可以看到它返回了多项自身元数据:
x-litellm-call-id
x-litellm-model-id
x-litellm-model-name
x-litellm-model-api-base
x-litellm-version
x-litellm-response-duration-ms
...
本次响应还携带了上游 Codex Backend 的元数据:
llm_provider-x-codex-active-limit
llm_provider-x-codex-plan-type
llm_provider-x-codex-primary-used-percent
llm_provider-x-codex-primary-reset-after-seconds
llm_provider-x-codex-primary-reset-at
...
其中包括较长的 llm_provider-x-codex-turn-state,以及 Cookie 等头部。这些字段叠加后,使本次响应头超过了 Nginx 的读取缓冲区。
这也解释了为什么本机直连成功,而经 Nginx 失败:直连请求没有经过 Nginx 的响应头读取环节。
修复配置与验证
我在 /etc/nginx/sites-enabled/one.conf 中调整了对应的 location。以下是本次部署最终使用的配置:
location / {
add_header Access-Control-Allow-Origin *;
proxy_pass http://127.0.0.1:6111;
proxy_http_version 1.1;
proxy_buffer_size 64k;
proxy_buffers 8 64k;
proxy_busy_buffers_size 128k;
proxy_buffering off;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
proxy_buffer_size 64k:本次修复 502 的关键,增大读取上游响应头的缓冲区(Nginx 文档)。proxy_buffers与proxy_busy_buffers_size:一并保留本次配置中的缓冲参数;不应把它们与响应头缓冲区混为一谈。proxy_buffering off:关闭代理响应缓冲,用于此次 SSE 流式场景。proxy_read_timeout与proxy_send_timeout:保留本次长连接场景采用的超时设置。
检查语法并重新加载
sudo nginx -t
确认检查成功后再重新加载 Nginx:
sudo systemctl reload nginx
本次不需要重启 LiteLLM。随后重新通过公网域名测试:
curl -v https://one.fullstar.tech/v1/responses \
-H "Authorization: Bearer YOUR_LITELLM_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-5.6-terra",
"input": [
{
"role": "user",
"content": "Reply with exactly OK."
}
],
"reasoning": {
"effort": "medium"
},
"stream": true
}'
请求恢复正常,SSE 返回 OK。从本机直连、绕过 CDN 到完整公网链路,前后测试形成了对应验证。
这次排查留下的经验
- 先测试 upstream。先确认本机服务能否处理同一份请求,再检查反向代理与 CDN。
- 用
curl --resolve对比源站。它适合在保持请求域名的情况下,判断绕过 CDN 后是否仍能复现。 - 尽早看 Nginx 错误日志。502 只是结果,
error.log通常能提供更明确的故障环节。 - 留意 AI 代理附带的响应元数据。本次 LiteLLM 与上游头部叠加,使默认缓冲区不够用;应根据实际日志和响应检查,而不是只猜模型 API 出错。
如果再次遇到类似问题,我会按 本机 upstream → 绕过 CDN → Nginx error.log → 对应配置或应用逻辑 的顺序排查。
另一个独立问题:公网 API 扫描
排查期间,LiteLLM Request Logs 中还出现了高频失败请求。请求尝试的 Key 呈现字典枚举特征,例如 12345、123456、111111 和 sk123456,并伴随大量不同的 Request ID / Session ID。
当时日志显示 Status: Failure、Duration: 0.00、Cost: -。这更符合 API 扫描和凭据猜测的特征;仅凭这些记录,没有证据表明对方已经获得有效 API Key。
它与此次 502 同时发生,但定位证据指向不同原因:一个是失败的认证请求,另一个是 Nginx 响应头缓冲区不足。
1. 使用强 API Key,并在边缘限制异常请求
LiteLLM 的 API Key 仍是应用层认证边界。即使弱 Key 枚举无法通过认证,请求也会建立连接、经过 Nginx、触发 LiteLLM 认证并写入日志,因此仍会消耗资源。
本次的防护思路是让明显异常的流量尽量在 Cloudflare 边缘被拦截,再由 LiteLLM 校验 API Key。
2. 为 /v1/* 设置合适的限流
我为机器调用的 /v1/* 设置了按来源 IP 计数的限流。本文记录的示例阈值是 每个 IP 每 10 秒 30 次请求,触发后封禁 10 秒。
这不是适合所有服务的通用阈值。LibreChat、OpenCode、Codex 和 Agent 的并行调用可能在短时间发出多次请求;阈值过低容易误伤正常使用。目标是降低单 IP 高频枚举与异常请求洪泛的影响。
3. 按私人服务的使用范围限制地域
对于只需在中国和美国使用的私人服务,本次采用了按域名限定的 Cloudflare Custom Rule:
(http.host eq "one.fullstar.tech"
and not ip.src.country in {"CN" "US"})
规则动作为 Block,即阻止该域名上来源国家不在 CN / US 内的请求。域名条件让这条规则限定于 one.fullstar.tech,而不是整个站点。地域范围需要按实际使用情况选择,也不能替代 API Key 认证。
4. 区分机器 API 与管理后台
| 入口 | 访问方式 | 本文采用或考虑的策略 |
|---|---|---|
/v1/* | 程序调用 | 限流、地域限制和 LiteLLM API Key |
/ui | 浏览器管理 | 可考虑 Cloudflare Access、人机验证或固定 IP 限制 |
不能直接要求普通 API 客户端完成浏览器式 Managed Challenge,否则正常机器请求也可能被挡住。
5. 关闭绕过 Cloudflare 的应用端口入口
当时 Docker 映射为 0.0.0.0:6111→4000/tcp,意味着端口监听所有网卡。如果安全组与防火墙也允许公网访问,就可以通过 http://SERVER_IP:6111 直接访问 LiteLLM,绕过 Cloudflare 上的规则。
在本文 Nginx 与 LiteLLM 位于同一宿主机的部署中,可将端口绑定改为本机地址:
ports:
- "127.0.0.1:6111:4000"
同时应核对安全组中的应用端口暴露情况。该修改针对的是 6111 的直连入口;源站 Nginx 的 443 端口是否允许绕过 Cloudflare 访问,需要另行检查(Cloudflare 源站保护文档),不能仅凭绑定本机端口就认为所有绕过路径都已关闭。
结语:用证据区分同时发生的问题
此次 502 的修复点是 Nginx 的 proxy_buffer_size;扫描流量则通过强 API Key、限流、按需地域限制和收紧端口暴露来处理。
两个问题同时出现,并不代表存在因果关系。保持测试请求一致、逐层缩小范围,再用错误日志验证,才避免了把“看到攻击流量”直接当成“服务返回 502”的原因。