本文主要记录 HTTP/2 升级后出现的偶发“网络连接已断开”类前端告警,以及对应的 Nginx HPACK 相关参数调优过程。
问题时间与 HTTP/2 上线窗口重合,日志侧大量 499,浏览器侧曾抓到 net::ERR_HTTP2_COMPRESSION_ERROR。根因与 HTTP/2 有状态的 Header 压缩(HPACK)及默认头大小限制有关。同系列还可参考 ssl_ciphers 与 INADEQUATE_SECURITY 与 0-RTT / proxy_set_header。
问题现象
监控与 QA 反馈:某业务线在升级 HTTP/2 后,邮箱 Web 内较频繁出现“网络连接已断开”类提示;多域可见,某一域更集中。示意截图(历史记录):


在 Nginx access 日志中,对应时段可检索到大量 499,且协议为 HTTP/2.0,TLS 多为 TLSv1.3。字段示意(IP、会话与内网上游已脱敏):
<client-ip> [<time>] <domain> 499 "POST /path?... HTTP/2.0" ... <upstream-ip>:80 ... https ... TLSv1.3 TLS_AES_256_GCM_SHA384
<client-ip> [<time>] <domain> 499 "GET /path?... HTTP/2.0" ... <upstream-ip>:80 ... https ... TLSv1.3 TLS_AES_256_GCM_SHA384
499 并非 RFC 标准状态码,而是 Nginx 对 客户端在服务端响应完成前关闭连接 的记法(Client Closed Request)。
客服侧同期几乎无对应投诉,结合升级已运行约一个月,判断为小概率触发的问题,而非全域大面积故障。
环境背景
| 项目 | 说明 |
|---|---|
| 变更 | 边缘 Nginx 启用 HTTP/2(及 TLS 1.3 等配套) |
| 协议 | 问题请求为 HTTP/2.0 |
| 各域配置 | HTTP/2 相关基础参数一致,可排除“某域独有错误配置” |
| 前端表现 | 业务页提示网络中断 / 连接断开 |
具体版本号以现网为准;下文结论不依赖单一小版本号。
排查过程
排除 TLS 1.3 协商与域间配置差异
初期曾怀疑 TLSv1.3 协商,但结合日志与对比后排除。各域 HTTP/2 基础参数一致,也不支持“只有某一域参数不同”的假设。
对照前端触发条件
与前端确认提示框逻辑大致为:
- 请求失败且返回数据为空
- 或监听到浏览器离线(如
navigator.onLine/ offline 事件)
navigator.onLine 反映的是客户端网络栈意义上的在线状态(断网、拔线等),与“单个 XHR 失败”不同。在排除真实离线后,重点回到:在 HTTP/2 多路复用同一连接上,是否有请求异常导致前端把失败放大成“全站断网”。
复现与浏览器错误码
在测试域成功复现一次后,开发者工具中可见:
net::ERR_HTTP2_COMPRESSION_ERROR
历史截图:

错误名直接指向 HTTP/2 的 compression 路径,结合协议特性,应重点看 Header 压缩(HPACK),而不是业务 body 的 gzip。
原因分析
RFC 7540 对 Header 压缩的要点:
- Header 列表经 HPACK 压缩后放入
HEADERS/CONTINUATION等帧 - 压缩上下文在整个连接上有状态(同一 connection 共用编解码上下文)
- Header block 解压失败必须视为 connection error,类型为
COMPRESSION_ERROR - 连接错误时端点应发
GOAWAY并关闭 TCP 连接
因此链路可以理解为:
- 某个请求的 Header(压缩后单字段或解压后整表)触及 Nginx 默认上限,或编解码状态异常
- 连接被判定为
COMPRESSION_ERROR并拆除 - 同一 TCP 上多路复用的其它请求一并失败
- 前端看到批量失败 / 空响应,叠加 offline 逻辑,打出“网络连接已断开”
- 客户端主动断开时,Nginx access 记为 499
这与“升级 HTTP/2 后出现、HTTP/1.1 时期少见、小概率、日志 499 + HTTP/2”的画像一致。
解决方法
Nginx HTTP/2 相关指令集中在 ngx_http_v2_module。与 HPACK / 请求头大小直接相关的是:
| 指令 | 默认(旧版文档) | 含义 |
|---|---|---|
http2_max_field_size | 4k | 单个 HPACK 压缩后的 header 字段(名或值)上限 |
http2_max_header_size | 16k | 解压后整个请求 header 列表上限 |
说明:
- 若启用 Huffman,解压后的字符串可能比线上更大,限制按文档语义理解
- 邮箱 Web、带长 Cookie / 长 Query / 复杂自定义头的页面,比静态站更容易顶满默认值
- 较新的 Nginx 已调整指令名称与默认值(例如转向
large_client_header_buffers等统一路径)。改配置前请对照当前版本官方文档,勿照搬旧指令名到不支持的版本
当时线上调整示意:
http2_max_field_size 32k;
http2_max_header_size 64k;
调整后按发布规范 reload Nginx,观察 499 与前端告警是否下降。
若使用的 Nginx 版本已移除上述指令,应改为文档推荐的等价配置,并在测试环境用“超长 Cookie / 超长 Header”用例压一轮。
验证结果
- 配置生效后,原问题场景下前端“网络连接已断开”告警明显缓解(以监控与 QA 反馈为准)
- 复现路径上不再稳定打出
ERR_HTTP2_COMPRESSION_ERROR - access 中与该问题同模式的 HTTP/2 499 减少
建议在变更后持续观察:
- 499 比率(按协议、URI 聚合)
- error_log 中与 http2 / header 相关的报错
- 前端网络中断类埋点
注意事项
- 加大 header 限制会提高单连接内存占用,且放大恶意超大头的成本;应结合 WAF/限流与业务真实头大小分布取值,而不是无脑加到极大。
- 499 原因很多(用户关页、超时、前端 abort 等),不能单凭 499 断定 HPACK;需要协议版本、错误码、时间对齐升级窗口一起看。
- 前端用“多请求失败 ≈ 断网”的策略在 HTTP/2 下更容易误报,可考虑区分
COMPRESSION_ERROR/ 连接级错误与真正 offline。 - 升级 HTTP/2 时,除密码套件、0-RTT 外,应把 header 大小与超时类参数 纳入检查清单。
- 指令名与默认值随 Nginx 版本变化;本文数值来自当时环境,迁移到新版本时以官方文档为准(必要时写“待确认”并在测试环境标定)。