在开启HTTP/2的服务端推送之后,Nginx的访问日志里会出现一些平时不常见的字段,特别是涉及推送决策的记录。如果不了解这些日志背后的编码逻辑,就很难判断推送到底有没有生效。本文从基础的http2_push配置讲起,再逐步拆解日志记录的解码方式,帮助大家把推送行为完整还原出来。

一、http2_push的基本配置与生效条件
HTTP/2的Push预加载是Nginx从1.13.9版本开始提供的功能,核心指令就是http2_push。它的作用是在客户端请求某个资源时,服务器主动把相关的后续资源一并推送过去,省去客户端再次发起请求的往返时间。最常见的用法是在首页响应中推送CSS和JS文件。
配置本身并不复杂,但有几个前提必须满足。首先是监听端口必须启用HTTP/2,也就是listen 443 ssl http2;这样的写法。其次是浏览器必须通过HTTPS访问,因为主流浏览器都不支持明文HTTP/2。最后,如果客户端在请求头里带了http2_push_preload并且值为on,或者浏览器自身设置了禁用推送,服务器就不应该再强行推送。
server {
listen 443 ssl http2;
server_name ipipp.com;
ssl_certificate /etc/nginx/cert.pem;
ssl_certificate_key /etc/nginx/cert.key;
location = /index.html {
root /var/www/html;
http2_push /static/style.css;
http2_push /static/app.js;
}
location /static/ {
root /var/www/html;
}
}这段配置的含义是:只有请求路径精确匹配/index.html时,才会推送样式表和脚本。注意http2_push的路径是相对URI,不能写成完整的URL,也不能指向外部域名。如果想动态控制推送,还可以用变量,例如根据请求头判断:
location = /index.html {
root /var/www/html;
# 只有当请求头中COookie不存在标记时才推送,避免重复推送
map $http_cookie $push_flag {
default 1;
~*visited=1 0;
}
http2_push /static/style.css;
}实际部署时,重复推送是一个隐蔽的性能坑。浏览器第二次访问时缓存里已经有资源,服务器再推一遍就是白白浪费带宽。常见做法是配合Cookie判断,首次访问时种下标记,后续请求命中标记就不再推送。Nginx官方也提供过类似的http2_push_preload方案,即响应头里写Link: </static/style.css>; rel=preload,由前端代理层完成推送。
二、推送相关的日志字段与diary日志结构
要验证推送是否生效,光看浏览器不行,因为开发者工具里被推送的资源会显示为状态码200加上特殊标记,不同浏览器展示差异很大。更可靠的方式是让Nginx自己把推送行为记录到日志里。这里说的diary日志,本质上就是带有详细变量的自定义access log,它像一本流水日记,把每个请求的推送决策都写了下来。
首先在log_format中引入几个关键变量:$http2表示当前连接是否为HTTP/2,值为h2或空;$http2_pushed是个非常关键的字段,当前请求是推送产生时它输出p,普通请求输出空串;再配合$request_time、$body_bytes_sent、$upstream_response_time等字段,就能完整还原每次推送的耗时和体积。
log_format diary '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent '
'h2=$http2 pushed=$http2_pushed '
'rt=$request_time urt=$upstream_response_time '
'ref="$http_referer" ua="$http_user_agent"';
access_log /var/log/nginx/diary.log diary;记录出来的日志大致长这样,注意第二行的pushed字段:
203.0.113.7 [12/Feb/2025:10:21:33 +0800] "GET /index.html HTTP/2.0" 200 5120 h2=h2 pushed= rt=0.031 urt=0.028 ref="-" ua="Mozilla/5.0" 203.0.113.7 [12/Feb/2025:10:21:33 +0800] "GET /static/style.css HTTP/2.0" 200 8421 h2=h2 pushed=p rt=0.002 urt=- ref="https://ipipp.com/index.html" ua="Mozilla/5.0"
解码这类日志的关键点在于理解哪些字段是客户端发起、哪些是服务端主动。被推送的请求在日志里同样是一条完整的记录,但它的特点是:referer指向触发推送的主文档,$http2_pushed为p,且请求时间通常极短,因为它根本没经过网络往返。把这三点结合起来,就能把日志里每条推送记录准确识别出来。
三、日志解码的实战分析与排查思路
拿到diary日志后,第一步通常是统计推送率。可以用一条awk命令把被推送的请求挑出来,按资源路径聚合,看看哪些资源推送得最多、平均体积多大:
grep 'pushed=p' /var/log/nginx/diary.log \
| awk '{print $6, $8}' \
| sort | uniq -c | sort -rn \
| head -20第二步是对比推送资源与普通资源的耗时差异。如果发现某个资源日志里pushed=p却反复出现,说明客户端没有缓存命中或者浏览器丢弃了推送,这时就应该检查是否缺少Cookie防重推逻辑。还有一种典型情况是日志里出现pushed为p、但状态码为404的记录,说明推送配置写错了路径,白白消耗了连接带宽。
第三步要结合业务收益判断。推送并非万能优化,它有明显的适用边界:被推送的资源必须几乎每次首屏都需要,且体积不能太大。如果日志显示某个2MB的JS文件每次都被推送,而实际用户只有三成会进入对应页面,那这项推送就是负优化。把它从http2_push列表里移除,改成Link预加载由浏览器自行决策,往往更合理。
实践建议:长期保留diary日志并按天切割,配合脚本统计pushed比例的变化趋势。当推送率长期低于50%时,就该考虑关闭对应资源的推送了。
最后补充一个解码时容易忽略的细节:HTTP/2连接复用会导致多条日志共用同一个客户端连接信息,因此不能靠连接数判断请求数。正确做法是始终以$request加$http2_pushed的组合来区分请求性质。理解了这些字段的生成逻辑,Nginx的推送日志就不再是一堆乱码,而是一本可以精确分析的优化账本。把日志解码做扎实,推送策略才能越调越准。
Nginxhttp2 push日志解码修改时间:2026-09-16 14:07:56