readheadertimeout仅控制请求头读取超时(1–5秒),不包含请求体、tls握手或handler执行;必须显式设置,否则默认0值将导致慢速攻击下连接长期卡住,且须≤readtimeout并与其他超时协同配置。
readheadertimeout 是 http.server 中一个**被严重低估但极其关键**的超时字段——它只管请求头(第一行 + 所有 header 行)的读取时间,不包含请求体、不包含 tls 握手、也不覆盖 handler 执行。设错或漏设,会导致服务在恶意慢请求或畸形客户端面前毫无抵抗力。
ReadHeaderTimeout 和 ReadTimeout 到底差在哪
两者都从连接建立完成(accept 返回后)开始计时,但终点完全不同:
-
ReadHeaderTimeout:只到最后一行请求头(\r\n\r\n)被完整读入为止;哪怕请求体有 100MB,它也不管 -
ReadTimeout:一直等到整个请求体也读完(或明确不读),才停止计时
这意味着:如果只设了 ReadTimeout: 30 * time.Second,但没设 ReadHeaderTimeout,攻击者可以每秒只发一个字节的请求头(比如 G → ET → / …),让连接卡住几十秒甚至几分钟,而你的服务完全无感知。
为什么必须显式设置 ReadHeaderTimeout
Go 默认值是 0(即不限制),这在公网暴露的服务中等于开门揖盗。常见误判场景包括:
- 用
curl -v --limit-rate 1模拟慢客户端,发现连接长期ESTABLISHED却无响应 - 日志里大量
http: Accept error: read tcp ...: i/o timeout,但ReadTimeout明明设了 5s - pprof 查看 goroutine 堆栈,大量卡在
net/http.readRequest或bufio.(*Reader).ReadSlice
根本原因:ReadHeaderTimeout 未设,底层 conn.SetReadDeadline() 没被触发,系统只能等 TCP 层超时(通常 2 分钟以上)。
典型配置值与协同要点
ReadHeaderTimeout 不是孤立参数,它必须和 ReadTimeout、WriteTimeout、IdleTimeout 配合使用:
- 推荐值:1–5 秒。对大多数 REST API,2 秒足够收完全部 header;若需支持带大量自定义 header 的内部调用,可放宽到 3 秒
- 必须 ≤
ReadTimeout:否则逻辑矛盾,ReadHeaderTimeout失效 - 不能替代
ReadTimeout:header 收完了,body 还可能被慢速上传拖垮,所以ReadTimeout仍需设(建议 10–30 秒) - HTTPS 场景下,
ReadHeaderTimeout**不包含 TLS 握手时间**——握手由http.Server.TLSConfig或监听层控制,不在这个字段管辖范围
示例启动代码:
srv := &http.Server{
Addr: ":8080",
Handler: myHandler,
ReadHeaderTimeout: 2 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
它不解决什么问题
明确划清边界,避免误用:
- 不控制 TLS 握手耗时:那是
tls.Config或http.Server.TLSConfig的事 - 不中断正在执行的 handler:handler 超时得靠
http.TimeoutHandler或 context 传播 - 不防止空闲连接堆积:那是
IdleTimeout的职责 - 不覆盖重定向或 DNS 解析:这是客户端侧
http.Client的领域
最常被忽略的一点:ReadHeaderTimeout 生效的前提是连接已成功建立并进入 HTTP 解析流程——如果客户端连 SYN 都发不过来,这个超时根本不会启动。











