Nginx跨域代理的完整排坑指南(從證書錯(cuò)誤到CORS配置)
一、背景與需求
最近在做一個(gè)項(xiàng)目,架構(gòu)如下:
- 前端域名:
https://www.example.com - 第三方API:
https://thirdparty-api.example.net(不支持 CORS) - 目標(biāo):前端需要調(diào)用該第三方 API,但存在跨域問題
解決方案:使用 Nginx 做反向代理,將請(qǐng)求轉(zhuǎn)發(fā)到自己的子域名 https://api.example.com,并在 Nginx 層添加 CORS 響應(yīng)頭。
預(yù)期請(qǐng)求鏈路:
瀏覽器 → api.example.com (Nginx) → thirdparty-api.example.net
本以為是個(gè)簡(jiǎn)單的配置,結(jié)果前后踩了 6 個(gè)坑,耗時(shí)整整一天。本文將完整記錄排坑過程,希望對(duì)遇到類似問題的朋友有所幫助。
二、踩坑全記錄
坑位1:SSL 證書域名不匹配
錯(cuò)誤現(xiàn)象:
ERR_CERT_COMMON_NAME_INVALID
原因分析:api.example.com 使用了 www.example.com 的 SSL 證書,導(dǎo)致瀏覽器校驗(yàn)證書時(shí)發(fā)現(xiàn)域名不匹配。
解決方案:
為 api.example.com 申請(qǐng)獨(dú)立的 SSL 證書:
certbot certonly --nginx -d api.example.com
教訓(xùn):每個(gè)子域名都需要自己的證書,或使用通配符證書 *.example.com。
坑位2:CORS 預(yù)檢請(qǐng)求失敗
錯(cuò)誤現(xiàn)象:
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
原因分析:
瀏覽器在發(fā)送實(shí)際請(qǐng)求前,會(huì)先發(fā)送一個(gè) OPTIONS 預(yù)檢請(qǐng)求。Nginx 沒有正確處理這個(gè)請(qǐng)求,也沒有返回必要的 CORS 響應(yīng)頭。
解決方案:
在 Nginx 配置中添加 CORS 頭和 OPTIONS 請(qǐng)求處理:
location / {
# CORS 響應(yīng)頭
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;
# 處理預(yù)檢請(qǐng)求
if ($request_method = 'OPTIONS') {
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=utf-8';
return 204;
}
# 代理配置...
}
坑位3:add_header 語(yǔ)法錯(cuò)誤
錯(cuò)誤現(xiàn)象:
[emerg] "add_header" directive is not allowed here in /etc/nginx/conf.d/ssl.conf:47
原因分析:add_header 指令被放在了 Nginx 不允許的位置。這個(gè)指令只能出現(xiàn)在 http、server、location 塊中,不能隨意放置。
錯(cuò)誤示例:
server {
# 正確位置
add_header 'Header1' 'value1' always;
location / {
# 正確位置
add_header 'Header2' 'value2' always;
}
}
# ? 錯(cuò)誤:在 server/location 塊外面
add_header 'Header3' 'value3' always;
解決方案:
確保所有 add_header 都在 server 或 location 塊內(nèi)部。
坑位4:Access-Control-Allow-Origin 重復(fù)
錯(cuò)誤現(xiàn)象:
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' ... 被寫了兩次,導(dǎo)致響應(yīng)頭中出現(xiàn)重復(fù)值。
解決方案:
檢查配置文件,確保每個(gè) CORS 頭只添加一次。如果同時(shí)在 server 和 location 塊中添加了,保留一處即可。
坑位5:代理 Host 頭不正確
錯(cuò)誤現(xiàn)象:
后端 API 返回 502 或 404,但直接訪問后端 API 是正常的。
原因分析:
默認(rèn)情況下 proxy_set_header Host $host 會(huì)將 Host 頭設(shè)置為 api.example.com,而后端 API 可能期望的是自己的域名 thirdparty-api.example.net。
解決方案:
使用 $proxy_host 變量,它保存的是 proxy_pass 中指定的域名:
# ? 錯(cuò)誤:Host 頭變成 api.example.com proxy_set_header Host $host; # ? 正確:Host 頭保持 thirdparty-api.example.net proxy_set_header Host $proxy_host;
坑位6:后端使用 HTTPS 導(dǎo)致證書問題
錯(cuò)誤現(xiàn)象:
Nginx 代理到 https://thirdparty-api.example.net 時(shí)出現(xiàn) SSL 錯(cuò)誤。
原因分析:
Nginx 作為代理去訪問 HTTPS 后端時(shí),需要驗(yàn)證后端證書。如果后端證書有問題(自簽名、過期、域名不匹配等),Nginx 會(huì)拒絕連接。
解決方案:
有兩種方式:
方案A(推薦):使用 HTTP 協(xié)議代理
# 如果后端支持 HTTP,直接用 HTTP proxy_pass http://thirdparty-api.example.net$request_uri;
方案B:忽略證書驗(yàn)證(僅臨時(shí)使用)
proxy_pass https://thirdparty-api.example.net$request_uri; proxy_ssl_verify off; # 關(guān)閉證書驗(yàn)證
三、最終正確的配置
經(jīng)過以上所有坑位的修復(fù),最終配置如下:
server {
listen 443 ssl;
server_name api.example.com;
# DNS 解析器(使用域名代理時(shí)推薦添加)
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;
# 處理預(yù)檢請(qǐng)求
if ($request_method = 'OPTIONS') {
add_header 'Content-Length' 0;
add_header 'Content-Type' 'text/plain charset=utf-8';
return 204;
}
# ========== 代理配置 ==========
# 核心轉(zhuǎn)發(fā)(使用 HTTP 避免后端證書問題)
proxy_pass http://thirdparty-api.example.net$request_uri;
# 關(guān)鍵:使用 $proxy_host 保持原始 Host
proxy_set_header Host $proxy_host;
# 傳遞真實(shí)客戶端信息
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 "";
# 超時(shí)設(shè)置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# API 最佳實(shí)踐:禁用緩存
proxy_buffering off;
proxy_cache off;
}
}
# HTTP 重定向到 HTTPS
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}四、核心經(jīng)驗(yàn)總結(jié)
1. 關(guān)于 SSL 證書
| 要點(diǎn) | 說明 |
|---|---|
| 子域名需要獨(dú)立證書 | api.example.com 不能使用 www.example.com 的證書 |
| 申請(qǐng) 命令 | certbot certonly --nginx -d api.example.com |
| 通配符證書 | *.example.com 可以覆蓋所有子域名 |
2. 關(guān)于 CORS 配置
| 要點(diǎn) | 說明 |
|---|---|
| 必須處理 OPTIONS | 瀏覽器預(yù)檢請(qǐng)求需要返回 204 |
| 頭不能重復(fù) | Access-Control-Allow-Origin 只能出現(xiàn)一次 |
| 位置限制 | add_header 只能在 server/location 塊內(nèi) |
3. 關(guān)于代理轉(zhuǎn)發(fā)
| 要點(diǎn) | 說明 |
|---|---|
Host 頭用 $proxy_host | 保持后端期望的域名 |
添加 $request_uri | 完整傳遞請(qǐng)求路徑 |
| 后端可以用 HTTP | 瀏覽器到 Nginx 需要 HTTPS,Nginx 到后端可以用 HTTP |
4. 調(diào)試技巧
# 測(cè)試 Nginx 配置語(yǔ)法 nginx -t # 查看錯(cuò)誤日志 tail -f /var/log/nginx/error.log # 測(cè)試 CORS 響應(yīng)頭 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"
五、一個(gè)重要的認(rèn)知
Postman 能請(qǐng)求成功 ≠ 瀏覽器能請(qǐng)求成功
| 工具 | 是否檢查 CORS | 說明 |
|---|---|---|
| Postman | ? 不檢查 | 桌面應(yīng)用,不受瀏覽器安全策略限制 |
| curl | ? 不檢查 | 命令行工具 |
| 瀏覽器 | ? 嚴(yán)格檢查 | 為了用戶安全,必須配置正確的 CORS |
這個(gè)認(rèn)知能幫助您在調(diào)試時(shí)快速定位問題:Postman 通但瀏覽器不通 → 99% 是 CORS 配置問題。
六、架構(gòu)總結(jié)
最終的成功架構(gòu):
瀏覽器 -----------(HTTPS)-----------> Nginx -----------(HTTP)-----------> 后端
(www.example.com) (api.example.com) (thirdparty-api)
↑ ↑ ↑
安全連接 SSL證書 + CORS頭 內(nèi)部通信
關(guān)鍵設(shè)計(jì)思想:
- 瀏覽器到 Nginx:必須 HTTPS,證書有效
- Nginx 到后端:可以用 HTTP,避免證書麻煩
- Nginx 層統(tǒng)一處理 CORS,后端無(wú)需改造
七、寫在最后
這次排坑讓我深刻體會(huì)到:
- Nginx 配置順序和位置非常重要,一個(gè)小錯(cuò)誤就能導(dǎo)致整個(gè)服務(wù)不可用
- CORS 不是后端的事,是 Nginx/網(wǎng)關(guān)層的事,統(tǒng)一處理比每個(gè)后端服務(wù)單獨(dú)配置要優(yōu)雅得多
- HTTPS 是端到端的,但中間代理可以用 HTTP 轉(zhuǎn)發(fā),不影響整體安全性
- 遇到問題先看日志,Nginx 的錯(cuò)誤日志非常詳細(xì),能定位 90% 的問題
以上就是Nginx跨域代理的完整排坑指南(從證書錯(cuò)誤到CORS配置)的詳細(xì)內(nèi)容,更多關(guān)于Nginx跨域代理排坑指南的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
同一臺(tái)服務(wù)器安裝多個(gè)Nginx的方法總結(jié)
在同一臺(tái)服務(wù)器上安裝多個(gè)nginx完全沒有問題,但有些地方是需要注意的,這篇文章為大家整理了一些常會(huì)遇到的問題以及解決方法,需要的可以參考一下2023-08-08
通過Nginx實(shí)現(xiàn)前端與后端的協(xié)同部署
在現(xiàn)代 web 開發(fā)中,前端與后端的協(xié)同部署是一個(gè)關(guān)鍵問題,一個(gè)高效的部署策略不僅能提升用戶體驗(yàn),還能簡(jiǎn)化開發(fā)流程,今天,我們就來探討如何利用 Nginx 實(shí)現(xiàn)前端與后端的協(xié)同部署,需要的朋友可以參考下2025-03-03
詳解Nginx中HTTP的keepalive相關(guān)配置
這篇文章主要介紹了Nginx中HTTP的keepalive相關(guān)配置,以及Nginx的Httpd守護(hù)進(jìn)程相關(guān)的keepalive timeout配置,需要的朋友可以參考下2016-01-01
WordPress與Drupal的Nginx配置rewrite重寫規(guī)則示例
這篇文章主要介紹了WordPress與Drupal的Nginx配置重寫規(guī)則示例,文中介紹的rewrite寫法簡(jiǎn)單而突出配置重點(diǎn),需要的朋友可以參考下2016-01-01

