431错误主因是请求头过大,需据协议类型分层处理:http/2场景须同步调大http2_max_header_size、http2_max_field_size及后端限制;http/1.1则调整client_header_buffer_size和large_client_header_buffers,并验证实际jwt负载与客户端协议协商情况。

431 错误大概率是 http2_max_header_size 触发的,但只在启用了 HTTP/2 的 server 块里生效;光调这个值不够,必须同步处理 http2_max_field_size 和后端限制,否则照样 431。
确认是不是 http2_max_header_size 在作怪
这个参数只对启用了 http2 的连接起作用,不是所有 431 都归它管。先看你的 server 块里有没有 listen 443 ssl http2 这种写法——如果只是 listen 443 ssl,那根本用不上 http2_max_header_size,问题可能出在 client_header_buffer_size 或 large_client_header_buffers 上。
快速验证方法:
- 执行
nginx -V 2>&1 | grep -o with-http_v2_module,没输出说明 Nginx 根本不支持 HTTP/2 - 在 error.log 里搜
client sent too large header while reading header,出现才说明是这个参数卡住的 - 用真实 JWT(别用
python3 -c "print('a'*20000)"模拟)发请求,curl 要加--http2,否则测的是 HTTP/1.1 路径
http2_max_header_size 设多少才合理
JWT 实际体积浮动很大:基础鉴权 token 可能就 2KB,但带完整用户上下文、多级 trace 头、嵌套声明的内部网关 token 很容易冲到 25KB 以上。默认 16KB(16k)在多数场景下确实偏小。
建议按场景分级设:
- 对外轻量 API、仅校验
Authorization: Bearer xxx:保持http2_max_header_size 16k即可 - 内部服务网关、JWT 含用户角色+部门+权限树+traceparent+x-b3-* 等:设为
32k或64k - 绝不设为
0或1m,这等于放弃防御,攻击者可以靠几百个长 header 耗尽 worker 进程内存
配置位置必须在 http 或 server 块内,例如:
server {
listen 443 ssl http2;
http2_max_header_size 32k;
}
为什么只改 http2_max_header_size 还是 431
HTTP/2 的头部限制是分层的,单点放大解决不了组合攻击:
-
http2_max_field_size控制单个 header 字段长度(比如一个超长Cookie),默认只有4k,若 JWT 放在Cookie里而非Authorization,这个值必须同步拉到32k -
max_headers(Nginx 1.29.8+ 才有)防“大量小 header”攻击,默认1000行,JWT 场景下常伴随一堆X-Request-ID、X-Forwarded-For等,建议压到500~800 - 后端不匹配也会透传失败:Node.js 要加
--max-http-header-size=32768启动参数;FastAPI/Uvicorn 本身不限,但若你把 Nginx 当透明代理直连,就得确保上游服务也能接住大 header
改完配置后必须做的三件事
很多人 reload 就以为完事了,结果 error.log 还在刷 431:
- 执行
nginx -t确认语法无误,nginx -s reload生效(别用restart,可能中断连接) - 检查 error.log 是否还有新出现的
client sent too large header,没有才说明 Nginx 层拦截住了 - 用生产环境实际生成的 JWT 请求接口,而不是人工拼字符串——有些 JWT 库签名后会引入不可见字符或 base64 填充,体积比预估多 20%
最易被忽略的一点:http2_max_header_size 对 HTTP/1.1 连接完全无效,如果你的客户端(比如某些旧版 Android WebView)没走 ALPN 协商,或者 TLS 握手失败降级,那它压根不会触发这个限制,此时得回头查 client_header_buffer_size 和 large_client_header_buffers。











