HTTP/3逐渐成为主流浏览器默认支持的协议,它基于QUIC运行在UDP之上,带来了更低的连接建立延迟和更好的弱网表现。与此同时,很多团队在架构中使用了Apache作为反向代理,并希望借助mod_cache模块减轻后端压力。但在HTTP/3流量引入之后,不少配置直接照搬HTTP/2时代的写法,结果出现缓存不生效、连接回退到TCP、甚至UDP端口被防火墙拦截等问题。本文将从代理缓存的原理讲起,逐步给出一套完整的Apache配置方案,并分析HTTP/3与QUIC在实际部署中的注意事项。

一、Apache代理缓存的工作原理
Apache的缓存体系由三层模块组成:mod_cache负责整体的缓存决策逻辑,mod_cache_disk或mod_cache_socache负责实际的存储后端,而mod_proxy则是反向代理的入口。请求到达时,mod_cache会先根据请求方法和响应头判断是否可缓存,如果缓存命中则直接返回本地副本,否则将请求转发给后端Origin服务器,再把响应写入缓存。
需要特别理解的一点是,缓存命中判断主要依据后端返回的响应头。如果后端返回了Cache-Control: no-store或者没有提供Last-Modified、ETag等验证字段,mod_cache默认不会缓存该响应。因此缓存命中率低的问题,九成以上出在后端响应头配置上,而不是Apache本身。
另一个常见误区是代理透明性。Apache作为反向代理时,会默认给上游请求加上Via和X-Forwarded-For头,这本身不影响缓存,但如果响应中带有Vary头且值包含过多变量(比如同时Vary了User-Agent和Accept-Encoding),缓存键会被拆分成大量变体,磁盘占用会迅速膨胀。建议在后端尽量控制Vary字段,只保留必要项。
二、mod_cache与mod_proxy的完整配置示例
下面给出一段可直接使用的配置。前提是已加载相关模块,编辑主配置文件或对应的vhost片段即可:
# 启用缓存相关模块(编译安装时可加 --enable-cache --enable-disk-cache)
LoadModule cache_module modules/mod_cache.so
LoadModule cache_disk_module modules/mod_cache_disk.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
# 磁盘缓存存储路径与容量控制
CacheEnable disk /
CacheRoot "/var/cache/httpd/proxy"
CacheDirLevels 2
CacheDirLength 1
CacheMaxFileSize 50000000
CacheMinFileSize 100
# 缓存失效策略:响应过期后允许在后台刷新
CacheStaleOnError on
CacheDetailHeader on
<VirtualHost *:443>
ServerName www.ipipp.com
ProxyPreserveHost On
# 反向代理到后端应用服务器
ProxyPass "/" "http://127.0.0.1:8080/"
ProxyPassReverse "/" "http://127.0.0.1:8080/"
# 对静态资源延长缓存时间
<LocationMatch "\.(css|js|png|jpg|woff2)$">
Header set Cache-Control "public, max-age=86400"
</LocationMatch>
</VirtualHost>配置完成后用htcacheclean -p /var/cache/httpd/proxy -l 512M限制缓存总大小,并建议配置为cron定时任务,避免磁盘被写满。验证缓存是否命中可以观察响应头中的X-Cache字段,配合CacheDetailHeader on输出的Age与X-Cache-Detail信息判断命中的是内存还是磁盘副本。
关于缓存锁,Apache还提供了CacheLock机制。当大量并发请求同时访问同一个未缓存的URL时,没有锁会导致所有请求都穿透到后端(俗称缓存击穿)。开启CacheLock on并设置CacheLockMaxAge 5,可以让同一时间只有一个请求回源,其余请求等待后直接读缓存。
三、HTTP/3与QUIC在Apache中的落地
Apache HTTP Server从2.4.x的实验模块开始逐步提供HTTP/3支持,通过mod_http3模块(依赖ngtcp2或quiche实现)监听UDP 443端口。与传统TCP不同,QUIC把传输层和TLS 1.3握手合并在一次往返内完成,因此配置时必须使用TLS 1.3证书,并且证书需同时服务于TCP和UDP两种监听。
实际部署中最关键的机制是Alt-Svc协商。浏览器并不会一开始就用HTTP/3,而是先通过HTTP/1.1或HTTP/2建立TCP连接,从响应头中读取Alt-Svc声明,之后才切换到QUIC。Apache侧的配置思路如下:
# 确保防火墙放行UDP 443
# firewall-cmd --add-port=443/udp --permanent
Protocols h2 h3 http/1.1
Listen 443
# nghttp3/quiche实现的http3模块
LoadModule http3_module modules/mod_http3.so
<VirtualHost *:443>
ServerName www.ipipp.com
Protocols h3 h2 http/1.1
SSLEngine on
SSLCertificateFile "/etc/pki/tls/certs/server.crt"
SSLCertificateKeyFile "/etc/pki/tls/private/server.key"
SSLProtocol -all +TLSv1.3
# 通告客户端可用HTTP/3,存活时间86400秒
Header always set Alt-Svc "h3-29=:443; ma=86400"
</VirtualHost>验证HTTP/3是否生效,可以在Chrome地址栏输入chrome://net-export抓取网络日志,或者使用curl的实验分支curl --http3测试。如果发现连接始终回退到TCP,优先排查三件事:UDP 443是否放行、证书是否支持TLS 1.3、以及中间是否存在不支持UDP的CDN或负载均衡层。
四、嵌入式场景下的代理转发思路(以EFM32为例)
在物联网方案中,经常出现一类特殊需求:资源受限的嵌入式设备(例如基于EFM32系列微控制器的传感节点)本身没有能力实现完整的QUIC协议栈。QUIC需要完整的TLS 1.3加密栈和较大内存来维护连接状态,而EFM32这类Cortex-M级别的MCU,RAM通常只有几十KB到几百KB,直接跑QUIC非常勉强。
合理的做法是让Apache充当协议网关:设备端继续使用轻量的HTTP/1.1或MQTT over TCP,通过局域网把请求发到Apache代理;Apache对外则以HTTP/3与云端通信,对内聚合设备流量。这样加密开销和连接管理全部上移到网关,MCU只需维护一个简单的TCP会话,功耗和内存占用都可控。配合前面配置的代理缓存,多个设备重复请求相同的固件更新包或配置文件时,可以直接命中磁盘缓存,不必每次穿透到外网。
需要注意的是,QUIC的0-RTT特性虽然能降低重复连接的开销,但对网关侧的防重放提出了要求,建议对携带写操作的请求禁用0-RTT,只在幂等的GET资源上启用。此外,EFM32这类设备通常通过 mbedtls实现加密,若确实需要在设备端直连HTTP/3,可以考虑裁剪过的tinyquic类实现,但生产环境更稳妥的仍然是网关代理模式。
五、性能监控与常见问题排查
缓存体系上线后,监控不可少。Apache自带的mod_cache会在server-status中输出命中率统计,也可以通过日志格式记录缓存命中情况:
LogFormat "%h %U %{X-Cache}o %{Age}o %Dus" cachetrack
CustomLog logs/cache_access.log cachetrack常见问题归纳如下:第一,命中率低先看后端的Cache-Control头;第二,出现陈旧内容检查CacheIgnoreNoLastMod等指令是否被滥用;第三,HTTP/3不通优先查UDP链路;第四,磁盘缓存增长过快时结合htcacheclean与CacheDirLevels调优目录深度。把这几项排查路径记牢,大部分代理缓存与QUIC相关的问题都能在几分钟内定位到根因。
Apache代理缓存HTTP/3QUIC修改时间:2026-09-01 10:02:40