HTTP/3 已经从草案阶段走进了主流浏览器的正式支持列表,Chrome、Firefox、Safari 都默认开启了它。相比 HTTP/1.1 和 HTTP/2,HTTP/3 最大的变化是底层传输协议从 TCP 换成了 QUIC(基于 UDP)。对于使用 Apache 作为入口网关、后端跑着 webpack 构建产物的前端项目来说,协议升级加上合理的代理缓存配置,往往能让首屏时间有一个肉眼可见的改善。这篇文章从原理讲到落地,把整个链路串起来。

一、QUIC 协议到底解决了什么问题
传统 HTTPS 连接需要 TCP 三次握手加上 TLS 握手,往返次数多,移动网络下延迟被进一步放大。QUIC 把传输层加密和连接管理合并到一起,首次连接通常一次往返就能完成加密协商,恢复连接时甚至可以做到零往返。这对弱网环境下的首字节时间(TTFB)影响非常直接。
第二个改进是队头阻塞。HTTP/2 虽然在应用层实现了多路复用,但底层仍然共享一条 TCP 连接,一旦某个报文丢失,所有流都要等待重传。QUIC 在传输层原生支持独立流,某个流丢包只会阻塞它自己,其他流继续传输。对于 webpack 打包出来的多个 JS chunk、CSS 文件并行加载的场景,这个特性收益明显。
第三点是连接迁移。QUIC 用连接 ID 而不是四元组来标识连接,手机从 WiFi 切换到 4G 时,IP 变了但连接不断,用户不需要重新握手。这在移动端体验上是一个很实在的加分项。
二、在 Apache 中启用 HTTP/3 监听
Apache 主线 2.4.x 早期版本并不原生支持 HTTP/3,长期以来的方案是使用 Cloudflare 的 mod_cloudflare 之外的第三方模块,或者在前面挂一层支持 QUIC 的代理。目前比较现实的路径有两种:一是使用较新的 Apache 实验分支(如 mod_http3 所在的 2.5.x 开发线或第三方维护的模块),二是在 Apache 前面部署一个 QUIC 网关(比如 Caddy 或 Nginx 的 QUIC 分支),把 HTTP/3 流量解包后以 HTTP/1.1 或 h2c 转发给 Apache 后端。
假设你用的是带 mod_http3 的构建版本,配置大致如下:
# 加载 HTTP/3 模块 LoadModule http3_module modules/mod_http3.so # 监听 UDP 443 端口,启用 HTTP/3 Protocols h3 h2 http/1.1 Listen 443 # 部分构建需要显式声明 UDP 监听 # 具体参数以所用模块文档为准 Listen 443 udp SSLEngine on SSLCertificateFile "/etc/ssl/certs/site.pem" SSLCertificateKeyFile "/etc/ssl/private/site.key"
需要特别注意的是,QUIC 走 UDP 443 端口,很多云服务器的安全组默认只放行了 TCP 443,配置完成后如果浏览器协商不到 h3,第一件事就是检查防火墙和安全组的 UDP 规则。另外可以在响应头里观察 alt-svc,浏览器正是通过这个头感知到服务端支持 HTTP/3 的:
Header always set Alt-Svc 'h3=":443"; ma=86400'
验证是否生效,可以打开 Chrome 的 chrome://net-export 抓取网络日志,或者在开发者工具的协议列中看到 h3 字样。
三、代理缓存与 webpack 静态资源的配合
协议升级解决的是传输效率,缓存策略解决的是重复传输本身。webpack 生产构建默认会对输出文件做 contenthash,文件名带哈希值意味着内容不变则文件名不变,这正是长缓存的最佳搭档。Apache 侧的代理缓存配置应该与这个机制配合:带哈希的静态资源设置超长的缓存时间,HTML 入口文件则禁止缓存。
# webpack 产物:js/css/字体,文件名带 contenthash
<FilesMatch "\.(js|css|woff2)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
# HTML 入口不缓存,保证发布后能立刻拉到新 hash
<FilesMatch "\.html$">
Header set Cache-Control "no-cache"
</FilesMatch>
# 如果 Apache 是反向代理,可开启 mod_cache 磁盘缓存
CacheEnable disk "/static/"
CacheRoot "/var/cache/apache2/proxy"
CacheDirLevels 2
CacheDirLength 1
CacheDefaultExpire 86400
这里有个容易踩的坑:immutable 指令告诉浏览器在缓存有效期内连条件请求都不用发,前提是文件名严格遵循 contenthash 规则。如果团队里有人手动改过打包产物而没有重新走构建流程,就会出现用户端拿到旧代码却无法刷新的问题。所以缓存策略必须和构建流水线绑定,发布只更新 HTML 中引用的 hash。
从整体效果看,HTTP/3 的多路复用让浏览器可以真正并行加载几十个 chunk 而互不干扰,长缓存又让回访用户根本不需要发请求,两者叠加后的性能收益远大于单独优化任何一项。配置完成后,建议用 Lighthouse 跑一轮前后对比,重点关注 TTFB 和 LCP 指标的变化,再结合访问日志确认 h3 连接的占比,逐步把旧协议流量迁移干净。