Nginx从1.13.9版本开始正式支持HTTP/2服务器推送,通过http2_push和http2_push_preload两个指令,可以把CSS、JS甚至图片资源主动推送给浏览器,省去浏览器解析HTML后再发请求的往返时间。但推送并不是无脑发送的,Nginx内部维护了一个推送日记(push diary),用来记录已经推送过的资源,避免对同一个连接重复推送。理解这个日记机制、它的大小限制以及在连接恢复时的行为,对排查“为什么资源没有推送”这类问题非常关键。

推送日记的工作原理与大小限制
HTTP/2协议本身规定了客户端可以通过SETTINGS_PUSH_DIARY_SIZE参数告知服务器自己能记录多少条推送。但浏览器厂商并没有真正实现这个参数,大多数客户端都会把该值设为0,表示不限制。Nginx为了在服务端做去重,自己实现了一个固定大小为64条记录的推送日记。
这个日记记录的是已推送资源的SHA-256指纹,而不是资源路径本身。Nginx在每次准备推送前,会把目标资源的路径哈希成256位指纹,然后与日记中的条目比对。如果指纹已经存在,就跳过推送;如果不存在,则执行推送并把指纹写入日记。用哈希而不是字符串的好处是比对速度恒定,且存储空间固定,每条记录占32字节,整个日记最大约2KB,内存开销可以忽略。
需要注意一个边界情况:由于日记容量只有64条,当推送的资源超过64个时,旧记录会被新记录覆盖。假设你的页面推送了第65个资源,它的指纹恰好覆盖了第一个资源的记录,那么第一个资源在下一次判断时就会被视为“未推送过”而再次推送。虽然绝大多数页面推送的资源远不到64个,但在批量推送静态资源清单的场景下,这个上限值得留意。
会话恢复时推送日记的行为
推送日记的生命周期与HTTP/2连接绑定,而不是与TLS会话绑定。这意味着一旦HTTP/2连接关闭,日记就随之销毁。客户端建立新连接后,Nginx会创建一个全新的空日记,之前推送过的资源可以重新推送。所以严格来说,Nginx并不存在跨连接“恢复推送日记”的能力,日记是连接级别的内存结构,不会持久化到磁盘。
这里容易与TLS会话票据混淆。TLS会话恢复可以让客户端复用之前的加密参数快速握手,但HTTP/2层的状态(包括流控窗口、HPACK动态表、推送日记)都是全新的。也就是说,即使浏览器通过会话票据恢复了TLS会话,Nginx依然会为这条新HTTP/2连接建立空的推送日记。如果你在测试中发现浏览器拒绝了推送,原因通常不是日记残留,而是浏览器主动发送了CANCEL帧取消推送流,或者浏览器本身禁用了推送功能。
另外,当Nginx执行nginx -s reload时,旧的worker进程会继续处理存量连接,新连接由新worker接管。旧连接的推送日记随旧worker存活,新连接则使用新配置。因此在验证配置变更时,最好用全新连接测试,可以用curl --http2 -v https://ipipp.com/并注意每次curl都是新连接,行为最可预期。
配置示例与验证方法
下面是一个完整的推送配置示例,包含静态推送和基于preload链接的动态推送两种方式:
server {
listen 443 ssl http2;
server_name ipipp.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 静态推送:无条件推送指定资源
location / {
http2_push /css/main.css;
http2_push /js/app.js;
root /var/www/html;
}
# 动态推送:根据响应头中的preload link自动推送
location /api/ {
http2_push_preload on;
proxy_pass http://127.0.0.1:8080;
}
}验证推送是否生效,最直接的方法是用curl观察PUSH_PROMISE帧。执行下面的命令,如果看到* received PUSH_PROMISE相关的输出,说明Nginx确实发起了推送:
curl -k --http2 -v https://ipipp.com/ 2>&1 | grep -i push
如果输出为空,先排查三点:第一,确认监听指令里包含http2参数;第二,确认推送的资源路径以斜杠开头且是相对路径,写绝对URL是无效的;第三,确认连接确实是HTTP/2,可用curl -sI --http2 https://ipipp.com/ | grepi HTTP检查协议版本。
浏览器弃用推送后的替代方案
一个必须面对的现实是:Chrome从106版本起移除了HTTP/2推送支持,Firefox也在逐步跟进,Safari虽然还保留但默认场景有限。也就是说,即便Nginx的推送和日记机制工作正常,主流浏览器也可能直接取消这些推送流。服务端收到CANCEL帧后不会重试,日记中仍会记录该指纹,这反而解释了部分“推送了但没生效”的困惑。
目前推荐的做法是改用标准的preload机制。在HTML头部写入<link rel="preload" href="/css/main.css" as="style">,浏览器解析到该标签后会立即发起请求,虽然多了一个往返,但兼容性远好于推送。Nginx侧只需保留http2_push_preload on,这样当上游应用输出preload头时,对仍支持推送的客户端(如某些内嵌WebView或代理)依旧可以触发推送,形成平滑过渡。
另一个演进方向是HTTP 103 Early Hints。浏览器收到103状态码后会提前加载提示的资源,再等待最终响应。Nginx本身暂未原生支持发送103,通常需要借助Lua模块或由上游应用直接输出,但作为推送的继任者,它已经被Chrome和Firefox实现,值得在架构规划时纳入考虑。
常见问题排查总结
整理几个高频疑问:资源不重复推送是正常现象,那是推送日记在同一个连接内去重;跨连接推送日记不会恢复,每次新连接都会重新初始化;http2_push可以写在server、location和if上下文中,多条指令会累积生效;路径必须使用相对形式,否则推送会被静默忽略。
最后建议通过访问日志结合$http2变量统计HTTP/2流量占比,同时在灰度环境观察推送资源的服务器端命中率与客户端实际使用率的差距。如果两者偏差巨大,大概率是客户端取消了推送流,此时应果断切换到preload或Early Hints方案,把优化重心放在资源提示的标准实现上,而不是继续依赖服务器推送这一正在退场的能力。
Nginxhttp2_push推送日记修改时间:2026-09-15 12:32:36