直接用add_header无法彻底解决跨域,必须显式拦截OPTIONS请求并return 204+完整CORS头,配合always参数、白名单Origin、Vary: Origin防缓存,且需屏蔽后端重复CORS头。

直接用 add_header 很难彻底解决跨域,尤其遇到带凭证、自定义 Header 或非简单方法(如 PUT/DELETE)时。核心问题在于:预检请求(OPTIONS)默认返回 204 状态且不带任何头,而 add_header 默认不作用于 204/304 响应——这就导致浏览器卡在预检阶段,报错 “No 'Access-Control-Allow-Origin' header is present”。真正有效的方案,是显式拦截 OPTIONS 并返回完整 CORS 头 + 204 状态,同时用 always 参数确保头写入所有响应。
必须显式处理 OPTIONS 预检请求
浏览器发起复杂跨域请求前,会先发一个 OPTIONS 请求探路。Nginx 默认只返回空 204,不加任何头。你不主动接管它,预检就失败。
- 在对应
location块内添加if ($request_method = 'OPTIONS') { ... return 204; } - 里面要写全 CORS 头:Origin、Methods、Headers、Credentials、Max-Age
- 特别注意
Content-Type和Content-Length也要设,否则部分浏览器可能拒绝空响应
Access-Control-Allow-Origin 不能滥用通配符 *
只要前端设置了 withCredentials: true(比如传 Cookie 或 Authorization),后端(或 Nginx)就不能用 *,必须精确匹配 Origin。
- 用
map指令安全映射白名单域名:map $http_origin $cors_origin {<br> default "";<br> "https://app.example.com" "https://app.example.com";<br> "https://admin.example.com" "https://admin.example.com";<br> } - 再用
add_header Access-Control-Allow-Origin $cors_origin always;
这样既支持多源,又避免了* + credentials的冲突
add_header 必须加 always 参数
默认情况下,add_header 只对 200–299 且非 304/204 的响应生效。但预检返回 204,正式响应可能被缓存为 304——没 always 就等于没加。
- 所有 CORS 相关的
add_header都应带上always - 例如:
add_header Access-Control-Allow-Credentials "true" always; - 否则,304 响应里缺头,浏览器照样判定跨域失败
防缓存与头冲突的两个关键细节
CORS 头若被 CDN 或浏览器缓存,可能把错误的 Origin 值返回给其他站点;后端若自己也返回了 CORS 头,Nginx 再加一遍会导致重复字段报错。
- 加
add_header Vary "Origin" always;,让缓存系统按 Origin 区分响应 - 如果后端已返回 CORS 头,用
proxy_hide_header Access-Control-Allow-Origin;先屏蔽,再由 Nginx 统一补全 - 避免混用:不要既让后端发头,又让 Nginx 无条件 add_header,容易触发
Multiple values for Access-Control-Allow-Origin











