当前位置: 首页 > news >正文

一次 Nginx 跨域代理的完整排坑实录:从证书错误到 CORS 配置

一次 Nginx 跨域代理的完整排坑实录:从证书错误到 CORS 配置

关键词:Nginx、CORS、跨域、SSL证书、反向代理、预检请求

一、背景与需求

最近在做一个项目,架构如下:

  • 前端域名https://www.example.com
  • 第三方APIhttps://thirdparty-api.example.net(不支持 CORS)
  • 目标:前端需要调用该第三方 API,但存在跨域问题

解决方案:使用 Nginx 做反向代理,将请求转发到自己的子域名https://api.example.com,并在 Nginx 层添加 CORS 响应头。

预期请求链路:

浏览器 → api.example.com (Nginx) → thirdparty-api.example.net

本以为是个简单的配置,结果前后踩了6 个坑,耗时整整一天。本文将完整记录排坑过程,希望对遇到类似问题的朋友有所帮助。

二、踩坑全记录

坑位1:SSL 证书域名不匹配

错误现象

ERR_CERT_COMMON_NAME_INVALID

原因分析
api.example.com使用了www.example.com的 SSL 证书,导致浏览器校验证书时发现域名不匹配。

解决方案
api.example.com申请独立的 SSL 证书:

certbot certonly--nginx-dapi.example.com

教训:每个子域名都需要自己的证书,或使用通配符证书*.example.com

坑位2:CORS 预检请求失败

错误现象

Access to fetch at 'https://api.example.com/...' from origin 'https://www.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present

原因分析
浏览器在发送实际请求前,会先发送一个OPTIONS预检请求。Nginx 没有正确处理这个请求,也没有返回必要的 CORS 响应头。

解决方案
在 Nginx 配置中添加 CORS 头和 OPTIONS 请求处理:

location / { # CORS 响应头 add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; add_header 'Access-Control-Allow-Credentials' 'true' always; # 处理预检请求 if ($request_method = 'OPTIONS') { add_header 'Content-Length' 0; add_header 'Content-Type' 'text/plain charset=utf-8'; return 204; } # 代理配置... }

坑位3:add_header 语法错误

错误现象

[emerg] "add_header" directive is not allowed here in /etc/nginx/conf.d/ssl.conf:47

原因分析
add_header指令被放在了 Nginx 不允许的位置。这个指令只能出现在httpserverlocation块中,不能随意放置。

错误示例

server { # 正确位置 add_header 'Header1' 'value1' always; location / { # 正确位置 add_header 'Header2' 'value2' always; } } # ❌ 错误:在 server/location 块外面 add_header 'Header3' 'value3' always;

解决方案
确保所有add_header都在serverlocation块内部。

坑位4:Access-Control-Allow-Origin 重复

错误现象

The 'Access-Control-Allow-Origin' header contains multiple values 'https://www.example.com, https://www.example.com', but only one is allowed.

原因分析
Nginx 配置中add_header 'Access-Control-Allow-Origin' ...被写了两次,导致响应头中出现重复值。

解决方案
检查配置文件,确保每个 CORS 头只添加一次。如果同时在serverlocation块中添加了,保留一处即可。

坑位5:代理 Host 头不正确

错误现象
后端 API 返回 502 或 404,但直接访问后端 API 是正常的。

原因分析
默认情况下proxy_set_header Host $host会将 Host 头设置为api.example.com,而后端 API 可能期望的是自己的域名thirdparty-api.example.net

解决方案
使用$proxy_host变量,它保存的是proxy_pass中指定的域名:

# ❌ 错误:Host 头变成 api.example.com proxy_set_header Host $host; # ✅ 正确:Host 头保持 thirdparty-api.example.net proxy_set_header Host $proxy_host;

坑位6:后端使用 HTTPS 导致证书问题

错误现象
Nginx 代理到https://thirdparty-api.example.net时出现 SSL 错误。

原因分析
Nginx 作为代理去访问 HTTPS 后端时,需要验证后端证书。如果后端证书有问题(自签名、过期、域名不匹配等),Nginx 会拒绝连接。

解决方案
有两种方式:

方案A(推荐):使用 HTTP 协议代理

# 如果后端支持 HTTP,直接用 HTTP proxy_pass http://thirdparty-api.example.net$request_uri;

方案B:忽略证书验证(仅临时使用)

proxy_pass https://thirdparty-api.example.net$request_uri; proxy_ssl_verify off; # 关闭证书验证

三、最终正确的配置

经过以上所有坑位的修复,最终配置如下:

server { listen 443 ssl; server_name api.example.com; # DNS 解析器(使用域名代理时推荐添加) resolver 114.114.114.114 223.5.5.5 valid=30s; resolver_timeout 10s; # SSL 证书(使用子域名自己的证书) ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; # HSTS 安全头 add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # SSL 配置 ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; client_max_body_size 100M; location / { # ========== CORS 配置 ========== add_header 'Access-Control-Allow-Origin' 'https://www.example.com' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; add_header 'Access-Control-Allow-Credentials' 'true' always; # 处理预检请求 if ($request_method = 'OPTIONS') { add_header 'Content-Length' 0; add_header 'Content-Type' 'text/plain charset=utf-8'; return 204; } # ========== 代理配置 ========== # 核心转发(使用 HTTP 避免后端证书问题) proxy_pass http://thirdparty-api.example.net$request_uri; # 关键:使用 $proxy_host 保持原始 Host proxy_set_header Host $proxy_host; # 传递真实客户端信息 proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # HTTP 1.1 支持 proxy_http_version 1.1; proxy_set_header Connection ""; # 超时设置 proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; # API 最佳实践:禁用缓存 proxy_buffering off; proxy_cache off; } } # HTTP 重定向到 HTTPS server { listen 80; server_name api.example.com; return 301 https://$server_name$request_uri; }

四、核心经验总结

1. 关于 SSL 证书

要点说明
子域名需要独立证书api.example.com不能使用www.example.com的证书
申请命令certbot certonly --nginx -d api.example.com
通配符证书*.example.com可以覆盖所有子域名

2. 关于 CORS 配置

要点说明
必须处理 OPTIONS浏览器预检请求需要返回 204
头不能重复Access-Control-Allow-Origin只能出现一次
位置限制add_header只能在server/location块内

3. 关于代理转发

要点说明
Host 头用$proxy_host保持后端期望的域名
添加$request_uri完整传递请求路径
后端可以用 HTTP浏览器到 Nginx 需要 HTTPS,Nginx 到后端可以用 HTTP

4. 调试技巧

# 测试 Nginx 配置语法nginx-t# 查看错误日志tail-f/var/log/nginx/error.log# 测试 CORS 响应头curl-XOPTIONS https://api.example.com/api/test\-H"Origin: https://www.example.com"\-H"Access-Control-Request-Method: POST"\-v2>&1|grep-i"access-control"

五、一个重要的认知

Postman 能请求成功 ≠ 浏览器能请求成功

工具是否检查 CORS说明
Postman❌ 不检查桌面应用,不受浏览器安全策略限制
curl❌ 不检查命令行工具
浏览器✅ 严格检查为了用户安全,必须配置正确的 CORS

这个认知能帮助您在调试时快速定位问题:Postman 通但浏览器不通 → 99% 是 CORS 配置问题。

六、架构总结

最终的成功架构:

浏览器 -----------(HTTPS)-----------> Nginx -----------(HTTP)-----------> 后端 (www.example.com) (api.example.com) (thirdparty-api) ↑ ↑ ↑ 安全连接 SSL证书 + CORS头 内部通信

关键设计思想

  • 浏览器到 Nginx:必须 HTTPS,证书有效
  • Nginx 到后端:可以用 HTTP,避免证书麻烦
  • Nginx 层统一处理 CORS,后端无需改造

七、写在最后

这次排坑让我深刻体会到:

  1. Nginx 配置顺序和位置非常重要,一个小错误就能导致整个服务不可用
  2. CORS 不是后端的事,是 Nginx/网关层的事,统一处理比每个后端服务单独配置要优雅得多
  3. HTTPS 是端到端的,但中间代理可以用 HTTP 转发,不影响整体安全性
  4. 遇到问题先看日志,Nginx 的错误日志非常详细,能定位 90% 的问题

希望这篇文章能帮助到正在被 Nginx 跨域问题困扰的您。如果您有更好的实践或发现文章中的错误,欢迎交流讨论!


相关文章推荐

  • MDN: CORS 跨域资源共享
  • Nginx 官方文档:ngx_http_proxy_module
  • Let’s Encrypt 免费 SSL 证书申请指南
http://www.jsqmd.com/news/613004/

相关文章:

  • 号易招商:0门槛0抽佣,成为一级代理赚取全额佣金 - 号易官方邀请码666666
  • 音频设备效率革命:极简操作实现Windows音频管理新体验
  • NVIDIA Profile Inspector技术深度解析:驱动级游戏性能调优实战指南
  • 终极鼠标抖动工具指南:告别屏幕锁定的5种实用方案
  • 独家披露:某省级政务平台PHP容器化国产化迁移全周期数据(耗时28天/零回滚/100%信创名录覆盖)——含架构图、镜像层分析与审计日志样本
  • 普通手机gps信息样本
  • 模块化设计革命:新型制氮设备如何满足柔性生产需求 - 品牌推荐大师1
  • wvp-GB28181-pro企业级视频监控平台架构设计与高可用部署指南
  • Qwen3.5-9B农业技术推广:病虫害图片诊断+防治方案生成+农事提醒
  • Win11Debloat:三步解决Windows 11臃肿问题,让你的电脑重获新生
  • PySimpleGUI实战:从零构建Python桌面应用界面
  • Windows音频管理革命:AudioSwitch一键切换与精细控制解决方案
  • Oracle迁移替代:国内数据库厂商能力排名与深度解析
  • 从零开始:为什么说AppFlowy是下一代AI协作空间的终极选择?
  • Redis连接池崩了?MySQL事务不回滚?Swoole常驻内存引发的5类隐性状态污染,现在不看明天线上告警!
  • 【人生底稿 12】入职 3 个月:从普通开发到小组组长,我是怎么带 4-5 人的小团队扛下核心业务的
  • 昆明富布斯|资质齐全,专业花艺师培训+高效花艺考证 - 深度智识库
  • Agent-Ready不是概念!从0到1构建可插拔智能代理体系,支撑日均亿级调用的电商中台(附开源POC代码)
  • 下一代数据保护:OpenStego 如何重塑信息安全边界
  • 探索Turbo Boost Switcher:揭秘Mac性能精细化控制的45%效率提升方案
  • [具身智能-310]:大模型的神经网络的输入是词向量序列,还是Token序列?
  • G-Helper:华硕笔记本性能调校的终极轻量解决方案
  • 创建专属BongoCat互动模型:从设计到分享的完全实战指南
  • EF Core 10向量查询响应延迟<50ms但云账单暴涨?3步定位Cosmos DB vs PostgreSQL向量后端的真实TCO差异
  • 从「投稿焦虑」到「一键发刊」:Paperxie 期刊论文写作功能全拆解,科研人发刊效率直接拉满
  • RK3588 交叉编译ffmpeg提示rockchip_mpp>=1.3.9 错误的问题
  • 解锁3大性能瓶颈:yuzu模拟器的分级优化指南
  • 猫抓Cat-Catch终极教程:5分钟掌握网页资源嗅探的免费神器
  • 告别重复操作:阴阳师智能辅助脚本让你的游戏时间更有价值
  • 别再死磕UPF语法了!从模块划分实战聊聊Power Domain的规划思路