Fullstar

Archives

  • August 2026
  • December 2025
  • August 2024
  • July 2024
  • February 2024
  • November 2023
  • August 2023
  • July 2023
  • January 2023
  • November 2022
  • October 2022
  • September 2022
  • February 2022
  • January 2022
  • September 2021
  • January 2021
  • December 2020
  • November 2020
  • October 2020
  • September 2020
  • August 2020
  • July 2020

Categories

  • Code
  • Lens
  • Life
0
Fullstar
  • Code

排查 LiteLLM 经 Cloudflare 访问出现 502:Nginx Response Header 太大

  • August 16, 2026
  • Brandon

Looking for a Shorter Overview?

AI Summary

公网经 Cloudflare 访问 LiteLLM 的 502,根因并非 Cloudflare 或 LiteLLM,而是 LiteLLM 返回的响应头超过了 Nginx 默认 upstream 缓冲区。通过直连 upstream、`curl --resolve` 绕过 CDN 和检查 `error.log` 可快速定位;增大 `proxy_buffer_size`、关闭 SSE 缓冲,并将 LiteLLM 仅绑定到本机可同时改善可用性与安全性。

Key Moments

AI-generated
1

用分层测试锁定故障层

先直连 `127.0.0.1:6111`,再用 `curl --resolve` 保持域名和 HTTPS 直连源站,排除了 LiteLLM 与 Cloudflare。
2

从错误日志确认超大响应头

Nginx 的 `upstream sent too big header` 明确表明 LiteLLM 返回的响应头超过 upstream 读取缓冲区。
3

调整 Nginx 支持 SSE

设置 `proxy_buffer_size 64k` 等缓冲参数、关闭 `proxy_buffering` 并增加超时后,流式 Responses 请求恢复正常。
4

封闭源站并分层防护 API

将 LiteLLM 端口绑定到 `127.0.0.1`,结合 Cloudflare 限流、地域限制和强 API Key,防止绕过 CDN 的扫描与撞库。
Total
0
Shares
0
0
0

最近在自建 LiteLLM Proxy 时遇到了一个比较有意思的问题:

同一个模型,通过服务器本机直接访问 LiteLLM 一切正常,但经过域名和 Cloudflare 后,请求却稳定返回:

502 Bad Gateway

开始时我怀疑过 Cloudflare、LiteLLM、Responses API,甚至一度怀疑是不是最近公网扫描流量导致服务异常。

最后经过逐层排查发现:

真正返回 502 的并不是 Cloudflare,也不是 LiteLLM,而是 Nginx。LiteLLM 返回的 Response Header 太大,超过了 Nginx 默认的 upstream header buffer。

最终的错误日志非常明确:

upstream sent too big header while reading response header from upstream

这篇文章记录完整的定位过程。

我的部署结构

大致架构如下:

Client
  │
  ▼
Cloudflare
  │
  ▼
Nginx :443
  │
  ▼
LiteLLM :6111
  │
  ▼
OpenAI / Codex Backend

LiteLLM 运行在 Docker 中:

litellm    0.0.0.0:6111->4000/tcp

也就是:

Host :6111
    ↓
Container :4000

Nginx 则负责:

https://one.fullstar.tech
        ↓
http://127.0.0.1:6111

问题表现

通过公网域名请求 LiteLLM:

https://one.fullstar.tech/v1/responses

会返回:

HTTP/2 502
server: cloudflare

Cloudflare 的错误页面显示:

Browser     Working
Cloudflare  Working
Host        Error

这很容易让人第一反应认为是:

  • LiteLLM 挂了
  • Cloudflare 回源失败
  • Docker 网络异常
  • LiteLLM Responses API 有 Bug
  • 上游 Codex API 不稳定

但需要继续逐层排除。


第一步:确认实际 LiteLLM 端口

首先查看 Docker 端口:

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

第二步:绕过 Nginx 和 Cloudflare,直接测试 LiteLLM

接下来直接请求本机 LiteLLM。

我使用的测试 Payload:

{
  "model": "gpt-5.6-terra",
  "input": [
    {
      "role": "user",
      "content": "Reply with exactly OK."
    }
  ],
  "reasoning": {
    "effort": "medium"
  },
  "stream": true
}

