Nginx HTTP/2 与 HTTP/3 实践:多路复用、QUIC 传输与降级策略

深入 Nginx 对 HTTP/2 与 HTTP/3 的支持,覆盖 HTTP/2 多路复用与头部压缩、gRPC over HTTP/2、QUIC 传输协议与 0-RTT、HTTP/3 配置实践、性能对比与 ALPN 降级策略,给出从 HTTP/1.1 平滑演进到 HTTP/3 的完整路径。

HTTP 协议在最近十年经历了两次重要升级:HTTP/2 用二进制分帧与多路复用解决了 HTTP/1.1 的队头阻塞问题;HTTP/3 更进一步,把传输层从 TCP 换成基于 UDP 的 QUIC,从根源上消除了「TCP 级别的队头阻塞」。Nginx 对两者的支持都已经成熟:HTTP/2 自 1.9.5 起内置支持,HTTP/3 在 1.25.0 起进入稳定支持。本文沿着「HTTP/2 特性与配置 → gRPC over HTTP/2 → QUIC 与 HTTP/3 → 性能对比 → 降级策略」的路径,给出在 Nginx 上落地 HTTP/2 与 HTTP/3 的完整实践。

一句话总结: HTTP/2 解决的是应用层队头阻塞,HTTP/3 通过 QUIC 把队头阻塞问题连根拔除,Nginx 用 ALPN 协商让新旧协议平滑共存。

1. HTTP/2 核心特性回顾

一句话总结: HTTP/2 用二进制分帧、多路复用、头部压缩与流优先级四个特性提升并发效率,它们决定了配置与排错的关键点。

HTTP/2 的核心是「一个 TCP 连接上并发多个请求」。HTTP/1.1 的多个请求要么排队(短连接)要么串行(keep-alive),并发上限受浏览器每域 6 个连接限制;HTTP/2 把请求拆成二进制帧,在一条连接上交错传输,彻底改变并发模型。

HTTP/1.1:  请求1 ▸ 响应1  请求2 ▸ 响应2  请求3 ▸ 响应3  (串行)
HTTP/2:    请求1 ▹▸  请求2 ▹▸  请求3 ▹▸  响应1 ▹▸ 响应2 ▹▸ 响应3 (一条连接并发)

四个关键特性直接影响 Nginx 配置:一是多路复用,一个连接承载所有请求,Nginx 侧要调大 http2_max_concurrent_streams 容纳并发流;二是头部压缩(HPACK),用静态表与动态表压缩重复的请求头,减少传输字节;三是流优先级,允许客户端声明哪些资源更重要;四是服务端推送,允许服务器主动下发资源(实践中多被弃用,因为推送可能浪费带宽)。

# HTTP/2 基础配置(Nginx 1.25.1+ 的写法,更早版本用 listen 443 http2;)
server {
    listen 443 ssl;
    http2 on;

    # 单个连接上的最大并发流数
    http2_max_concurrent_streams 128;
    # 接收窗口大小,影响大对象吞吐
    http2_recv_buffer_size 256k;
    # 头部表大小
    http2_max_field_size 4k;
    http2_max_header_size 16k;
}

http2_max_concurrent_streams 设置过低会让客户端不得不排队,过高则可能被大量并发流打爆内存;http2_recv_buffer_size 影响大响应在 Nginx 层的缓冲。HTTP/2 部署后要关注「连接复用率」指标:理想情况下一个客户端对同一站点的所有请求都复用同一条连接,如果连接数仍然很多,说明客户端或中间设备没有启用 HTTP/2。注意 HTTP/2 强制要求 TLS(h2 明文变体基本没有浏览器支持),所以必须配好证书。

流优先级(Stream Priority)是 HTTP/2 容易被忽略的配置点。它允许客户端声明「关键资源优先传输」,Nginx 侧默认不干预流的调度,但在带宽受限时合理利用优先级能显著改善首屏体验。实践中多数站点选择不调整优先级,把调度交给浏览器——因为错误的优先级配置可能让关键资源反而被拖后。服务端推送(Server Push)也值得了解:它允许服务器主动下发资源,但浏览器缓存与请求合并的演进让推送经常造成浪费,多数生产环境选择关闭。

# 关闭服务端推送,避免无谓的带宽消耗(按站点评估是否启用)
server {
    listen 443 ssl;
    http2 on;
    http2_push_preload off;
    # 若需启用推送,用 http2_push 声明预加载资源
    # http2_push /static/app.js;
}

