在 Nginx 反代架构里,把客户端访问切到 HTTP/3 后,不少工程师会习惯性地沿用 HTTP/2 的服务器推送配置,结果发现日志中既看不到推送记录,回源请求也没有像预期那样走 HTTP/3。其实问题不在 Nginx 配置缺失,而在于 HTTP/3 协议从一开始就没有定义服务器推送能力。下面从日志字段、协议差异、回源验证三个方向把这件事说清楚。

Nginx 日志回源需要记录哪些字段
要判断回源链路是否正常、客户端是否协商到 HTTP/3,首先得把日志格式配好。Nginx 默认的 combined 格式只包含请求时间、状态码、UA 等基础信息,看不到上游地址、上游耗时和协议版本。因此建议单独定义一个用于排查回源问题的 log_format,把关键变量全部记录下来。这样一旦出现异常,不用重新改配置再复现问题,直接查看已有日志即可。
下面是一段可落地的日志格式配置,重点记录上游地址、上游返回码、上游响应时间、客户端请求协议以及响应头中的 Alt-Svc 字段:
http {
log_format upstream_debug '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream_addr=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_response_time=$upstream_response_time '
'request_protocol=$server_protocol '
'alt_svc=$sent_http_alt_svc';
access_log /var/log/nginx/upstream_debug.log upstream_debug;
}
这里 $upstream_addr 表示实际回源的后端地址,$upstream_status 是上游返回的状态码,$server_protocol 用于记录客户端请求使用的协议版本。如果客户端通过 HTTP/3 访问,这个值会显示为 HTTP/3.0;如果走 HTTP/2,则显示 HTTP/2.0。$sent_http_alt_svc 则能体现 Nginx 是否在响应中返回了 Alt-Svc 头,这个头用来告知客户端后续可以尝试使用 HTTP/3。
回源阶段默认情况下 Nginx 使用 HTTP/1.0 向后端发起请求,这会带来连接不能复用、缺少 Host 头等问题。因此通常会在 location 中显式指定回源协议为 HTTP/1.1,并关闭 Connection 头以启用 keepalive。只有先把回源协议确定下来,日志中的上游信息才有参照意义。
HTTP/3 为什么没有服务器推送
HTTP/2 中的服务器推送允许服务端在客户端请求某个资源后,主动把后续可能用到的 CSS、JS 等文件一并推给客户端。Nginx 通过 http2_push 指令来实现这一能力,例如在 location 块中列出需要推送的静态资源。这个特性在 HTTP/2 时代确实能减少部分往返次数,但也带来了头部压缩复杂、流取消成本高、容易浪费带宽等问题。
HTTP/3 基于 QUIC 协议重新设计了传输层,虽然它在逻辑上继承了大量 HTTP/2 的语义,但服务器推送并没有被纳入正式规范。在 RFC 9114 中明确指出 HTTP/3 不支持服务器推送。原因之一在于 QUIC 的流模型和 HTTP/2 的流控制差异较大,贸然引入推送会让实现复杂度大幅上升;另一方面,实际生产环境中服务器推送的收益并不稳定,很多浏览器也开始限制或移除对 push 的依赖。因此 HTTP/3 干脆把这个特性砍掉了。
Nginx 的 HTTP/3 模块也没有提供类似 http2_push 的指令。即使你在同一个 server 块中同时监听了 443 的 QUIC 和 TCP 端口,http2_push 也只对 HTTP/2 连接生效,对 HTTP/3 连接没有任何作用。同理,配置 add_header Alt-Svc 只是告诉浏览器“这个站点还支持 h3”,并不是服务器推送,两者不能混为一谈。
| 协议 | 传输层 | 服务器推送 | Nginx 配置指令 |
|---|---|---|---|
| HTTP/2 | TCP | 支持 | http2_push |
| HTTP/3 | QUIC | 不支持 | 无对应指令 |
下面这段配置只会在 HTTP/2 连接上执行推送,放到 HTTP/3 场景中不会产生任何推送记录:
location / {
http2_push /css/style.css;
http2_push /js/app.js;
}
从日志角度验证回源与协议协商
要把前端 HTTP/3、回源 HTTP/1.1 的链路跑通,可以先配置一个同时支持 QUIC 和 TCP 的 server 块。Nginx 需要显式监听 443 的 quic 端口,并配置好证书。为了让客户端知道可以升级到 HTTP/3,通常会返回 Alt-Svc 响应头。示例配置如下:
server {
listen 443 quic reuseport;
listen 443 ssl;
server_name ipipp.com;
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
add_header Alt-Svc 'h3=":443"; ma=86400';
location / {
proxy_pass http://backend_upstream;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Protocol $server_protocol;
}
}
配置完成后,用支持 HTTP/3 的客户端请求一次站点,再到访问日志中查看对应记录。典型的日志行可能长这样:
192.0.2.10 - - [12/Dec/2024:10:00:00 +0000] "GET /index.html HTTP/3.0" 200 1532 "-" "curl/8.6.0" upstream_addr=10.0.0.5:8080 upstream_status=200 upstream_response_time=0.024 request_protocol=HTTP/3.0 alt_svc=h3=":443";
从这条日志可以看出,request_protocol 为 HTTP/3.0,说明客户端确实通过 QUIC 连到了 Nginx;upstream_addr 指向了后端服务地址,upstream_status 为 200,说明回源请求成功;alt_svc 字段记录了返回给客户端的 Alt-Svc 头内容。整条链路中没有任何与服务器推送相关的字段,这正是 HTTP/3 协议特性的直接体现。
需要注意的是,Nginx 核心变量里并没有直接给出“回源协议”这一项。想要确认回源是走了 HTTP/1.1 还是其他版本,要么去后端服务的访问日志中查看,要么像上面的配置那样通过自定义请求头 X-Protocol 把客户端协议带给后端,再由后端记录。实际回源协议由 proxy_http_version 决定,只要不写 proxy_http_version 2.0,回源默认就是 HTTP/1.0 或 1.1。
常见误区与可落地的排查建议
第一个常见误区是把 http2_push 当成 HTTP/3 的推送指令来用。在同时监听 QUIC 和 TCP 的场景中,http2_push 只对 HTTP/2 客户端生效,对 HTTP/3 客户端完全无效。因此如果你只在 HTTP/3 连接下测试,日志里肯定不会有推送记录,这并不代表配置错误。
第二个误区是把 Alt-Svc 响应头看作服务器推送。Alt-Svc 只是告诉客户端“同一资源可以通过 HTTP/3 获取”,它属于连接迁移提示,不涉及资源内容的主动下发。第三个误区是认为回源也必须使用 HTTP/3 才能发挥性能。实际上客户端到 Nginx 这一段使用 HTTP/3 已经能解决弱网下的队头阻塞问题,回源链路通常处于稳定内网环境,继续使用 HTTP/1.1 加上 keepalive 连接池往往已经足够,盲目追求回源 HTTP/3 反而可能引入不必要的复杂度。
排查此类问题时,建议先看日志中的 $server_protocol 确认客户端协议,再看 $sent_http_alt_svc 确认是否返回了 h3 提示,最后结合后端日志核对回源状态。这样一层层剥离,就能快速定位问题到底出在客户端协商、Nginx 配置还是回源链路上。不要在 HTTP/3 场景下寻找服务器推送,因为它本来就不存在。
Nginx日志HTTP/3服务器推送回源修改时间:2026-09-20 20:04:53