HTTP/3 已经逐步成为主流浏览器的默认传输协议之一,它基于 QUIC 运行在 UDP 之上,解决了 TCP 队头阻塞的问题,并在弱网环境下表现出更好的连接恢复能力。很多站点的前端仍然是 Apache,通过反向代理将请求转发给后端应用。如果想让这套架构同时享受 QUIC 带来的低延迟和代理缓存带来的负载削减,就需要把三者合理地组合起来:Apache 对外提供 HTTP/3 监听,对内通过代理模块转发请求,并利用缓存模块把静态与半静态内容沉淀在内存或磁盘里。

一、整体架构设计:QUIC 入口与代理缓存的分层
在动手配置之前,先明确数据流向。客户端通过 QUIC(UDP 443 端口)与 Apache 建立 HTTP/3 连接,Apache 作为边缘节点完成 TLS 1.3 握手,随后通过 mod_proxy 将请求转发到后端的源站服务器(可以是 HTTP/2 或 HTTP/1.1 的应用服务器)。在转发之前,mod_cache 会先检查本地缓存,命中则直接返回,未命中才回源。
这种分层设计的价值在于:QUIC 的高握手效率作用于客户端到边缘的公网段,而代理缓存把大部分重复请求挡在了内网之外。对于静态资源占比高的站点,缓存命中率通常可以达到 80% 以上,回源流量大幅下降。需要注意的是,Apache 对 HTTP/3 的支持依赖于较新的版本(2.4.x 后期版本配合实验性的 mod_http3 模块),编译安装时需要确认 OpenSSL 与 nghttp3、ngtcp2 等依赖库已经就绪。
二、启用 HTTP/3 监听与 QUIC 相关配置
首先在 Apache 中打开 UDP 443 监听并加载 HTTP/3 模块。编译安装 mod_http3 后,在配置文件中加入如下内容:
# 加载 HTTP/3 模块(需编译安装 mod_http3)
LoadModule http3_module modules/mod_http3.so
# 同时监听 TCP(HTTP/2 兼容)与 UDP(QUIC)
Listen 443
Listen 443 udp
<VirtualHost *:443>
Protocols h3 h2 http/1.1
ServerName www.ipipp.com
SSLEngine on
SSLCertificateFile "/etc/httpd/ssl/server.crt"
SSLCertificateKeyFile "/etc/httpd/ssl/server.key"
# 通过 Alt-Svc 头告知客户端可以升级到 HTTP/3
Header always set Alt-Svc 'h3=":443"; ma=86400'
# 代理转发到后端源站
ProxyPreserveHost On
ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
</VirtualHost>
Protocols h3 h2 http/1.1 这一行声明了协议协商的优先级,客户端先尝试 h3,失败则回落到 h2。如果 Apache 版本不支持实验模块,也可以退而求其次:由 Nginx 或 CDN 层终结 QUIC,Apache 在内层继续承担代理与缓存职责,效果接近但少了一层端到端的 HTTP/3 语义。
Alt-Svc 头是 HTTP/3 落地的关键细节。浏览器第一次访问时走的可能是 HTTP/2,收到 Alt-Svc 响应头后,后续请求才会尝试 QUIC。参数 ma=86400 表示该声明有效期为一天,建议设置得足够长,避免频繁回退。
三、配置 mod_cache 构建代理缓存层
缓存模块由 mod_cache、mod_cache_disk(磁盘缓存)和 mod_cache_socache(共享内存缓存)组成。对于代理场景,磁盘缓存更常用,容量大且重启后缓存仍然有效。基础配置如下:
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
LoadModule headers_module modules/mod_headers.so
<IfModule mod_cache.c>
CacheQuickHandler off
CacheLock on
CacheLockMaxAge 5
CacheEnable disk "/"
# 后端未返回缓存头时,根据 URL 路径强制设置缓存策略
CacheEnable disk "/static/"
Header set Cache-Control "public, max-age=3600" expr="%{REQUEST_URI} =~ m#^/static/#"
CacheDefaultExpire 3600
CacheMaxExpire 86400
CacheLastModifiedFactor 0.1
CacheIgnoreNoLastMod On
# 对带 Cookie 的响应不缓存,避免登录态内容被串用
CacheIgnoreCookies On
CacheDirLevels 2
CacheDirLength 1
</IfModule>
# 缓存存储目录
CacheRoot "/var/cache/httpd/proxy"
几个参数值得展开说明。CacheQuickHandler off 让请求先经过完整的处理阶段(包括 rewrite、认证判断),再做缓存查询,安全性更好;如果站点全部是可缓存的公开内容,开启 quick handler 能获得极致性能。CacheLastModifiedFactor 0.1 用于后端只返回 Last-Head 而没有 Expires 头的情况,实际过期时间等于(当前时间减去最后修改时间)乘以该系数。
命中率调优方面,要特别注意 URL 规范化。带随机参数的地址(如 /api?id=123&t=1699)会导致缓存键爆炸,可以在代理层剥离无效参数;另外 Vary 头要谨慎使用,Vary: User-Agent 会让缓存条目成倍膨胀,一般应改为按 Accept-Encoding 做 Vary,并在代理层开启解压后缓存统一版本。
四、QUIC 与缓存结合的常见问题与排查
第一类问题是缓存失效判断异常。QUIC 本身不影响 HTTP 缓存语义,但如果中间经过 CDN 或负载均衡,客户端真实的 0-RTT 请求可能被改写,导致 Age 头与 Date 头出现偏差。排查时用 curl --http3 直接对源站验证:
# 使用支持 HTTP/3 的 curl 测试 curl --http3 -I https://www.ipipp.com/static/app.js # 查看缓存命中状态,HIT 表示代理缓存命中 curl --http3 -s -o /dev/null -D - https://www.ipipp.com/static/app.js | grep -i "x-cache"
第二类问题是 UDP 被防火墙丢弃。QUIC 依赖 UDP 443,部分云厂商的安全组默认只放行 TCP,表现为浏览器始终回落到 HTTP/2。可以在服务器上用 tcpdump udp port 443 观察是否有入站包。此外 QUIC 的连接迁移特性会让客户端在切换网络时保持连接,这对代理层是透明的,但如果 Apache 配置了基于 IP 的限流,可能误判为异常流量,建议在 QUIC 监听的虚拟主机上放宽源 IP 维度的并发限制。
第三类是队头阻塞残留。HTTP/3 消除了传输层的队头阻塞,但应用层如果后端是串行的 HTTP/1.1 连接,仍可能出现请求排队。把 ProxyPass 指向支持 HTTP/2 的后端,或者开启 proxy_http2 模块做多路复用,可以彻底打通整条链路的并发能力。
五、性能验证与总结
上线前建议用 h2load 或浏览器 DevTools 的协议面板做对比测试,重点观察三项指标:QUIC 握手是否触发 0-RTT 恢复、代理缓存的 HIT 比例、以及首字节时间(TTFB)在弱网模拟下的改善幅度。一般来说,静态资源在缓存命中时 TTFB 可以稳定在个位数毫秒,而 QUIC 在丢包率 2% 以上的网络中相比 HTTP/2 有 20% 到 40% 的加载优势。
整体来看,Apache 代理缓存加 HTTP/3 的组合并不复杂,核心在于分清职责:QUIC 负责公网传输效率,缓存负责削减回源压力,代理负责路由与协议转换。三者的配置相互独立,可以分阶段灰度上线,先用代理缓存拿到确定性收益,再逐步开启 QUIC 监听,最后通过 Alt-Svc 引导客户端平滑迁移,整个过程对存量用户完全无感。