HPACK 头部压缩的收益体现在重复的 Cookie、Authorization 等请求头上。Nginx 侧的 http2_max_header_size 决定单个请求头总大小的上限,过小会拒绝大 Cookie 请求,过大则给内存带来压力。如果站点 Cookie 特别大(超过 16k),可以调大该值,但要同时评估 header 尺寸对吞吐的影响。

2. gRPC over HTTP/2

一句话总结: gRPC 以 HTTP/2 为传输底座,Nginx 用 grpc_pass 做七层代理,需要禁用缓冲与长度校验以适配流式语义。

gRPC 是微服务通信的主流 RPC 框架,它以 HTTP/2 为传输协议,天然受益于 HTTP/2 的多路复用与流式传输。Nginx 从 1.13.10 起提供 grpc_pass 指令,直接在 HTTP/2 层面代理 gRPC 流量,无需在 Nginx 层解包 Protobuf。

server {
    listen 443 ssl;
    http2 on;

    location /order.v1.OrderService/ {
        grpc_pass grpc://order_grpc;
        # gRPC 走流式语义,必须关闭缓冲,否则破坏流的即时性
        grpc_socket_keepalive on;
    }
}

upstream order_grpc {
    server 10.0.1.10:50051;
    server 10.0.1.11:50051;
    keepalive 32;
}

gRPC 代理有三个容易踩的坑。第一是「路径必须以服务/方法全名匹配」:gRPC 的 HTTP/2 路径是 /<package>.<Service>/<Method>,如 /order.v1.OrderService/CreateOrder,location 要用 = 精确匹配或前缀匹配并保留完整路径。第二是「必须关闭响应缓冲」:grpc_pass 默认关闭缓冲,但若在更外层配置了 proxy_buffering on 或对 gRPC 路径误用 proxy_pass,会破坏流式响应的即时性。第三是「健康检查路径」:gRPC 标准健康检查走 grpc.health.v1.Health/Check,Nginx 的主动健康检查需要探测该路径。

# gRPC 健康检查端点转发到后端
location = /grpc.health.v1.Health/Check {
    grpc_pass grpc://order_grpc;
}

客户端到 Nginx 之间的加密也必须考虑:gRPC 客户端通常以 grpcs:// 连接(即 gRPC over TLS),Nginx 侧终结 TLS 后再以明文 grpc 转发到内网后端,这与常规反向代理的 TLS 终结模式一致。如果后端也需要 mTLS,可以在 grpc_pass 前配置 grpc_ssl_certificate 与 grpc_ssl_verify。gRPC 是长连接 + 多路复用的典型场景,务必配置 keepalive 让 Nginx 与后端复用连接,否则每次 RPC 都新建后端连接会成为吞吐瓶颈。

# 后端 gRPC 走 mTLS 的配置
upstream order_grpc {
    server 10.0.1.10:50051;
    keepalive 32;
}

server {
    location /order.v1.OrderService/ {
        grpc_pass grpc://order_grpc;
        grpc_ssl_certificate     /etc/nginx/ssl/backend-client.crt;
        grpc_ssl_certificate_key /etc/nginx/ssl/backend-client.key;
        grpc_ssl_verify on;
        grpc_ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
    }
}

gRPC 排错要善用 Nginx 日志。grpc_pass 代理失败时 error_log 会记录具体的 gRPC 错误码(如 12 UNIMPLEMENTED 表示路径不匹配、14 UNAVAILABLE 表示后端不可达),据此可以快速定位是路由配置问题还是后端问题。生产上建议给 gRPC 路由单独配置 error_log 级别,方便在故障期间拿到更细的诊断信息。

3. QUIC 与 HTTP/3 基础

一句话总结: QUIC 基于 UDP 实现可靠传输,把握手、加密、拥塞控制都内建在传输层,0-RTT 与连接迁移是它的招牌能力。

HTTP/3 最大的变化是传输层:不再使用 TCP,而是用基于 UDP 的 QUIC 协议。QUIC 把 TLS 1.3 内建到传输层,握手与加密一体完成,首次连接 1-RTT、恢复连接 0-RTT;它还引入了连接标识(Connection ID),网络切换(WiFi 换 4G)时连接不中断,即连接迁移。最核心的改进是消除了「队头阻塞」:TCP 的一个包丢失会阻塞后续所有已到达的数据,QUIC 则在每条流上独立处理丢失,一条流的丢包不影响其他流。

