编程 Caddy 2.11.6 给反代加了默认 idle 超时,SSE 到 60 秒就被切断:2.11.7 修了什么

2026-10-26 21:03:52

Caddy 2.11.6 给反代加了默认 idle 超时,SSE 到 60 秒就被切断:2.11.7 修了什么

v2.11.7(2026-10-03)是一个补丁版本,修的主要就是 2.11.6 带进来的回归:HTTP/2 上反代崩溃,以及流式响应跑到一分钟被切。跑着 2.11.6 的直接升。

  • Release:
  • 超时配置文档:
  • 流式相关:

2.11.6 改了什么

2.11.6 引入了 Slowloris 缓解:默认开启 idle read/write 超时,每次成功读取或写入都会重置 deadline;另外有可选的最低传输速率,以及新增的 timeouts handler 指令用于按路由单独调(#7913)。同一版还有 url_pattern 匹配器、tls_automate_names 全局选项、expected_underscore_headers、Tailscale 低 MTU 上的 HTTP/3、encode 后面的 SSE 立即流式输出(#7905),以及优雅退出会等上一版配置里的 server 结束。

问题就出在那套 idle 超时上。

2.11.6 的三个副作用

HTTP/2 反代 panic(#8101)。 请求体的 idle deadline 活得比设置它的 handler 还久。handler 返回后反代还在读 body,HTTP/2 下就触发 nil pointer dereference。

HTTP/1.1 带 body 的流式响应被整 60 秒切断(#8103)。 典型场景是客户端用 POST 打开 SSE 流:body 读完那一刻开始计时,60 秒后响应被切断,跟流上有没有数据无关。

上面两个都在 #8107 修掉。

HTTP/2 上安静 SSE 被判超时(#8118、#8119)。 流只是写与写之间间隔超过 write_idle(默认 1 分钟),就被 stream error 重置。语义应该反过来:只有写入真的卡住才算超时,暂停不算。

另外有一个与超时无关但同样来自 2.11.6 的回归:缺失 cookie 的占位符又变回字面输出了。请求里没有那个 cookie 时,respond 的头里会原样吐出 {http.request.cookie.*};明文 HTTP 下的 {http.request.tls.*} 也一样。

2.11.7 的修复与新增

除了修上面这些,2.11.7 新增对 Incremental 头字段(RFC 10036)的支持,它是 NGINX X-Accel-Buffering 的标准替代。上游响应里带 Incremental: ?1 时,reverse_proxy 立即转发,效果等同于 flush_interval -1,encode 也会走流式而不是压着攒。Mercure、SSE 这类场景直接用。

按 RFC 的要求,如果 request_buffers 或 response_buffers 会让增量转发无法成立,Caddy 不再静默缓冲,而是返回 501 Not Implemented;proxy_status_name 选项会加一个 Proxy-Status 头说明拒绝原因。

其余改动:

  • TLS 握手更快。没有订阅者且 debug 日志关闭时,CertMagic 不再为每次握手构造事件数据;证书查找约快 2 倍,分配次数从 15 降到 10。
  • Unix socket:reload 把 listener 从 unix socket 上移走时,立刻关闭旧 socket 并删掉文件(以前客户端会挂到 GC,大约 2 分钟后才恢复)。
  • headers handler:JSON 配置里多个 Set-Cookie 值现在是各自独立的 header 字段,不再用逗号拼在一起。
  • caddy fmt 不再删掉输入最末尾的 {。

配置与排查

全局超时可以在 site 块外调,SSE 长连接把 idle 放宽比关掉更稳:

{
timeouts {
idle 10m
}
}

example.com {
encode zstd gzip

reverse_proxy localhost:8080 {
flush_interval -1
}
}

上游支持 Incremental 时,不用写 flush_interval -1,由上游给 Incremental: ?1 即可;reverse_proxy 后面的 encode 也会跟着流式。

排查时先用 curl 关掉客户端缓冲,确认断点是不是卡在 60 秒:

curl -N -H 'Accept: text/event-stream' https://example.com/events

-N 禁用 curl 自己的缓冲,输出如果正好在 60 秒左右停住、且服务端没有对应错误,基本就是这次的 idle 超时。再看 access log:响应时长集中在 60.0x 秒、状态码正常但连接被断,服务端侧对应的是 write timeout,而不是上游报错。HTTP/2 客户端还可以对比 curl --http1.1,2.11.6 上两条路径的表现不一样。

复制全文 生成海报 Caddy 运维 反向代理 SSE

推荐文章

程序员茄子在线接单