一、背景与需求
最近在做一个项目,架构如下:
- 前端域名:
https://www.example.com - 第三方api:
https://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 -d api.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 不允许的位置。这个指令只能出现在 http、server、location 块中,不能随意放置。
错误示例:
server {
# 正确位置
add_header 'header1' 'value1' always;
location / {
# 正确位置
add_header 'header2' 'value2' always;
}
}
# ❌ 错误:在 server/location 块外面
add_header 'header3' 'value3' always;
解决方案:
确保所有 add_header 都在 server 或 location 块内部。
坑位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 头只添加一次。如果同时在 server 和 location 块中添加了,保留一处即可。
坑位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 -x options https://api.example.com/api/test \ -h "origin: https://www.example.com" \ -h "access-control-request-method: post" \ -v 2>&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,后端无需改造
七、写在最后
这次排坑让我深刻体会到:
- nginx 配置顺序和位置非常重要,一个小错误就能导致整个服务不可用
- cors 不是后端的事,是 nginx/网关层的事,统一处理比每个后端服务单独配置要优雅得多
- https 是端到端的,但中间代理可以用 http 转发,不影响整体安全性
- 遇到问题先看日志,nginx 的错误日志非常详细,能定位 90% 的问题
以上就是nginx跨域代理的完整排坑指南(从证书错误到cors配置)的详细内容,更多关于nginx跨域代理排坑指南的资料请关注代码网其它相关文章!
发表评论