最干净可靠的解法是先用proxy_hide_header屏蔽后端跨域头,再由nginx通过add_header always统一注入合规cors头,并单独处理options预检请求,避免浏览器因重复头报错。

核心是“先清再加”——用 proxy_hide_header 拦截后端返回的冲突响应头,再由 Nginx 用 add_header always 统一注入干净、可控的头。不清理就叠加,浏览器会直接拒绝响应。
屏蔽后端自带的跨域头
后端(如 Spring Boot、Express、FastAPI)若已开启 CORS,会返回 Access-Control-Allow-Origin 等头;Nginx 再 add_header 就会导致重复,浏览器报错 “cannot contain multiple values”。必须在 location 块中显式隐藏:
- proxy_hide_header Access-Control-Allow-Origin;
- proxy_hide_header Access-Control-Allow-Methods;
- proxy_hide_header Access-Control-Allow-Headers;
- proxy_hide_header Access-Control-Allow-Credentials;
- proxy_hide_header Access-Control-Expose-Headers;
- proxy_hide_header Access-Control-Max-Age;
注意:这些指令只对当前 location 生效,且必须写在 proxy_pass 之后 才起作用。
统一由 Nginx 注入合规跨域头
屏蔽之后,所有跨域头必须由 Nginx 主动添加,并确保对非 2xx 响应(如 401、500、204)也生效:
- add_header Access-Control-Allow-Origin "$http_origin" always;
- add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" 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;
其中 $http_origin 支持动态匹配多域名,比写死 * 更安全;always 参数是关键,否则错误响应不带跨域头,前端拿不到完整报错信息。
单独处理 OPTIONS 预检请求
浏览器发 OPTIONS 请求时,不应转发给后端,而应由 Nginx 快速响应:
- 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;
- }
这段要放在 proxy_pass 同级位置,确保预检不穿透到后端,避免后端未处理 OPTIONS 导致超时或 404。
顺便清理其他易冲突的安全头
除跨域头外,后端常默认输出暴露技术栈或已过时的安全头,也建议一并屏蔽:
- proxy_hide_header X-Powered-By;
- proxy_hide_header Server;
- proxy_hide_header X-AspNet-Version;
- proxy_hide_header X-Frame-Options;
- proxy_hide_header X-XSS-Protection;
- proxy_hide_header Strict-Transport-Security;(若由 Nginx 统一配置 HTTPS 策略)
后续用 add_header 补上你真正需要的版本,比如 X-Frame-Options SAMEORIGIN 或 HSTS max-age=31536000,确保策略一致、可维护。