完整测试命令:

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
  }'

结果:

HTTP/1.1 200 OK
server: uvicorn
content-type: text/event-stream

并且 SSE 正常返回:

data: {"type":"response.output_text.delta", ... "delta":"OK" ...}

最终:

OK

这一步非常重要。

它证明:

LiteLLM ✅
Docker 端口映射 ✅
gpt-5.6-terra ✅
Responses API ✅
Streaming ✅
LiteLLM → 上游 Codex ✅

所以问题已经可以缩小到:

Cloudflare / Nginx

这一层。


第三步:测试 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
  }'

结果同样正常。

所以 LiteLLM 本身基本可以完全排除。


第四步:通过 Cloudflare 域名请求

接下来发送完全相同的 Responses 请求,只把地址改成:

https://one.fullstar.tech/v1/responses

此时得到:

HTTP/2 502
server: cloudflare
cf-ray: ...

问题出现。

此时链路为:

Client
  ↓
Cloudflare
  ↓
Nginx
  ↓
LiteLLM

但还无法确定究竟是:

Cloudflare → Nginx

还是:

Nginx → LiteLLM

出现问题。


第五步:使用 –resolve 绕过 Cloudflare

这是整个排障过程中非常关键的一步。

使用 curl 的:

--resolve

直接把:

one.fullstar.tech

解析到服务器真实公网 IP,而不是 Cloudflare IP。

例如:

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
  }'

这时候请求路径变成:

Client
  │
  │  绕过 Cloudflare
  ▼
Server Public IP :443
  │
  ▼
Nginx
  │
  ▼
LiteLLM

结果依然是:

HTTP/2 502
server: nginx/1.18.0 (Ubuntu)

这一步直接证明:

Cloudflare 是无辜的。

因为完全绕过 Cloudflare 后,Nginx 自己仍然返回 502。

因此问题正式缩小到:

Nginx
  ↓
LiteLLM

第六步:检查 Nginx 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 没有问题:

Nginx
  ↓
127.0.0.1:6111
  ↓
LiteLLM

而 127.0.0.1:6111 本身也已经通过 curl 验证可以正常访问。

于是只剩 Nginx 处理 upstream response 本身的问题。


第七步:查看 Nginx error.log

运行:

sudo tail -n 100 /var/log/nginx/error.log

终于看到真正的错误:

upstream sent too big header while reading response header from upstream

而且不是偶发一次,而是大量重复:

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"

问题完全定位。


根因:LiteLLM Response Header 太大

为什么 LiteLLM 的 Header 会这么大?

直接访问 LiteLLM 时可以看到大量 Header,例如:

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 的 Response Header 也被 LiteLLM 带了出来:

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 等 Header。

最终形成:

LiteLLM
  │
  │ 巨大的 Response Header
  ▼
Nginx
  │
  │ 默认 proxy header buffer 不够
  ▼
upstream sent too big header
  │
  ▼
502 Bad Gateway

这也很好地解释了为什么:

curl 127.0.0.1:6111

完全正常。

因为此时根本没有经过 Nginx。


最终解决方案

修改:

/etc/nginx/sites-enabled/one.conf

原始配置:

location / {
    add_header Access-Control-Allow-Origin *;
    proxy_pass http://127.0.0.1:6111;
}

改成:

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;
}

其中解决这次 502 最关键的是:

proxy_buffer_size 64k;

因为它决定 Nginx 读取 upstream response header 时使用的缓冲区大小。

同时考虑到 LiteLLM 使用 SSE Streaming:

Content-Type: text/event-stream

关闭:

proxy_buffering off;

也更加合适。

并把长连接 timeout 调大:

proxy_read_timeout 600s;
proxy_send_timeout 600s;

检查配置

修改完成后先检查语法:

sudo nginx -t

得到:

nginx: configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

然后 reload:

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
  }'

此时请求恢复正常。

整个链路变成:

Client
  ↓
Cloudflare
  ↓
Nginx
  ↓
LiteLLM
  ↓
Codex Backend
  ↓
SSE Streaming
  ↓
OK

问题解决。


整个定位过程总结

这次最有效的不是不断修改配置,而是逐层缩小问题范围。