HTTP/1.1:  TCP 握手 1-RTT + TLS 1-RTT + 请求/响应
HTTP/2:    TCP 握手 1-RTT + TLS 1-RTT + 多路复用(但 TCP 层仍有队头阻塞)
HTTP/3:    QUIC 握手 1-RTT(恢复连接 0-RTT),流内独立重传,无队头阻塞

QUIC 对 Nginx 而言是「在 UDP 端口上运行一套 HTTP 栈」。Nginx 的 HTTP/3 实现基于 ssl_quic 与 listen ... quic 指令,需要编译时带 --with-http_v3_module(1.25+ 内置)。QUIC 服务与 TLS 1.3 强绑定:没有 TLS 1.3 就没有 0-RTT 与连接迁移能力。因此 HTTP/3 配置的前提是证书与 TLS 1.3 就绪。

# 检查 Nginx 是否编译了 HTTP/3 模块
nginx -V 2>&1 | grep -o 'http_v3_module'
# 期望输出: --with-http_v3_module

QUIC 的可靠传输与拥塞控制在 Nginx 的用户态实现,配置面与 TCP 不同。Nginx 提供几个 QUIC 专属指令:quic_gso 开启通用分段卸载,降低小包发送的系统调用开销,Linux 内核需支持 GSO;quic_retry 在握手前做地址校验,能缓解 UDP 反射放大攻击,但会增加一次 RTT;quic_max_idle_timeout 控制空闲连接回收。这些指令的取舍要结合网络环境评估:

# QUIC 专属调优指令(按内核与链路能力选择)
server {
    listen 443 quic reuseport;
    http3 on;

    quic_gso on;              # 内核支持 GSO 时开启
    quic_retry on;            # 防 UDP 反射放大,代价是一次握手 RTT
    quic_max_idle_timeout 60s;
    quic_host_key /etc/nginx/quic_host.key;   # 用于地址迁移的密钥
}

quic_host_key 与 QUIC 的连接迁移相关:它是服务器用于验证客户端地址迁移的持久密钥,所有 Nginx 节点应共享同一把 key,否则客户端在节点间漂移时连接会中断。部署 HTTP/3 前要确认三点:一是编译版本带 v3 模块;二是防火墙放行 UDP 端口(QUIC 走 UDP 443,很多安全组默认只放行 TCP);三是确认 CDN 与负载均衡链路支持 UDP 转发——如果前面挂着四层 LB,LB 必须支持 QUIC 或做 UDP 透传,否则 HTTP/3 流量根本到不了 Nginx。

4. Nginx 开启 HTTP/3

一句话总结: HTTP/3 与 HTTP/2/1.1 共用 443 端口,靠 QUIC 的 TLS 1.3 扩展与 ALPN 区分协议,Nginx 需同时监听 tcp 与 quic。

Nginx 开启 HTTP/3 的配置要同时监听 TCP(承载 HTTP/2 与 HTTP/1.1)和 UDP(承载 HTTP/3)。关键指令是 listen ... quic reuseport 与 ssl_alpn,其中 http/3 必须排在 ALPN 列表首位,客户端协商到 HTTP/3 优先:

server {
    # TCP 443:承载 HTTP/2 与 HTTP/1.1
    listen 443 ssl;
    http2 on;
    http3 on;

    # UDP 443:承载 HTTP/3(QUIC)
    listen 443 quic reuseport;

    server_name api.example.com;
    ssl_certificate     /etc/nginx/ssl/api.example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

    # ALPN 必须把 http/3 放在首位,客户端才会优先协商 HTTP/3
    ssl_alpn h3 h2 http/1.1;

    # 允许 0-RTT 恢复连接
    ssl_early_data on;
    add_header Alt-Svc 'h3=":443"; ma=86400';
}

Alt-Svc 头是浏览器发现 HTTP/3 的机制:浏览器首次通过 HTTP/2 拿到 Alt-Svc: h3=":443" 后,才知道该站点支持 HTTP/3,后续升级到 QUIC。reuseport 让多个 worker 共享 UDP 443 端口,避免单 socket 争抢。ssl_early_data on 开启 0-RTT,但要注意重放攻击风险:0-RTT 请求可能在客户端未知的情况下被重放,写操作接口(POST)不建议用 0-RTT。

