CDN边缘缓存会丢弃未显式配置透传的CORS头,导致浏览器报错;需在Nginx中用add_header ... always设置并配合Vary: Origin,同时在CDN控制台启用自定义响应头缓存及OPTIONS不缓存策略。

CDN 边缘缓存为何会“吃掉” CORS 头
CDN(如 Cloudflare、阿里云 CDN、腾讯云 CDN)在转发请求到源站(Nginx)前,通常会剥离或重写部分响应头。尤其是 Access-Control-Allow-Origin、Access-Control-Allow-Methods 等 CORS 相关 Header,如果未显式配置为“可缓存”,CDN 默认不会透传给浏览器——它只缓存并返回自己认为“安全”的响应头(如 Content-Type、Cache-Control),而把跨域头当作“非标准响应元数据”直接丢弃。
结果就是:API 响应本身 200 OK,但浏览器控制台报错 CORS header ‘Access-Control-Allow-Origin’ missing —— 实际是 CDN 拦截了 Nginx 原始响应中的 CORS 头,没传下去。
Nginx 必须显式设置且“标记可缓存”的 CORS Header
仅在 Nginx 中用 add_header 是不够的。CDN 缓存行为依赖于响应头是否被标记为 Vary 或是否在 CDN 的“缓存白名单”中。常见错误写法:
add_header Access-Control-Allow-Origin "https://myapp.com";
这行指令在 Nginx 中生效,但若 CDN 未配置透传该 Header,它仍不会出现在最终响应里。
正确做法分两步:
-
在 Nginx 中使用
add_header并确保作用域覆盖所有 API location(推荐写在 server 或 http 块):
add_header Access-Control-Allow-Origin "https://myapp.com" always;<br>add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always;<br>add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" always;<br>add_header Access-Control-Expose-Headers "X-Total-Count, X-Request-ID" always;<br>add_header Access-Control-Allow-Credentials "true" always;
注意:always参数至关重要——它让 Nginx 在 200/404/500 等所有响应状态码下都输出这些头,避免 OPTIONS 预检成功但实际请求失败。 -
强制 CDN 缓存并透传这些头:
• 在 CDN 控制台中开启「自定义响应头缓存」或「透传响应头」功能(名称因厂商而异);
• 将Access-Control-Allow-Origin等明确加入「缓存包含头」或「回源保留头」列表;
• 同时设置Vary: Origin(见下一点),否则多源场景会出问题。
必须加 Vary: Origin,否则多域名跨域会混乱
如果你的 API 要支持多个前端域名(如 https://a.com、https://b.com),不能硬编码单个 Origin。此时需动态判断并设置响应头,并配合 Vary: Origin 告诉 CDN:“这个响应内容依赖于请求头 Origin 的值,请按 Origin 值分别缓存”。
Nginx 动态处理示例:
map $http_origin $cors_origin {
default "";
"~^https?://(a\.com|b\.com|localhost:3000)$" "$http_origin";
}
server {
...
location /api/ {
if ($cors_origin) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary "Origin" always;
add_header Access-Control-Allow-Credentials "true" always;
}
...
}
}
这样,CDN 收到带 Origin: https://a.com 的请求,缓存一份对应响应;收到 Origin: https://b.com,缓存另一份——避免 a.com 的用户拿到 b.com 的 CORS 头导致失败。
预检请求(OPTIONS)别缓存,但要快速响应
浏览器对非简单请求(如带 Authorization 或 Content-Type: application/json)会先发 OPTIONS 预检。如果 CDN 缓存了 OPTIONS 响应,可能造成后续真实请求的 CORS 头不匹配(比如缓存过期后源站更新了 Allow-Methods,但 CDN 还在返回旧的)。
建议策略:
- 在 Nginx 中对 OPTIONS 请求直接返回 204,不代理到后端应用:
location /api/ { if ($request_method = 'OPTIONS') { add_header Access-Control-Allow-Origin $cors_origin always; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE" always; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization" always; add_header Access-Control-Allow-Credentials "true" always; add_header Access-Control-Max-Age 17280000 always; add_header Content-Length 0 always; add_header Content-Type 'text/plain; charset=utf-8' always; return 204; } proxy_pass http://backend; } - 在 CDN 上将
OPTIONS请求设为「不缓存」或「绕过缓存」(Bypass Cache),确保每次预检都打到 Nginx。
验证是否生效:三步终端检查法
不要只看浏览器 Network 面板——它可能受本地缓存干扰。用 curl 直连 CDN 域名验证最可靠:
-
查响应头是否完整到达:
curl -H "Origin: https://myapp.com" -I https://cdn.example.com/api/users
确认输出中包含Access-Control-Allow-Origin、Vary: Origin等。 -
查 CDN 是否真正缓存了 CORS 头:
连续执行两次上述命令,观察cf-cache-status(Cloudflare)或x-cache(阿里云)是否为HIT,且两次响应头完全一致。 -
模拟不同 Origin 测试隔离性:
分别用-H "Origin: https://a.com"和-H "Origin: https://b.com"请求,确认返回的Access-Control-Allow-Origin值各自匹配,且Vary: Origin存在。

