完整过程可以总结为:

1. 公网域名请求
   ↓
   502

2. 直接请求 127.0.0.1:6111
   ↓
   200
   ↓
   排除 LiteLLM

3. 测试 Responses + Chat Completions
   ↓
   都正常
   ↓
   排除 endpoint compatibility 问题

4. 通过 Cloudflare 请求
   ↓
   502

5. curl --resolve 绕过 Cloudflare
   ↓
   Nginx 仍然 502
   ↓
   排除 Cloudflare

6. 检查 proxy_pass
   ↓
   127.0.0.1:6111 配置正确

7. 查看 /var/log/nginx/error.log
   ↓
   upstream sent too big header

8. 增大 proxy_buffer_size
   ↓
   reload nginx

9. 再次测试
   ↓
   200 OK

也就是:

最开始怀疑:

Cloudflare?
LiteLLM?
Responses API?
Codex?
Docker?

最终:

Nginx proxy response header buffer 太小。

几个值得记录的经验

1. Cloudflare 的 502 页面不等于 Cloudflare 出问题

如果看到:

Browser     Working
Cloudflare  Working
Host        Error

应该优先考虑:

Origin / Reverse Proxy

Cloudflare 很可能只是把源站返回的错误展示出来。

2. curl --resolve 非常适合排查 CDN / 反代问题

例如:

curl --resolve example.com:443:ORIGIN_IP https://example.com/

可以在保持:

Host
SNI
HTTPS

基本不变的情况下直接访问真实源站。

对于判断:

到底是 Cloudflare 有问题
还是我的服务器有问题

非常有效。

3. 一定要直接测试 upstream

如果:

Nginx → LiteLLM

出问题,那么第一件事应该测试:

curl http://127.0.0.1:6111

不要从最外面的 Cloudflare 开始猜。

4. error.log 往往比 502 页面有价值

浏览器只能告诉我:

502 Bad Gateway

但:

/var/log/nginx/error.log

直接告诉我:

upstream sent too big header while reading response header from upstream

两者的信息量完全不是一个级别。

5. AI API 的 Response Header 可能比普通 Web API 大很多

尤其是存在:

LiteLLM
+
Provider metadata
+
Codex usage metadata
+
Turn state
+
Streaming

这类组合时,不能默认认为 Nginx 的默认 buffer 一定够用。


最终配置

目前 LiteLLM 的 Nginx 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;
}

如果之后再遇到类似:

502 Bad Gateway

我的排查顺序会直接变成:

本机 upstream
→ 绕过 CDN
→ Nginx error.log
→ 最后才研究应用层

比一开始从 Cloudflare、LiteLLM 和模型 API 一层层猜要快得多。

By the way:LiteLLM 公网接口还在被自动化扫描

排查 502 的过程中,还顺手发现了另一个问题。

LiteLLM 的 Request Logs 中突然出现了大量失败请求,而且请求频率明显异常。

更关键的是,请求中使用的 Key Hash / Key 值呈现出非常明显的字典枚举特征,例如:

12345
123456
123456789
111111
000000
sk12345
sk123456
sk666666
...

同时这些请求具有几个共同特点:

Status      Failure
Duration    0.00
Cost        -

并且短时间内出现了大量不同的 Request ID / Session ID。

这种模式基本可以判断为:

公网扫描器发现了 OpenAI Compatible API Endpoint,随后开始使用常见弱 Key 对接口进行自动化枚举。

严格来说,这更准确地叫:

API scanning
+
credential guessing
+
dictionary attack

而不是已经“入侵成功”。

从当时的 LiteLLM 日志来看,这些请求全部失败,也没有产生模型调用 Cost,因此没有证据表明攻击者真正获得了有效 API Key。

但既然 LiteLLM 已经直接暴露到了公网,还是顺手加了一层 Cloudflare 防护。


第一层:LiteLLM 自己的 API Key

原本的第一道防线仍然是 LiteLLM:

Internet
   ↓
LiteLLM
   ↓
API Key 校验

只要 API Key 是足够长的随机值,而不是:

123456
password
litellm
sk123456

这类弱密码,那么普通字典爆破成功的概率实际上非常低。

