必须添加 always 参数,否则 Nginx 仅在 2xx/3xx 响应中输出 CORS 头,导致 4xx/5xx 和 OPTIONS 预检失败时缺失头信息,引发浏览器跨域拦截。

在 Nginx 中配置跨域(CORS)时,很多人会写 add_header Access-Control-Allow-Origin "*";,却发现浏览器仍然报错:“The 'Access-Control-Allow-Origin' header contains multiple values”。问题往往就出在没加 always 参数——它决定了响应头是否在所有 HTTP 状态码下都生效,而跨域预检失败、404、500 等非 2xx 响应恰恰最容易被忽略。
为什么跨域请求常在非200响应中失败?
浏览器发起跨域请求前,会先发一个 OPTIONS 预检请求;若后端返回 401、404 或 500,Nginx 默认不会将 add_header 指令添加到这些响应中(除非显式声明 always)。此时响应里缺失 Access-Control-Allow-Origin 等关键头,浏览器直接拒绝后续实际请求,控制台报 CORS 错误。
- OPTIONS 预检返回 404 → 缺少 CORS 头 → 浏览器终止请求
- 真实请求返回 500 → Nginx 不注入 CORS 头 → 控制台显示 “No 'Access-Control-Allow-Origin' header”
- 静态资源返回 403(如未授权访问 /api)→ 同样不带 CORS 头 → 跨域拦截
always 参数到底改变了什么?
add_header ... always; 的作用是:让该响应头在所有响应状态码(包括 200、201、302、400、404、500 等)中都强制输出。默认情况下,add_header 只对 2xx 和 3xx 响应生效。
- ❌ 默认写法:
add_header Access-Control-Allow-Origin "*";→ 仅 2xx/3xx 响应携带 - ✅ 正确写法:
add_header Access-Control-Allow-Origin "*" always;→ 所有响应都携带 - ⚠️ 注意:多个
add_header指令叠加时,always必须每个都加,不能继承
完整跨域配置示例(含 always)
以下是最小可用且健壮的 Nginx 跨域配置,覆盖预检、正常请求及错误响应:
location /api/ {
# 允许任意源(生产环境请替换为具体域名)
add_header Access-Control-Allow-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,Range,Authorization" always;
add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always;
add_header Access-Control-Allow-Credentials "true" always;
<pre class="brush:php;toolbar:false;"># 处理预检请求
if ($request_method = OPTIONS) {
add_header Access-Control-Max-Age 1728000;
add_header Content-Type "text/plain; charset=utf-8";
add_header Content-Length 0;
return 204;
}}
注意:上面所有 add_header 行末尾都带 always,确保 4xx/5xx 错误响应也具备跨域许可能力,避免前端“看不见的拦截”。
容易踩坑的几个细节
光加 always 还不够,还需配合其他实践:
- 不要在 server 块顶层写
add_header,应在具体 location 中定义,避免继承污染 -
Access-Control-Allow-Origin和Access-Control-Allow-Credentials: true不能同时设为"*",否则浏览器拒绝;需动态设置 Origin 值(用$http_origin) - 使用
if判断$request_method = OPTIONS是常见做法,但需确保其内部也用了always(如上例) - 重启 Nginx 后务必用
curl -I -X OPTIONS http://yourdomain/api/test验证响应头是否真实存在
不复杂但容易忽略 —— always 就像给 CORS 头加了个“全状态保险”,让跨域逻辑真正贯穿整个请求生命周期,而不是只在“一切顺利”时才起作用。

















