HTTP/3的普及速度比多数人预想的要快,但反向代理缓存真正跑通QUIC却并不轻松。Apache HTTP Server长期使用HTTP/1.1和HTTP/2作为回源协议,前端即使已经启用HTTP/3,代理到源站时还是会转身走TCP。社区里出现的pgm220补丁试图把Cloudflare的quiche实现接进mod_proxy,让缓存层能与HTTP/3源站直接通信。这个方案并不改变Apache的整体架构,只是在代理协议栈里追加了一条UDP通道。

HTTP/3给反向代理缓存带来的实际收益
很多人以为HTTP/3只是把TCP换成UDP,实际上它解决的是两个长期困扰HTTP/2的问题:传输层队头阻塞和连接握手开销。反向代理在回源时通常维持少量长连接,如果其中一条TCP连接出现丢包,所有经过该连接的请求都会被阻塞,即使它们请求的是完全不同的资源。QUIC在一条连接内使用独立的数据流,丢包只影响对应的流,其他流照常交付。对于缓存未命中后的并发回源场景,这种隔离能明显降低尾部延迟。
缓存层部署在数据中心时,网络质量往往很好,丢包率低于公网,因此队头阻塞带来的收益可能不如移动端明显。但另一个优势是0-RTT:QUIC可以将握手和请求数据合并发送,回源首字节时间能缩短大约一个RTT。当缓存过期、需要立即回源拉取新内容时,这部分节省非常直接。此外,QUIC的连接迁移特性对多网卡源站也有帮助,不过前提是源站和代理都完整实现了地址验证。
pgm220补丁的出现让这些协议优势可以实际落到Apache的代理缓存路径中。它没有重写mod_cache,而是扩展了mod_proxy的连接建立逻辑,让ProxyPass能够识别http3://前缀,并在后端连接池中维护QUIC会话。对于已经熟悉Apache缓存配置的运维人员来说,迁移成本主要集中在前期的编译和参数调整上。
从编译开始:pgm220补丁与Apache的重新构建
Apache官方在2.4.54版本之后通过mod_http3提供了实验性的HTTP/3前端支持,但代理模块一直只能面对HTTP/1.1或HTTP/2后端。mod_proxy_http2虽然可以完成双向多路复用,底层仍旧是TCP。pgm220补丁针对2.4.x源码树打补丁,依赖quiche库和Rust工具链。安装前需要准备rustc、cargo、cmake以及libssl-dev,然后按照补丁包内的README编译quiche,再重新生成Apache构建配置。
整个编译过程大致如下:先获取Apache 2.4.58源码,解压后打入pgm220补丁;接着编译quiche静态库并安装到/usr/local/quiche;然后在Apache源码目录执行./configure时加入--enable-http3 --with-quiche=/usr/local/quiche;最后make和make install。示例命令如下:
cd /usr/local/src tar xzf httpd-2.4.58.tar.gz cd httpd-2.4.58 patch -p1 < ../pgm220-http3.patch cd ../quiche cargo build --release --features ffi,pkg-config-meta make install DESTDIR=/usr/local/quiche cd ../httpd-2.4.58 ./configure --enable-http3 --with-quiche=/usr/local/quiche --enable-proxy --enable-cache --enable-cache-disk make -j4 make install
需要特别留意补丁与Apache版本是否匹配,quiche的API迭代很快,不同版本的pgm220补丁可能要求特定的quiche commit。如果configure阶段报错提示找不到quiche.h,一般是因为pkg-config路径没有设置正确,可以追加PKG_CONFIG_PATH=/usr/local/quiche/lib/pkgconfig后重试。
mod_proxy与mod_cache的HTTP/3配置实例
编译完成后,配置文件的调整主要集中在三个模块:mod_proxy负责转发,mod_cache负责缓存,mod_http3负责前端监听。假设源站地址为192.168.1.100,监听443端口且已支持HTTP/3,代理服务器需要把根路径的请求通过QUIC转发过去,同时将响应缓存到本地磁盘。一个最小化的配置如下:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
LoadModule http3_module modules/mod_http3.so
LoadModule ssl_module modules/mod_ssl.so
<VirtualHost *:443>
ServerName proxy.ipipp.com
Protocols h3 http/1.1
SSLEngine on
SSLCertificateFile /etc/ssl/certs/proxy.crt
SSLCertificateKeyFile /etc/ssl/private/proxy.key
ProxyRequests Off
ProxyPass "/" "http3://192.168.1.100:443/"
ProxyPassReverse "/" "https://192.168.1.100:443/"
CacheEnable disk /
CacheRoot "/var/cache/apache2"
CacheDefaultExpire 3600
CacheMaxExpire 86400
CacheIgnoreHeaders Set-Cookie
Header edit Alt-Svc ""
</VirtualHost>
ProxyPass中使用http3://前缀是pgm220补丁扩展出来的协议标识,它告诉mod_proxy创建QUIC连接而不是TCP。ProxyPassReverse仍然使用https://,因为响应头中的Location字段通常返回标准HTTPS URL,代理需要把它改回对外可见的地址。CacheEnable disk /表示所有路径都启用磁盘缓存,CacheRoot指定缓存目录。Header edit Alt-Svc的用途是清理源站响应中可能携带的Alt-Svc头,避免客户端绕过代理直接连到源站。
缓存键的生成在HTTP/3下与HTTP/2没有本质区别,但需要注意0-RTT请求的安全性:如果代理启用了0-RTT回源,非幂等请求一旦被重放可能导致源站执行两次操作。建议在mod_rewrite层面对POST、PUT等请求强制关闭0-RTT,或者通过quiche的配置参数限制0-RTT只用于GET和HEAD。另一个常见问题是Vary头:源站如果返回Vary: Accept-Encoding,缓存会为不同的压缩格式保存多份副本,磁盘消耗会增加,这是正常行为,无需强制移除。
验证、排错与缓存调优
验证HTTP/3代理是否生效,可以用curl的前端测试和抓包结合。前端测试确认客户端到代理的QUIC连接正常,抓包则能确认代理回源时确实发送了UDP 443端口的QUIC Initial包。命令示例如下:
curl --http3-only -I https://proxy.ipipp.com/ tcpdump -i eth0 udp port 443 -w quic.pcap
如果curl返回的HTTP状态码和缓存头符合预期,但抓包只能看到TCP 443,说明代理回源没有走HTTP/3。这种情况最常见的原因是mod_proxy没有识别http3://前缀,需要确认补丁是否编译进mod_proxy的符号表:使用httpd -M查看proxy_module是否加载,再用ldd检查mod_proxy.so是否链接了libquiche。如果链接失败,启动时会提示undefined symbol,需要检查LD_LIBRARY_PATH是否包含quiche的lib目录。
缓存命中率的调优主要围绕过期策略和缓存分区。对于频繁更新的API响应,可以设置较短的CacheDefaultExpire并使用Cache-Control: max-age由源站控制。对于静态资源,建议将CacheMaxExpire提高到一周以上,并启用CacheLock避免缓存击穿。另外要定期清理磁盘缓存,否则inode耗尽会让Apache停止缓存。pgm220补丁目前还处于实验阶段,生产部署前建议在灰度环境跑通完整的回源、缓存命中、缓存过期链路,并观察UDP连接的内存占用情况。
整体来看,Apache通过社区补丁实现HTTP/3代理缓存是一条可行路径,尤其适合不想引入Nginx或Traefik等新组件的团队。它保留了Apache成熟的缓存管理和访问控制体系,同时把QUIC的传输优势带进了回源链路。随着官方对HTTP/3代理支持的完善,这类补丁最终会被合并或替代,但在此之前,pgm220提供了一个值得尝试的过渡方案。
Apache代理缓存HTTP/3QUIC修改时间:2026-09-28 21:44:40