readheadertimeout 是服务器读取完整请求头的最大时间,防“假死”即拦截连接已建好但只发半行请求头就挂起的情况,避免 goroutine 和文件描述符被长期占用。

ReadHeaderTimeout 是什么,为什么它防假死
ReadHeaderTimeout 控制的是从 TCP 连接建立完成(或 TLS 握手结束)开始,到服务器**完整读取请求头(request line + all headers)** 所允许的最大时间。它不包含读 body 的时间,也不包含 TLS 握手本身——那由 TLSHandshakeTimeout 管。
它防“假死”的本质是:拦截那种“连接已建好、客户端却只发了半行 GET /path HTTP/1.1 就停住”的情况。这类请求不会触发 ReadTimeout(因为还没开始读 body),也不会被 IdleTimeout 立即关掉(连接仍有活动),但会一直占着 goroutine 和文件描述符,直到 ReadTimeout 触发(如果设了)或连接被操作系统回收。
常见现象:net/http: TLS handshake timeout 或 read tcp …: i/o timeout 报错前,http.Server 的 goroutine 数持续上涨,lsof -i :8080 | wc -l 明显偏高。
ReadHeaderTimeout 必须小于 ReadTimeout
这两个字段不是互斥的,而是嵌套关系:ReadHeaderTimeout 是 ReadTimeout 的子集。如果设了 ReadTimeout 但没设 ReadHeaderTimeout,那么读 header 阶段就只能靠 ReadTimeout 兜底,容易让慢 header 消耗掉全部预算。
-
ReadHeaderTimeout应设为 2–5 秒,足够绝大多数标准请求(包括带长 Cookie 或 Authorization 的)完成 header 解析 -
ReadTimeout应明显更长(比如 15–30 秒),留给后续读 body(如上传大文件)或 handler 处理使用 - 若两者相等或
ReadHeaderTimeout > ReadTimeout,Go 会静默忽略ReadHeaderTimeout,退回到仅用ReadTimeout
HTTPS 场景下,ReadHeaderTimeout 不覆盖 TLS 握手
这是最容易混淆的一点:ReadHeaderTimeout 的计时起点是“连接已就绪”,即 TLS 握手**已完成**。它完全不管握手过程花了多久。
所以 HTTPS 服务必须同时配:TLSHandshakeTimeout(防握手卡住)+ ReadHeaderTimeout(防握手成功后 header 卡住)。缺一不可。
- 不设
TLSHandshakeTimeout:弱网下可能卡在证书验证、OCSP Stapling 等环节,默认 10 秒太长 - 不设
ReadHeaderTimeout:客户端建连成功后发一半 header 就挂起,server 会等满ReadTimeout才断开 - 示例配置:
srv := &http.Server{<br> Addr: ":443",<br> Handler: myHandler,<br> TLSHandshakeTimeout: 3 * time.Second,<br> ReadHeaderTimeout: 3 * time.Second,<br> ReadTimeout: 15 * time.Second,<br> WriteTimeout: 10 * time.Second,<br>}
ReadHeaderTimeout 对反向代理和长连接的影响
如果你用 http.Server 做反向代理(比如转发到后端 gRPC 或其他 HTTP 服务),ReadHeaderTimeout 依然生效——它约束的是**前端 client 到你的 server** 这一段的 header 读取,不影响你作为 client 去调用后端的超时设置。
对 HTTP/1.1 keep-alive 连接,ReadHeaderTimeout 每次新请求都会重置;对 HTTP/2,它同样适用,因为每个 stream 的 header frame 仍需解析。
真正容易被忽略的是:当你的 handler 里手动调用 req.Body.Read() 时,ReadHeaderTimeout 已经结束,此时受控的是 ReadTimeout 或底层连接的系统 read 超时。别指望它能限制 body 读取。











