Access-Control-Max-Age 在预检请求中不生效,是因为 Apache 默认不对 OPTIONS 响应自动设置该头;必须用 Header always set 显式绑定到 OPTIONS 方法,并确保响应未被代理或模块干扰,否则浏览器无法缓存预检结果。

为什么 Access-Control-Max-Age 在预检请求中不生效?
直接在 VirtualHost 或 .htaccess 里写 Header set Access-Control-Max-Age "86400" 很可能没用——Apache 默认不会对 OPTIONS 预检响应自动附带这个头,除非你显式拦截并重写该响应。
更常见的情况是:浏览器发了预检,但每次仍重复 OPTIONS 请求,说明缓存未命中。根本原因通常是服务端没在 OPTIONS 响应里返回 Access-Control-Max-Age,或者返回了但值被中间代理/CDN 清洗掉了。
-
Access-Control-Max-Age只在预检响应(即OPTIONS请求的 200 响应)中起作用,普通 GET/POST 响应里设它无效 - Apache 的
Header指令默认只作用于主请求响应,不自动覆盖OPTIONS的响应头 - 若使用了
mod_proxy或反向代理,需确认代理是否透传或重写了 CORS 相关头
如何让 Apache 对 OPTIONS 请求返回有效的 Access-Control-Max-Age
必须把 Header 设置绑定到 OPTIONS 方法上,并确保该响应不被其他模块干扰。推荐用 + Header always set 组合:
<ifmodule mod_headers.c><limit options>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS"
Header always set Access-Control-Allow-Headers "Content-Type,X-Requested-With,Authorization"
Header always set Access-Control-Max-Age "86400"
Header always set Access-Control-Allow-Credentials "true"
</limit></ifmodule>
注意点:
-
always set是关键,它确保即使响应由内部模块(如mod_proxy或静态文件处理)生成,头也会被注入 - 不要在
或块里用Header set(无always),它对OPTIONS响应常失效 - 若资源路径有严格限制(比如只允许特定 Origin),
Access-Control-Allow-Origin不能设为*,此时Access-Control-Allow-Credentials必须为false,否则浏览器拒绝缓存
为什么加了 Access-Control-Max-Age 还是频繁触发预检?
预检缓存是否生效,不仅取决于服务端头,还受客户端请求特征约束。以下任一条件不满足,浏览器都会忽略缓存、重新发起预检:
- 后续请求的
Origin与预检时不同(哪怕只是协议或端口变化) - 实际请求方法(如
PUT)不在预检响应的Access-Control-Allow-Methods列表中 - 请求携带了未在
Access-Control-Allow-Headers中声明的自定义头(例如X-Trace-ID) - 请求含 credentials(
withCredentials: true),但预检响应中Access-Control-Allow-Credentials是false或缺失 - 服务器返回的
Access-Control-Max-Age值超过浏览器上限(Chrome 通常支持最大 24 小时 / 86400 秒,Firefox 更低)
调试建议:用 curl -I -X OPTIONS https://your.domain/api/xxx 确认响应中确实包含 Access-Control-Max-Age: 86400,且没有 Vary: Origin 导致 CDN 缓存分裂。
生产环境绕不开的兼容性细节
某些旧版 Apache(2.2.x)不支持 Header always 在 内使用,会报语法错误。此时需改用 SetEnvIf + Header 条件设置:
SetEnvIf Request_Method "OPTIONS" is_options Header set Access-Control-Allow-Origin "*" env=is_options Header set Access-Control-Allow-Methods "GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS" env=is_options Header set Access-Control-Max-Age "86400" env=is_options
另外要注意:
- 如果后端是 Node.js/Python 等应用层服务,优先在应用代码中处理
OPTIONS响应,比 Apache 配置更可控 -
Access-Control-Max-Age单位是秒,但部分老旧浏览器(如 IE11)只识别整数值,避免写"86400.0"或科学计数法 - 若启用 HTTP/2,确保 TLS 握手阶段未因 SNI 或证书问题导致 OPTIONS 请求被中断——这种失败不会留下 CORS 头痕迹,容易误判为配置问题
真正卡住的地方往往不是配不配得上,而是预检请求本身有没有被完整送达、响应有没有被某层中间件静默截断或重写。先抓包看 OPTIONS 的原始响应体和头,比反复调 Apache 配置更省时间。