# 0-RTT 保护:读操作开 early data,写操作关闭
map $request_method $early_data {
    default     off;
    GET         on;      # 幂等读操作可接受 0-RTT
    HEAD        on;
}

server {
    ssl_early_data $early_data;
}

HTTP/3 上线验证有几个常用手段。一是用 curl --http3 或浏览器开发者工具确认协议列显示 h3;二是检查访问日志中 $server_protocol 变量是否出现 HTTP/3.0;三是用 nghttp3 客户端做回归。Nginx 的 $server_protocol 变量可以直接写进 log_format,据此统计 h2/h3/1.1 的流量占比,评估 HTTP/3 的实际采用率。

5. HTTP/2 与 HTTP/3 性能对比

一句话总结: HTTP/3 在高丢包、弱网、移动网络切换场景优势显著,稳定低丢包的宽带场景下与 HTTP/2 差距不大。

HTTP/2 与 HTTP/3 的性能差异取决于网络环境。在低丢包、低延迟的稳定网络(如机房内网、光纤宽带)上,两者差距很小,因为队头阻塞几乎没有机会出现;但在高丢包、高延迟的移动网络与弱网场景下,HTTP/3 的独立流重传优势被放大——一条流的丢包不会拖累其他流。

场景                 HTTP/2           HTTP/3
稳定宽带(丢包~0%)   基准              ≈ 基准
移动网络(丢包 1-2%)  明显队头阻塞      流级重传,延迟更稳
高丢包(>2%)         全部流被拖慢      仅丢失的流重传
网络切换(WiFi↔4G)   连接中断需重连     连接迁移,无缝切换
首次连接              TCP+TLS 2-RTT     QUIC 1-RTT
恢复连接              需要重握手          0-RTT

量化对比通常采用两种方法。一是合成网络工具(如 tc 注入丢包与延迟)做受控对比;二是收集线上 $server_protocol 分布与 P99 延迟做真实对比。Nginx 侧要特别注意:开启 HTTP/3 后监控 UDP 端口的收包错误与重传率,netstat -su 的 UDP 丢包可能来自防火墙或上游链路。

# 检查 UDP 统计,确认 QUIC 没有异常丢包
netstat -su | grep -A5 -i udp
# 检查 Nginx 日志中的协议分布
grep '"server_protocol":"HTTP/3.0"' access.json | wc -l

HTTP/3 的收益也有上限:服务端推送在 HTTP/3 中仍存在类似问题;0-RTT 只对「恢复连接」生效;QUIC 的加密强度更高,CPU 开销略高于 HTTP/2(可通过硬件加速或 ssl_ecdh_curve 优化缓解)。选型的建议是:面向移动端用户、链路质量参差的站点优先上 HTTP/3;纯内网服务与固定宽带用户为主的站点,HTTP/2 已经足够,不必为协议升级承担额外运维复杂度。

6. 降级策略与兼容性

一句话总结: HTTP/3 失败时客户端自动降级到 HTTP/2/1.1,Nginx 的 ALPN 顺序与旧版配置写法决定了降级路径是否顺畅。

HTTP/3 的部署必须设计降级路径:不是所有客户端都支持 QUIC(旧浏览器、部分企业代理、某些移动端 WebView),而且 UDP 443 可能被网络中间设备封锁。协议协商的顺序是:客户端与服务器通过 ALPN 与 QUIC 能力探测决定用哪个协议,失败则逐级降级。Nginx 侧要保证 TCP 443 上的 HTTP/2 与 HTTP/1.1 始终可用。

# 兼容写法(旧版本 Nginx,1.25.0 之前用 listen 443 http2;)
server {
    listen 443 ssl http2;          # HTTP/2 挂载在 listen 上
    # 没有 http3 模块时,仅提供 h2/1.1
    ssl_alpn h2 http/1.1;
}

ssl_alpn 的列表顺序就是降级顺序:h3 h2 http/1.1 表示优先 h3,协商不到则 h2,再不行则 http/1.1。对于不支持 ALPN 的极端旧客户端,Nginx 回落到 HTTP/1.1。另一个兼容性要点是「QUIC 与 HTTP/2 的配置尽量共用」:证书、缓存、限流等指令在同一个 server 块里同时作用于 TCP 与 UDP 监听,降低双栈配置漂移的风险。