但问题在于:

即使猜不中 Key,攻击请求还是会真实到达自己的服务器。

也就是说,它仍然会:

  • 建立连接
  • 进入 Nginx
  • 到达 LiteLLM
  • 触发认证逻辑
  • 写 Request Log
  • 消耗一定 CPU / IO / 数据库资源

因此更合理的方式是:

尽量在 Cloudflare 边缘节点就把明显异常的请求拦掉。

最终结构变成:

Internet
   ↓
Cloudflare
   ↓
WAF / Rate Limit
   ↓
Nginx
   ↓
LiteLLM API Key
   ↓
LLM Provider

第二层:给 /v1/* 增加 Rate Limit

首先在 Cloudflare 中进入:

Security
→ WAF
→ Rate limiting rules

针对 LiteLLM 的 OpenAI Compatible Endpoint:

/v1/*

增加限流。

我的思路是按来源 IP 独立计数,例如:

30 requests / 10 seconds / IP

达到阈值后:

Block 10 seconds

也就是:

某个 IP
   ↓
10 秒内请求超过 30 次
   ↓
Cloudflare Block

这里没有把阈值设置得特别低。

原因是 LiteLLM 后面可能接:

LibreChat
OpenCode
Codex
Agent
并行 Tool Calls

正常使用时,本身就有可能在短时间产生多次 API 请求。

如果设置成:

5 requests / 10s

很容易误伤自己的 Agent。

所以 Rate Limit 的目标不是:

每一个扫描请求都挡住。

而是:

防止单 IP 高频撞库以及异常请求洪泛。


第三层:按国家限制私人 LiteLLM

由于这个 LiteLLM 实际上是私人服务,因此还可以采用一个非常简单但有效的策略:

只允许自己可能使用的国家访问。

例如我平时只需要:

China
United States

于是增加了一条 Cloudflare Custom Rule。

逻辑为:

Hostname = one.fullstar.tech

AND

Country != China

AND

Country != United States

Action:

Block

等价 expression 可以写成:

(http.host eq "one.fullstar.tech"
 and not ip.src.country in {"CN" "US"})

效果就是:

CN → Allow
US → Allow

其他国家 → Block

而且因为加入了:

http.host eq "one.fullstar.tech"

所以不会影响 fullstar.tech 下的其他网站。

这对于私人 API 是一种性价比非常高的防御方式。



第四层:管理后台和 API 应该区别对待

LiteLLM 本身其实存在两种完全不同的流量:

/v1/*

这是机器调用的 API。

而:

/ui

则属于人使用的管理后台。

因此它们不应该使用完全一样的 Cloudflare 策略。

例如:

/v1/*

不适合直接加:

Managed Challenge

因为 OpenCode、LibreChat、Codex 等程序并不会像浏览器一样完成 Cloudflare 人机验证。

否则:

正常 API Client
   ↓
Cloudflare Challenge
   ↓
无法通过
   ↓
API 全挂

所以 /v1/* 更适合:

Rate Limit
+
Geo Restriction
+
LiteLLM API Key

而 /ui 这种管理后台则可以考虑:

Cloudflare Access

或者:

Managed Challenge

甚至只允许自己的固定 IP。


一个额外的安全问题:不要让源站绕过 Cloudflare

这里还有一个很重要的问题。

我的 LiteLLM Docker 当时是:

0.0.0.0:6111->4000/tcp

这意味着 LiteLLM 的 6111 端口绑定在所有网卡上。

如果云厂商 Security Group / iptables 同时允许公网访问 6111,那么攻击者理论上可以直接请求:

http://SERVER_IP:6111

此时整个 Cloudflare:

WAF
Rate Limit
Country Block

都会被直接绕过。

因为请求路径变成了:

Attacker
   ↓
SERVER_IP:6111
   ↓
LiteLLM

而不是:

Attacker
   ↓
Cloudflare
   ↓
Nginx
   ↓
LiteLLM

因此更合理的方式是只让 LiteLLM 监听本机:

ports:
  - "127.0.0.1:6111:4000"

这样:

公网 → 6111

无法直接访问。

只有:

Nginx → 127.0.0.1:6111

可以连接 LiteLLM。

或者至少在云厂商安全组中关闭公网 6111 端口。

最终真正希望得到的结构是:

                   Internet
                       │
                       ▼
                  Cloudflare
                       │
          ┌────────────┴────────────┐
          │                         │
     Rate Limit                Custom Rules
          │                         │
          └────────────┬────────────┘
                       ▼
                     Nginx
                       │
                       ▼
               127.0.0.1:6111
                       │
                       ▼
                    LiteLLM
                       │
                  API Key Auth
                       │
                       ▼
                 LLM Provider

这样 Cloudflare 才真正成为第一层入口,而不是一个可以轻易绕过去的 CDN。


有趣的是:攻击和 502 最终没有关系

一开始看到:

大量异常 API 请求
+
Cloudflare 502

很容易自然地产生一个联想:

LiteLLM 是不是被攻击打挂了?

但经过完整排查以后发现,这其实是两个同时发生、但彼此独立的问题。

安全问题

公网扫描
→ 大量弱 Key 枚举
→ LiteLLM 全部鉴权失败

解决:

Cloudflare Rate Limit
+
Geo Restriction
+
强 API Key
+
关闭源站公网端口

502 问题

LiteLLM Response Header 很大
→ Nginx proxy buffer 不够
→ upstream sent too big header
→ Nginx 502
→ Cloudflare 展示源站 502

解决:

proxy_buffer_size 64k;
proxy_buffers 8 64k;
proxy_busy_buffers_size 128k;

这也是这次排障里比较有价值的一点:

两个问题恰好同时出现,并不意味着它们存在因果关系。

如果因为看到攻击流量就直接认定:

502 = 被攻击导致

反而会把排查方向带偏。


最终 LiteLLM 的防御思路

目前这套私人 LiteLLM 可以简单总结成四层:

第一层
Cloudflare Geo Restriction
→ 非预期国家直接 Block

第二层
Cloudflare Rate Limit
→ 高频请求直接 Block

第三层
Nginx
→ 唯一公网 Reverse Proxy

第四层
LiteLLM API Key
→ 最终认证

同时:

LiteLLM :6111

本身不应该直接暴露公网。

对于个人自建 LiteLLM 来说,这套方案已经可以挡掉相当大一部分互联网上无差别的:

端口扫描
API 扫描
弱密码枚举
低成本撞库
脚本滥用

而正常的 LibreChat / OpenCode / Codex API 请求基本不受影响。

Questions Answered

AI-generated

Cloudflare 页面显示 502 时,如何判断究竟是哪一层出错?

直连 upstream 并用 `curl --resolve` 绕过 CDN 逐层验证。

为什么 LiteLLM 直连正常,经 Nginx 却返回 502?

LiteLLM 的大量元数据和上游头部超出 Nginx 默认缓冲区。

如何配置 Nginx 才能稳定代理 LiteLLM 的 SSE 流式响应?

增大响应头缓冲、关闭代理缓冲,并设置较长读写超时。

如何避免公网 LiteLLM 被扫描器绕过 Cloudflare 直接访问?

端口仅监听本机,同时关闭安全组中的公网应用端口。
Total
0
Shares
Share 0
Tweet 0
Pin it 0
Brandon

Previous Article
  • Code

Letta部署记录

  • December 26, 2025
  • Brandon
View Post
You May Also Like
View Post
  • Code

Letta部署记录

  • Brandon
  • December 26, 2025
View Post
  • Code

WordPress 后台任务利器:使用 BGRunner 构建可靠的异步处理

  • Brandon
  • December 14, 2025
View Post
  • Code

WordPress image offload

  • Brandon
  • December 14, 2025
View Post
  • Code

ComfyUI应用手册

  • Brandon
  • December 6, 2025
View Post
  • Code

Leetcode Java常用代码

  • Brandon
  • February 17, 2024
View Post
  • Code

Golang入门

  • Brandon
  • February 4, 2024
View Post
  • Code

Setting Up and Maintaining a Ubuntu Environment for My Home Server

  • Brandon
  • November 24, 2023
View Post
  • Code

Swift Learning Log

  • Brandon
  • August 31, 2023

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Fullstar

Input your search keywords and press Enter.