HTTP/2服务器推送可以让Nginx在客户端请求HTML页面的同时,主动把CSS、JS等静态资源推送到浏览器缓存中,从而减少往返延迟。但推送是否真的发生了、推送了哪些资源、推送有没有被客户端拒绝,这些问题在默认的access_log里是看不到答案的。不少人在配置文件里写了http2_push指令后,发现浏览器Network面板里看不到推送痕迹,就开始怀疑配置写错了。实际上,Nginx把HTTP/2相关的诊断信息记录在错误日志中,需要用特定的日志级别才能看到。本文就来详细讲解这套机制的原理和排查方法。

一、http2_push指令的工作原理与基础配置
在Nginx中开启服务器推送主要依赖两个指令:http2_push和http2_push_preload。http2_push接受一个URI参数,当某个location的响应返回时,Nginx会向客户端发起一个PUSH_PROMISE帧,承诺推送这个URI对应的资源。配置示例如下:
server {
listen 443 ssl http2;
server_name example.ipipp.com;
# 单个资源推送
http2_push /style.css;
# 也可以用通配符匹配所有静态资源(1.19.3以上版本支持)
http2_push /assets/*;
location / {
root /data/www;
index index.html;
}
}http2_push_preload则是另一种思路:当响应头中包含Link: </style.css>; as=style; rel=preload这样的预加载标记时,Nginx会自动将其转换为服务器推送,而不仅仅是浏览器自己去拉取。这种方式的好处是推送逻辑由应用层控制,Nginx只需要放行即可:
location / {
http2_push_preload on;
proxy_pass http://backend;
}需要注意,http2_push的匹配是基于URI的,它会检查客户端是否已经推送过同一资源。所谓Push Diary(推送日志表),本质上是Nginx内部维护的一张已推送URI记录表,用来避免对同一资源重复推送。理解这一点对后面的日志解读很重要。
二、开启日志获取推送相关的条目
推送的执行过程记录在错误日志中,而非访问日志。默认的error_log级别是error,此时HTTP/2的运行信息完全不会输出。要看到推送诊断条目,需要把日志级别调到info:
server {
listen 443 ssl http2;
server_name example.ipipp.com;
error_log /var/log/nginx/http2_info.log info;
http2_push /style.css;
}配置生效后,用curl发起一次HTTP/2请求:curl -k --http2 https://example.ipipp.com/ -o /dev/null -v。此时查看/var/log/nginx/http2_info.log,你会看到类似这样的条目:
2024/06/12 10:23:45 [info] 3521#3521: *3 http2 push resource: /style.css while sending to client, client: 203.0.113.10, server: example.ipipp.com 2024/06/12 10:23:45 [info] 3521#3521: *3 http2 PUSH_PROMISE frame sent for stream 1, pushed stream id: 2
第一条条目表示Nginx根据Push Diary判断该资源尚未推送过,于是登记并准备推送;第二条则记录了PUSH_PROMISE帧的发送以及推送流的ID。如果你反复请求同一资源但日志里不再出现push条目,说明连接复用时Nginx认为该资源已在推送记录中,这是正常行为而非故障。
除了info级别,还可以使用http2_recv_buffer_size、http2_max_concurrent_pushes等参数观察推送是否达到并发上限。当并发推送数被限制时,日志中会出现相应的告警条目,此时可以适当调大限制:
http2_max_concurrent_pushes 20; http2_push_max_concurrent_streams 20;
三、如何解读日志条目与常见问题排查
拿到日志条目后,关键在于逐字段解读。以http2 push resource: /style.css为例:*3是连接序号,用于追踪同一条HTTP/2连接上的所有事件;PUSH_PROMISE frame sent for stream 1表示承诺帧附着的请求流编号;pushed stream id: 2是服务端为推送资源分配的偶数流ID(客户端发起的流是奇数ID,这是HTTP/2协议的规定)。通过流ID可以在日志中串联起一次推送的完整生命周期。
排查时常见三类问题。第一类是日志里完全没有push条目,多半是listen指令缺少http2参数,连接实际跑在HTTP/1.1上,可以用curl -v观察协商出的协议版本确认。第二类是推送了但浏览器没有使用,这通常是因为推送资源与页面实际引用的资源URI不一致,比如推送了/style.css而页面引用的是/style.css?v=2,Push Diary按精确URI匹配,带参数就会被视为不同资源。第三类是客户端通过SETTINGS帧禁用了推送(一些代理或旧浏览器会这样做),此时Nginx日志中会出现推送被拒绝的提示,属于客户端行为,服务端无法干预。
对于重复推送问题,还可以结合Cache-Control响应头控制推送行为,并注意Push Diary的作用域是单条连接:客户端一旦断开重连,记录表就会清空,Nginx会重新推送。因此评估推送效果时应保持长连接测试,例如使用curl --http2 -k https://example.ipipp.com/ https://example.ipipp.com/style.css在一次连接中完成两个请求,第二条请求若命中推送缓存,整体耗时应该明显下降。
四、实践建议与性能权衡
服务器推送并非越多越好。推送客户端已经缓存的资源反而浪费带宽,这也是Chrome后来默认禁用推送的原因之一。实践中有两条建议:一是只推送首屏关键资源,数量控制在3到5个以内;二是优先使用http2_push_preload配合应用层的Link头,让后端根据用户状态动态决定推送内容。观察线上效果时,保持info级别日志运行一段时间,统计push条目数量与实际请求量的比值,如果推送命中率低,就应该收缩推送清单。
总结一下排查路径:确认HTTP/2协商成功,确认error_log级别为info,从日志条目中找到push resource和PUSH_PROMISE记录,再用流ID追踪推送是否被客户端接受。掌握这套方法后,无论是配置调优还是故障定位,都能做到有据可依,而不是靠猜测反复改配置。
Nginxhttp2_pushHTTP/2服务器推送修改时间:2026-09-05 07:32:33