# 双栈共用的安全基线:HTTP/3 也继承同一套安全头与限流
server {
    listen 443 ssl;
    http2 on;
    http3 on;
    listen 443 quic reuseport;

    limit_req zone=cc_req burst=20 nodelay;   # TCP 与 UDP 共用
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass http://web_cluster;
    }
}

降级路径要纳入监控。重点关注三个信号:一是 h3 流量占比过低,说明 UDP 链路可能被封锁或 Alt-Svc 头没有正确下发;二是 QUIC 握手失败率偏高,可能与 TLS 1.3 配置或防火墙有关;三是 HTTP/1.1 占比异常升高,可能是 ALPN 配置错误导致客户端全部降级。通过 $server_protocol 的分布统计,可以持续验证降级策略是否符合预期。

7. 从 HTTP/1.1 平滑演进

一句话总结: 演进分三步走:先启 HTTP/2 观察连接复用,再开 QUIC 验证 UDP 链路,最后用 Alt-Svc 与协议统计灰度放大 HTTP/3。

从 HTTP/1.1 到 HTTP/3 不必一步到位,分阶段演进可以把风险拆小。第一阶段启用 HTTP/2:只需在 listen 上加 http2 on(或旧式写法),观察连接复用率与多路复用效果,确认没有兼容性问题。第二阶段评估 QUIC:确认 Nginx 版本、防火墙 UDP 放行、LB 支持 UDP 转发,先在一台边缘节点灰度开启 HTTP/3。第三阶段通过 Alt-Svc 全量宣告,让支持 QUIC 的客户端自动升级。

# 阶段一:确认 HTTP/2 生效
curl -sI https://api.example.com/ -o /dev/null -w '%{http_version}\n'
# 期望输出: 2

# 阶段三:确认 HTTP/3 可用(curl 7.66+ 编译了 quic)
curl --http3 -sI https://api.example.com/ -o /dev/null -w '%{http_version}\n'
# 期望输出: 3

演进期间要持续观察协议分布与关键指标。Nginx 日志里记录 $server_protocol,配合监控面板统计 h2/h3 占比;关注 P99 延迟、连接数、UDP 收包与重传。协议升级的收益通常体现在弱网用户,因此要看移动端的分段指标,而不是只看全局平均。如果 h3 上线后全局 P99 没有变化,先不要急着下结论——把弱网用户单独分组看。

# 在 log_format 中记录协议版本,便于按协议拆分指标
log_format main '$remote_addr - [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" proto=$server_protocol';

演进的原则是「可观测、可回滚」。任何协议层变更都配得上 nginx -t 校验 + 平滑 reload + 灰度放量 + 快速回滚的流程。HTTP/2 与 HTTP/3 作为接入层能力,影响所有站点流量,建议先在低流量站点验证整套流程,再复制到核心业务。

8. 总结

维度HTTP/2HTTP/3
传输层TCPUDP + QUIC
队头阻塞应用层解决,TCP 层仍在流级独立,彻底消除
握手TCP+TLS 2-RTT首次 1-RTT,恢复 0-RTT
关键配置http2 on、http2_max_concurrent_streamslisten quic、http3 on、ssl_alpn h3、ssl_early_data
gRPCgrpc_pass 原生代理同样基于 HTTP/2 语义
降级ALPN h2 http/1.1ALPN h3 h2 http/1.1 + Alt-Svc
适用场景稳定网络、内网、固定宽带移动端、弱网、高丢包链路

HTTP/2 与 HTTP/3 不是非此即彼的关系,而是共同构成现代 Web 的协议阶梯:HTTP/2 已是标配,HTTP/3 在弱网与移动场景下提供增量收益。Nginx 通过 ALPN 与 QUIC 的共存配置,让三种协议(h3/h2/1.1)在同一个 443 端口上按客户端能力自动选择,运维上只需要维护一份双栈配置。落地时记住三个要点:确认编译模块与 UDP 链路就绪、用 Alt-Svc 宣告 HTTP/3 能力、用 $server_protocol 持续观测协议分布与降级健康度。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「nginx」更多文章

  1. Nginx 在服务网格中的角色:边车代理、mTLS 与 Envoy 取舍
  2. Nginx 大文件上传与请求体处理:缓冲、临时文件与断点续传
  3. Nginx 证书自动化与 ACME:certbot、DNS-01 通配符与自动续期