Go的http.Server默认不限制请求头大小,需显式设置MaxHeaderBytes(如1MB)防OOM;其底层bufio.Scanner单行无硬上限,恶意长header会持续扩容buffer直至内存耗尽。

Go 的 http.Server 默认不校验请求头大小,超大 header 会直接写入内存直到 OOM
Go 标准库的 http.Server 对请求头(headers)不做长度限制,只在解析阶段用 bufio.Scanner 按行读取。而 bufio.Scanner 默认单行上限是 64KB,但 header 字段名+值拼成一行时可能远超此值——比如带长 JWT、嵌套 base64 元数据或恶意构造的 X-Forwarded-For,几万个字符就能让单行突破 1MB。此时 Scanner 会静默扩容 buffer,不 panic、不报错,只悄悄吃光内存。
- 必须显式设置
http.Server.MaxHeaderBytes,例如MaxHeaderBytes: 1 (1MB) - 该值控制「整个请求头」总字节数(含所有字段名、值、冒号、换行符),不是单个 header 的长度
- 设得太小会误杀合法请求(如含大量自定义 header 的 SSO 场景);设得太大等于没设
- 注意:这个限制在连接建立后、任何 handler 执行前就触发,超限直接返回 400 Bad Request,不会进路由逻辑
MaxHeaderBytes 不生效?检查 Nginx 或其他反向代理是否提前截断
如果你的服务部署在 Nginx 后面,MaxHeaderBytes 可能根本没机会运行——Nginx 默认 large_client_header_buffers 是 4KB×4,且 client_header_buffer_size 默认 1KB。一旦 header 超过这些值,Nginx 会在 Go 收到请求前就返回 400 或 414,日志里甚至看不到 access log。
- 确认 Nginx 配置中设置了足够大的
client_header_buffer_size和large_client_header_buffers,例如:client_header_buffer_size 8k;large_client_header_buffers 4 64k; - 同时检查
proxy_buffering off;是否被误开——开启时 Nginx 会缓存 header,但缓冲区大小仍受上述参数约束 - 用
curl -v -H "X-Test: $(printf 'a%.0s' {1..10000})"模拟大 header 请求,对比 Nginx error log 和 Go 服务日志,确认拦截发生在哪一层
框架中间件提前读取 header 会导致 MaxHeaderBytes 失效?不会,但要注意副作用
MaxHeaderBytes 是底层 net/http 在解析阶段强制执行的,跟中间件无关。但某些中间件(如 JWT 验证)会调用 r.Header.Get() 或遍历 r.Header,这本身不触发解析,只是读 map。真正危险的是调用 r.ParseForm() 或 r.FormValue() ——它们会隐式触发 ParseMultipartForm 或 ParsePostForm,而这些函数内部会再次读取并解析 header + body,可能绕过首次校验逻辑(尤其当 header 已部分解析时)。
- 避免在中间件中调用
r.ParseForm(),除非你明确需要表单数据 - 若需读取特定 header(如
Authorization),直接用r.Header.Get("Authorization")即可,无需解析整个请求 - 不要在 handler 里重复调用
r.ParseForm()——第二次调用会返回 nil error,但可能引发不可预期的 header 重解析行为
调试大 header 问题时,pprof 看不到泄漏点,要盯 runtime.ReadMemStats
header 过大导致的内存暴涨,通常不会体现在 pprof -inuse_space 的 top 结构体里——因为 header 数据被 bufio.Scanner 临时分配在栈或堆上,解析完就丢弃,但扩容过程中的旧 buffer 可能未及时被 GC 回收,造成短时间高内存占用。这时候 runtime.ReadMemStats 比 pprof 更直接。
立即学习“go语言免费学习笔记(深入)”;
- 在 server 启动时加一个健康端点,返回
MemStats.Alloc和MemStats.Sys,观察并发压测下这两项是否阶梯式上涨 - 如果
Sys持续增长而Alloc波动不大,说明是 mmap 分配未归还,大概率是底层 bufio 或 net.Conn 缓冲区失控 - 临时启用
GODEBUG=gctrace=1,看 GC 是否频繁触发但回收量极少——这是 buffer 扩容后残留的典型表现


















