HTTP/2的多路复用解决了连接数限制,但页面加载仍然遵循请求-响应节奏:客户端先拿到HTML,解析后才发现还需要样式、脚本和图片。Nginx从1.13.9版本开始提供http2_push指令,允许服务端在返回主文档时主动向客户端推送关联资源。对日记类站点来说,首页通常由一份HTML、一份CSS、少量JavaScript和头像图片组成,如果这些静态资源能随HTML一起到达,浏览器就不必再发出第二轮请求。本文会结合一个可运行的配置示例,说明如何用Nginx完成这组推送。

需要先明确一点:Server Push只工作在HTTPS之上。浏览器通过TLS协商启用HTTP/2协议后,服务端才能发送PUSH_PROMISE帧。若Nginx仍以明文HTTP访问,配置了http2_push也不会产生任何效果。下面先看一个最小化的server配置。
一、开启HTTP/2并认识http2_push指令
要让Nginx具备服务端推送能力,第一步是让站点监听在支持HTTP/2的HTTPS端口上。较老版本的Nginx需要在listen指令中直接携带http2参数,写法如下。
server {
listen 443 ssl http2;
server_name diary.ipipp.com;
ssl_certificate /etc/nginx/ssl/diary.crt;
ssl_certificate_key /etc/nginx/ssl/diary.key;
root /var/www/diary;
index index.html;
}
较新版本的Nginx将HTTP/2开关从listen参数中拆分出来,可以改为listen 443 ssl;再在server块里写http2 on;。无论哪种写法,只有确认浏览器与Nginx使用HTTP/2进行通信,后续的http2_push指令才有意义。
http2_push的作用是在响应主请求时,额外向客户端推送一个同源资源。它可以在http、server或location上下文中使用,参数是站点的相对URI。比如首页需要用到一张头像图片,可以在对应location中这样写:
location = /diary/ {
http2_push /assets/diary.css;
http2_push /assets/diary.js;
http2_push /assets/avatar.png;
}
这里要注意,http2_push只能接收站内相对路径,路径必须以/开头,不能携带查询字符串,更不能写成https://cdn.ipipp.com/xxx.css这种完整外部地址。如果推了一个不存在的文件,客户端在收到PUSH_PROMISE后会发起匹配请求,最终可能看到一条失败记录。
二、为日记首页配置静态资源推送
日记首页通常位于/diary/或根路径。为了让CSS和JS随HTML一并到达,可以把http2_push放到匹配该页面的location中。下面是一份相对完整的Nginx配置片段,既推送首屏资源,也保证静态资源目录可正常访问。
server {
listen 443 ssl http2;
server_name diary.ipipp.com;
ssl_certificate /etc/nginx/ssl/diary.crt;
ssl_certificate_key /etc/nginx/ssl/diary.key;
root /var/www/diary;
index index.html;
location = /diary/ {
http2_push /assets/diary.css;
http2_push /assets/diary.js;
http2_push /assets/favicon.ico;
}
location /assets/ {
expires 7d;
try_files $uri =404;
}
}
在这个配置中,只有精确访问/diary/时才会触发推送,而/assets/目录仍然按照正常请求处理。这样做的意义在于,推送资源并没有绕过正常请求流程,它只是提前告诉浏览器这些资源马上需要,浏览器后续接收时可以直接使用缓存中的响应。
推送资源的选择非常关键。日记首页如果拉取了编辑器的完整脚本、评论组件、统计代码,不一定全部都需要推送。首屏渲染真正依赖的CSS、暴露首屏交互的少量JS、favicon和头像往往收益最大。推送太多资源会占用带宽和并发流,部分客户端还可能因为缓存已有而发送RST_STREAM取消推送,反而增加开销。Nginx提供的http2_max_concurrent_pushes可以用来限制并发推送数量,通常设置为10以内比较稳妥。
另外,如果站点里有多个页面都匹配/diary/前缀,建议使用location = /diary/精确匹配首页,避免对/diary/edit、/diary/settings等页面也执行同一组推送。不同页面的依赖资源不同,分开配置更加清晰。
三、用http2_push_preload自动读取Link头
如果日记页面由后端模板或静态生成器输出,不能为每篇文章在Nginx配置中写死资源列表。此时可以通过响应头Link配合http2_push_preload on;实现自动推送。后端或Nginx在返回HTML时附带一个预加载头,Nginx解析其中的rel=preload项并转换为PUSH_PROMISE帧。
location /diary/ {
http2_push_preload on;
add_header Link "</assets/diary.css>; rel=preload; as=style";
add_header Link "</assets/diary.js>; rel=preload; as=script";
}
上面的配置表示:凡是匹配/diary/前缀的响应,Nginx都会检查响应头中的Link字段。只要字段里出现rel=preload,Nginx就会把它当作推送目标。这样做的好处是,资源列表可以由后端动态决定,Nginx保持通用配置即可。后端程序也可以直接输出Link响应头,此时Nginx不需要再写add_header。
需要区分的是,rel=preload会被转换成服务端推送,而rel=prefetch不会。前者表示当前页面马上要用,后者只是下一跳可能要用。因此,如果只是想让浏览器空闲时提前下载,不要使用http2_push_preload触发推送,普通预加载即可。
as属性也很重要,它告诉浏览器资源的类型,从而影响请求优先级和后续使用方式。常见值包括style、script、font、image。比如首屏字体可以写as=font,头像图片可以写as=image。这个信息会随响应头传送给客户端,并不直接改变Nginx返回的Content-Type,但能帮助客户端更准确地处理资源。
四、验证推送效果与排错思路
配置完成后,单靠页面能打开并不能证明推送已经生效。最直观的方法是在浏览器开发者工具中打开Network面板,查看CSS或JS请求的Initiator字段。如果看到Push / Other,说明资源是由服务端主动推送而来;如果仍然是Parser或script,说明浏览器是在解析HTML标签后才发起的普通请求,Nginx的推送没有成功。
命令行验证可以使用nghttp工具。下面这条命令会访问日记主页,并把HTTP/2帧打印出来。
nghttp -ans https://diary.ipipp.com/diary/
如果输出中出现了PUSH_PROMISE帧,就说明Nginx确实发送了推送。没有看到时,可以从下面几个方面排查:
- 站点是否使用HTTPS和HTTP/2协议,明文HTTP不会触发Server Push。
server_name与实际访问域名是否一致,证书是否有效。http2_push是否写在了正确匹配请求的location中。- 推送路径是否以
/开头,并且文件真实存在。 - 使用反代或CDN时,Server Push可能在边缘节点丢失。
对日记类应用的实际建议是:推送资源控制在2到4个,以首屏CSS、JS和字体为主;用户自己的日记列表接口、文章详情内容、带会话状态的数据都不适合推送。这些内容经常变化,推送给客户端后往往会被取消,不会带来稳定的收益。静态资源推送则应配合expires等缓存头使用,并观察命中率。如果推送资源长期频繁被RST_STREAM取消,说明客户端已有缓存,此时可以把该资源从推送列表中移除。
HTTP/2 Server Push在合适的静态资源场景下能省去一次往返,但它不是所有页面都适用的灵丹妙药。配置http2_push时要把推送范围限制在首屏关键资源,并持续观察命中率和取消次数。对动态生成的日记页面,http2_push_preload on;配合响应头Link更为灵活,可以有效降低Nginx配置的维护成本。
Nginx HTTP/2推送http2_push服务器推送修改时间:2026-09-30 18:01:10