HTTP/2协议带来的多路复用、头部压缩等特性已经广为人知,但其中一个比较特殊的能力是Server Push,也就是服务端推送。简单来说,当浏览器请求一个HTML页面时,服务器可以顺手把这个页面引用的CSS、JS甚至图片一并推送过去,浏览器不用等到解析HTML后才发现还需要这些资源再去发起请求。Nginx从1.13.9版本开始原生支持这一特性,通过http2_push指令即可启用。不过这个功能用起来有不少细节容易踩坑,比如推送了浏览器已经缓存的资源反而浪费带宽,或者模块没编译进去导致指令不生效。本文从配置入手,把完整流程和常见问题讲清楚。

一、环境准备与版本确认
首先确认Nginx版本。http2_push指令要求Nginx版本不低于1.13.9,且HTTP/2模块属于核心功能,一般默认编译都会包含。可以通过下面的命令查看当前Nginx的版本和编译参数:
nginx -V # 输出中确认版本号,例如 nginx version: nginx/1.24.0
版本满足要求后,还需要确保监听端口开启了http2。注意从Nginx 1.25.1开始,写法从listen 443 ssl http2;改为单独的http2 on;指令,两种写法不要混用,否则启动时会报重复配置的错误。
# 1.25.1 之前的写法 listen 443 ssl http2; # 1.25.1 及之后的写法 listen 443 ssl; http2 on;
另外要明白一点:Server Push只工作在HTTPS环境下,本地用curl测试时需要加-k参数忽略证书校验,并用--http2指定协议。如果客户端不支持HTTP/2,Nginx会自动跳过推送逻辑,不会报错。
二、http2_push的两种配置方式
第一种是直接在配置文件中声明要推送的资源。指令http2_push可以放在server、location等上下文中,值为资源的URI:
server {
listen 443 ssl http2;
server_name www.ipipp.com;
root /var/www/html;
location = /index.html {
http2_push /css/main.css;
http2_push /js/app.js;
}
}这种写法简单直接,适合资源关系固定的页面。当浏览器请求/index.html时,Nginx会立刻通过PUSH_PROMISE帧告知客户端即将推送两个资源,然后主动发起这两个请求的响应。
第二种方式是http2_push_preload,它通过识别响应头中的Link字段自动推送。后端应用在返回HTML时加上类似下面这样的响应头,Nginx检测到后就会执行推送:
location / {
proxy_pass http://127.0.0.1:8080;
http2_push_preload on;
}
# 后端返回的响应头:
# Link: </style.css>; as=style; rel=preload两种方式的区别在于控制粒度。http2_push把逻辑固定在Nginx层,适合纯静态站点;http2_push_preload把决策权交给应用层,后端可以动态决定哪些页面推送哪些资源,灵活度更高,也是目前更推荐的做法。
三、验证推送是否生效
配置完成后,用支持HTTP/2的curl可以直观看到推送过程:
curl -k --http2 -v https://www.ipipp.com/index.html -o /dev/null
如果推送生效,输出中会出现* received PUSH_PROMISE相关的行,说明服务器已经承诺推送资源。用Chrome访问时,打开开发者工具的Network面板,被推送的资源在Initiator列会显示为Push字样,这是最直接的证据。
如果看不到推送,优先排查三点:一是TLS是否协商到了HTTP/2,老浏览器或未启用ALPN的握手会退回HTTP/1.1;二是http2_push指令所在location是否真正匹配到了请求;三是确认没有在更高层级用http2_push off覆盖了配置。
四、最大的坑:重复推送浪费带宽
Server Push有一个天然缺陷:服务器并不知道浏览器缓存里有什么。假如用户第二次访问页面,CSS早已在本地缓存中,服务器还是会强行推送一份,反而消耗了带宽并占用连接资源。对于首屏优化是收益,对于回访用户就是负资产。
常见的解决办法是借助Cookie判断。首次访问时不设置Cookie,推送资源并写入一个标记;后续请求检测到该Cookie就关闭推送:
server {
listen 443 ssl http2;
server_name www.ipipp.com;
root /var/www/html;
# 无标记Cookie时推送并写入标记
location = /index.html {
if ($http_cookie !~* "pushed=1") {
add_header Set-Cookie "pushed=1; Path=/; Max-Age=86400";
}
}
location /css/ {
if ($http_cookie ~* "pushed=1") {
# 已推送过则关闭推送
return 204;
}
http2_push /css/main.css;
}
}不过这个方案有局限,Cookie失效或用户清理缓存后的边界情况不好处理。Chrome在106版本之后实际已经移除了对Server Push的支持,重点转向了rel=preload和103 Early Hints。因此在做技术选型时,需要评估目标用户群体的浏览器分布,不能只看协议本身的优雅程度。
五、Server Push与Preload的取舍
Preload是通过HTML中的<link rel="preload">标签提示浏览器提前加载资源,决策由浏览器执行,会正确参考本地缓存,不存在重复下载的问题,兼容性也更好。Server Push的优势则在于可以推送HTML里不方便声明的资源,且时机比浏览器解析HTML更早,理论上首字节到资源到达的间隔更短。
从目前的工程实践看,Preload加CDN缓存的组合已经成为主流,Server Push更像是特定场景下的补充手段,比如内网系统、客户端可控的环境,或者与http2_push_preload配合做精细化控制的动态站点。无论选择哪种,都建议通过Lighthouse或WebPageTest实测首屏指标,用数据说话,而不是盲目追求协议新特性。理解了推送的收益边界和缓存盲区这两个核心问题,配置层面其实非常简单,一条指令就能跑起来。
NginxHTTP/2 Server Pushhttp2_push修改时间:2026-09-15 16:22:35