nginx 的 location 块仅负责路由匹配,添加自定义响应头必须使用 add_header 指令并置于 location 内;跨域需配置 access-control-allow-headers 允许自定义请求头,并用 access-control-expose-headers 显式暴露响应头供 js 读取,且推荐加 always 参数确保错误响应也生效。

Nginx 的 location 块本身不负责设置响应头,它只做路由匹配。真正添加自定义 HTTP 头部,必须用 add_header 指令,且必须写在 location 块内部。
要让非标准自定义头部(比如 X-Request-ID、X-App-Version)能被前端 JavaScript 正确读取或被浏览器接受,需分两步处理:允许客户端发过来的自定义请求头,以及让服务端返回的自定义响应头对 JS 可见。
明确声明允许的自定义请求头(Access-Control-Allow-Headers)
浏览器发起带自定义请求头(如 X-User-Role)的跨域请求时,会先发预检 OPTIONS 请求。Nginx 必须在响应中明确列出这些头的名字,否则预检失败。
- 值是逗号分隔的字符串,中间不能有空格
- 名字大小写敏感,必须和客户端实际发送的一致
location /api/ {
proxy_pass http://backend;
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,PATCH,OPTIONS";
add_header Access-Control-Allow-Headers "X-App-Version,X-User-Role,X-Request-ID";
}
显式暴露自定义响应头给前端 JavaScript(Access-Control-Expose-Headers)
即使后端返回了 X-Request-ID,前端 JS 默认也读不到——除非你在响应头里声明它:
add_header Access-Control-Expose-Headers "X-Request-ID,X-RateLimit-Remaining";
- 这个头只影响浏览器是否允许
response.headers.get('X-Request-ID')成功调用 - 它和
Access-Control-Allow-Headers内容可以不同,两者无依赖关系 - 推荐加
always参数,确保 4xx、5xx 等错误响应里也能暴露(避免调试时拿不到 ID)
add_header Access-Control-Expose-Headers "X-Request-ID" always;
正确拦截并响应 OPTIONS 预检请求
Nginx 默认不会把 OPTIONS 请求交给后端,但若没显式处理,可能返回 405 或直接透传导致后端报错。
推荐用独立 location = /api/ 或正则匹配拦截:
location = /api/ {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET,POST,PUT,DELETE,PATCH,OPTIONS";
add_header Access-Control-Allow-Headers "X-App-Version,X-User-Role";
add_header Access-Control-Expose-Headers "X-Request-ID";
add_header Access-Control-Max-Age 86400;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
注意:return 204 必须放在最后,且不要跟 proxy_pass 混用在同一 location 中。
确保自定义响应头不被过滤或覆盖
- Nginx 默认会过滤掉非标准响应头(如
X-My-Header),但add_header本身就能发出,无需额外开关 - 若用了
proxy_pass,确认没配proxy_hide_header X-Request-ID;—— 这会主动屏蔽该头 -
add_header不继承父级同名头,如果server块已设安全头,又在location里加新头,记得把所有需要的头都重写一遍 - 对重定向(如
return 301)或错误响应(404/502),要加always才生效:
add_header X-Trace-ID $request_id always;
不复杂但容易忽略











