HTTP/2 Server Push 允许服务器在客户端请求HTML时,主动把首屏必需的CSS和JS资源一并推送到连接中。这样做能减少一次额外往返,但推送决策不能凭感觉,否则会导致带宽浪费和缓存失效。要让推送真正可观测,需要把Nginx的访问日志改造为结构化格式,再借助日志采集和可视化工具分析哪些资源被实际推送、哪些Link头声明了预加载却未生效。这篇文章不讨论过高阶的协议细节,重点放在一套可落地的方案:在Nginx中启用HTTP/2 Push,定制JSON日志,使用Promtail采集到Loki,最后在Grafana中建立面板观察推送趋势。

在开始配置之前,需要确认Nginx版本大于1.13.9,编译参数包含--with-http_v2_module,并且站点已经配置了TLS证书。因为主流浏览器只会在TLS之上的HTTP/2连接中接受Server Push,没有HTTPS就无法验证效果。
一、Nginx中开启Server Push的两种方式
第一种方式是在location块中直接使用http2_push指令。它接受一个相对路径或完整URI,表示在响应当前请求时同时推送的目标资源。这种方式适合静态资源规则固定、路径可枚举的场景。下面是一个HTTPS server块示例,监听443端口并开启http2,在根路径响应HTML时推送style.css和app.js。
server {
listen 443 ssl http2;
server_name ipipp.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
location = / {
http2_push /css/style.css;
http2_push /js/app.js;
root /var/www/html;
index index.html;
}
}
第二种方式是借助响应头Link字段,配合preload关系让Nginx自动识别并推送。例如在HTML文档头部输出Link头,或者由后端应用设置。Nginx从1.13.9开始会在响应中检测Link: rel=preload并触发推送,但需要满足同源和资源类型限制。下面是一个PHP示例,通过header函数设置preload,Nginx会自动完成推送。
header('Link: </css/theme.css>; rel=preload; as=style', false);
需要说明的是,http2_push指令只能用在location或if上下文中,不能直接放在server块,但可以通过map或rewrite配合条件判断。如果多个location需要推送相同资源,建议使用include片段或变量组合。另外,当客户端已经缓存资源时,服务器虽然可以推送,但浏览器会拒绝重复缓存,流量仍被消耗,所以建议在HTML中同时保留preload提示,让浏览器决定是否复用。
二、用JSON日志记录Push相关请求
Nginx原生访问日志不会为被推送的资源单独创建记录,因为Server Push由服务器主动发起,不经过客户端请求生命周期。可观测性只能建立在主请求的响应特征上。最直接的办法是记录响应头中的Link字段,以及当前连接是否启用HTTP/2。下面定义一个JSON格式的日志模板,把时间、客户端、请求URI、状态码、HTTP/2标识和Link头都输出为结构化字段。
log_format push_json escape=json '{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request_uri":"$request_uri",'
'"status":"$status",'
'"http2":"$http2",'
'"link":"$sent_http_link"'
'}';
access_log /var/log/nginx/push_analytics.log push_json;
这里使用escape=json可以自动转义特殊字符,防止日志被注入换行导致采集错乱。$http2变量在HTTP/2连接中非空,$sent_http_link则记录下发送给客户端的Link响应头。即使你没有显式使用http2_push指令,只要后端返回了preload的Link头,Nginx也会尝试推送,所以这个字段能反映推送意图。
日志路径建议与普通access_log分开,例如/var/log/nginx/push_analytics.log。这样Promtail只采集这一份文件,既能减少解析压力,又能避免在主访问日志中混入大量无关字段。如果站点流量较大,还可以设置日志轮转,避免单文件持续膨胀。
三、Promtail+Loki+Grafana构建推送监控
Grafana本身只是可视化层,存储和采集需要搭配Loki。Promtail作为日志采集代理,可以安装在Nginx所在服务器上,实时读取push_analytics.log并发送到Loki。下面是一份最小化的Promtail配置。
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
- job_name: nginx_push_log
static_configs:
- targets:
- localhost
labels:
job: nginx_push
__path__: /var/log/nginx/push_analytics.log
配置中clients指向Loki的HTTP接口,scrape_configs通过static_configs指定日志文件路径。labels里的job会在Loki中作为筛选维度,后续所有查询都以{job="nginx_push"}为基础。如果你的Loki部署在Kubernetes或远程机器上,只需要修改clients的url地址即可。
日志进入Loki后,因为内容已经是JSON,使用LogQL解析非常方便。先在Grafana中配置Loki数据源,然后新建面板,使用以下查询统计最近5分钟内声明了Link头的请求数量。
count_over_time({job="nginx_push"} | json | link != "" [5m])
如果要查看具体哪些URI推送了哪些资源,可以使用:
{job="nginx_push"} | json | line_format "{{.time}} {{.request_uri}} - {{.link}}"
在Grafana面板中设置表格展示,可以按request_uri分组,快速定位重复推送或错误资源。还可以配合Alert规则,当某五分钟内link字段为空的请求占比异常升高时发出提醒,说明Push配置可能失效。
整个链路并不复杂,关键是先让推送意图进入可分析的结构化日志。http2_push配置只负责决定推什么,真正决定是否有效还要看浏览器缓存状态和实际网络条件。借助JSON日志与Grafana监控,你可以根据数据逐步调整推送列表,减少无效推送。如果未来Nginx官方能够提供更直接的Push事件日志接口,监控粒度还能进一步提高。
Nginx HTTP/2 Push日志采集Grafana修改时间:2026-09-25 18:09:12