HTTP/2 Server Push是HTTP/2协议引入的一种服务器主动推送机制,它允许服务器在客户端请求某个资源时,提前把页面渲染所需的关联资源一并发送过去。对于首屏性能优化,这个特性可以减少浏览器发现资源、发起请求、等待响应的往返时间。Nginx从1.13.9版本开始提供http2_push和http2_push_preload指令来支持这一能力,但要在生产环境真正发挥价值,需要把配置、日志监控与抓包验证结合起来。

接下来的内容会从配置、日志摘要生成、验证优化三个角度展开,帮助你把Server Push用对、用好。
一、Nginx启用HTTP/2 Server Push的配置步骤
要让Nginx支持Server Push,首先需要确保使用HTTP/2协议。HTTP/2在Nginx中通过listen指令的http2参数开启,且通常与SSL/TLS配合使用,因为主流浏览器只支持基于TLS的HTTP/2。基础配置如下:
server {
listen 443 ssl http2;
server_name ipipp.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
root /var/www/html;
index index.html;
location = /index.html {
http2_push /css/main.css;
http2_push /js/app.js;
}
}
上面的配置中,http2_push指令手动指定了当访问/index.html时要推送的两个资源。指令的参数是同一个location内的URI路径,Nginx会为这些资源生成PUSH_PROMISE帧,并在主响应之前把资源内容推给客户端。需要注意的是,这些URI必须能够被当前location匹配并正常响应,否则推送会失败。
另一种更灵活的做法是利用响应头Link配合preload关键字,再配合http2_push_preload指令自动触发推送。例如:
server {
listen 443 ssl http2;
server_name ipipp.com;
root /var/www/html;
index index.html;
http2_push_preload on;
location = /index.html {
add_header Link "<//css/main.css>; rel=preload; as=style";
add_header Link "<//js/app.js>; rel=preload; as=script";
}
}
注意Link头的值中使用了双斜杠开头,这是一种协议相对路径写法,表示使用当前请求的主机。如果写成</css/main.css>这种形式,浏览器也会解析,但Nginx在处理时可能无法正确识别资源。实际部署时建议写绝对路径,比如</css/main.css>。当http2_push_preload开启后,Nginx会解析响应头中的Link字段,对于带有rel=preload的资源自动执行推送,不需要逐个写http2_push。
两种方式可以混合使用,但要注意避免重复推送。手动http2_push的优先级更高,如果同一资源同时出现在两个位置,Nginx会去重。
二、通过自定义日志格式记录Server Push日志并生成摘要
Server Push配置完成后,接下来要回答的问题就是推送是否真正发生、推送了哪些资源、失败率如何。Nginx的access log会记录每一个请求,包括由http2_push发起的伪请求,但这些伪请求与普通客户端请求混在一起,无法直接区分。一个可行的方案是在响应中增加Link头,然后在日志格式里记录这个响应头和请求URI,再通过脚本解析出推送资源清单进行汇总。
首先在http块中定义一个新的日志格式,把$sent_http_link变量包含进去。这个变量代表服务器实际返回给客户端的Link响应头内容。同时记录请求URI、状态码、响应时间和连接编号,方便关联分析:
log_format push_log '$remote_addr - $request_uri - $status - $request_time - $connection - $sent_http_link'; access_log /var/log/nginx/push_access.log push_log;
这里$connection是连接序号,同一个HTTP/2连接内的所有请求(包括主请求和推送的伪请求)会共享这个编号。通过它可以把推送资源与主页面请求关联起来。例如某次访问/index.html的日志可能包含以下几条:
192.168.1.10 - /index.html - 200 - 0.045 - 12345 - </css/main.css>; rel=preload; as=style 192.168.1.10 - /css/main.css - 200 - 0.012 - 12345 -
从上面可以看出连接编号12345对应的连接既请求了主页面,也请求了被推送的/css/main.css。虽然日志中的/css/main.css请求没有Link头,但它的出现时间与主请求几乎同时,并且连接编号一致,可以判断为推送触发。为了方便统计,你可以用脚本把每个连接中除主请求外的其他请求都识别为可能由推送产生的请求。
除了$sent_http_link,还可以考虑记录$http2变量来确认请求是否发生在HTTP/2连接上,以及$request_time来评估推送资源的响应开销。对于更精细的日志摘要,建议把日志格式设计为结构化分隔符,比如使用竖线或tab分隔,降低后续解析成本。
三、用awk和Python生成推送日志摘要
有了带Link头的日志之后,就可以用命令行工具快速统计。假设日志文件为/var/log/nginx/push_access.log,格式为空格分隔,最后一个字段可能是Link头内容,也可能为空。下面这条awk命令可以按URI统计每个资源的请求次数和平均响应时间:
awk '{ uri=$2; status=$3; time=$4; count[uri]++; total_time[uri]+=time; if (status==200) ok[uri]++ } END { for (u in count) printf "%-30s count=%d avg_time=%.3f ok=%d\n", u, count[u], total_time[u]/count[u], ok[u] }' /var/log/nginx/push_access.log
这条命令遍历日志每一行,把第二个字段当作请求URI,第三个字段当作状态码,第四个字段当作响应时间。它统计每个URI出现的次数、平均响应时间和200状态码的数量。对于推送场景,你更关心那些在Link头中出现过的资源是否都有对应的200请求记录。如果某个资源在Link头中声明了preload,但在日志中找不到对应请求,说明推送没有生效,需要检查location匹配或证书配置。
对于需要定期生成日报或接入监控的场景,Python脚本更合适。下面的示例读取日志文件,解析每行字段,先收集每个连接的主请求和关联资源,然后输出每个主页面推送资源的覆盖情况:
import re
from collections import defaultdict
log_path = '/var/log/nginx/push_access.log'
pattern = re.compile(r'(\S+) - (\S+) - (\d+) - ([\d.]+) - (\d+) - (.*)')
connections = defaultdict(list)
with open(log_path) as f:
for line in f:
m = pattern.match(line.strip())
if not m:
continue
remote, uri, status, req_time, conn, link = m.groups()
connections[conn].append({
'uri': uri,
'status': int(status),
'time': float(req_time),
'link': link,
})
for conn, requests in connections.items():
main_req = [r for r in requests if 'link' in r and r['link']]
if not main_req:
continue
main_uri = main_req[0]['uri']
link_field = main_req[0]['link']
pushed = re.findall(r'<([^>]+)>', link_field)
for res in pushed:
matched = [r for r in requests if r['uri'] == res]
status = matched[0]['status'] if matched else 'missing'
print(f'{main_uri} -> {res} : {status}')
注意脚本中使用正则提取Link头里尖括号包住的资源路径,因此日志文件中的尖括号必须是原始字符。如果你的日志系统把尖括号做了HTML转义,需要先反转义再处理。另外这个脚本只做演示,实际生产环境建议把连接编号、时间戳和请求头都记录下来,并加上异常捕获。
四、浏览器与nghttp2交叉验证推送效果
日志摘要只能反映服务端视角的请求记录,要确认浏览器是否真正接收并使用了推送资源,还需要借助浏览器开发者工具。在Chrome或Edge中打开开发者工具,切换到Network面板,刷新页面后查看目标资源的Initiator列。如果资源是通过Server Push获取的,Initiator会显示Push或者PUSH_PROMISE字样,而不是常规的脚本名或URL。
更底层的验证方式是使用nghttp2命令行工具。它能够以人类可读的方式输出HTTP/2帧信息,直接观察PUSH_PROMISE帧的流ID和对应的请求头。例如执行下面命令:
nghttp2 -vn https://ipipp.com/index.html
命令输出中会包含类似[ 0.123] recv PUSH_PROMISE frame的帧信息,并列出被推送资源的URI。通过对比日志摘要中统计到的资源列表和nghttp2实际抓到的PUSH_PROMISE帧,可以判断推送是否被Nginx正确发出,以及浏览器是否取消了某些推送流。
关于取消推送,这是HTTP/2 Server Push最容易踩的坑。如果浏览器缓存中已经存在某个资源,或者Service Worker拦截了请求,客户端可能会发送RST_STREAM帧来拒绝推送,导致服务器白传数据。Nginx目前不支持Cache-Digest机制来感知客户端缓存,因此无法在服务端动态跳过已缓存的资源。实践中应避免对所有资源盲目开启preload,只对首屏必需且更新频率低的关键CSS、JavaScript进行推送,并通过URL版本化或Cookie控制推送范围。
另外,Server Push只适用于HTTP/2连接,如果客户端使用HTTP/1.1访问,Link头仍然会被返回,但Nginx不会执行推送。这并不会报错,但日志中不会出现被推送的伪请求。所以在分析日志摘要时,需要确认请求的协议版本,避免误判为推送失败。
综合来看,Nginx的HTTP/2 Server Push配置并不复杂,难的是如何量化验证推送效果。通过自定义日志格式记录连接编号和Link响应头,再配合awk或Python生成推送摘要,可以快速发现推送覆盖不全或重复推送的问题。加上浏览器和nghttp2的交叉验证,可以形成一套完整的推送监控方案。
NginxHTTP/2 Server Push日志摘要修改时间:2026-09-20 22:26:58