Nginx 反向代理配置避坑指南(proxy_pass 斜杠、502排查、CORS、文件上传)
作者:追梦开发者
文章导读:本文系统整理 Docker 环境下 Nginx 反向代理配置的高频踩坑场景,涵盖 proxy_pass 末尾斜杠行为差异、502/504 三大排查路径、upstream keepalive 配置、Host 与真实 IP 透传、文件上传 413 处理、CORS 完整配置及 location 匹配优先级,每个问题给出可直接使用的配置片段。适合正在排查 Nginx 反向代理问题或准备上生产的开发/运维人员。
一、改配置前必做:nginx -t 验证
每次修改 Nginx 配置后,必须先执行语法验证再 reload,避免配置错误导致 reload 失败、服务无法恢复。
# 在运行中的容器里验证 docker exec nginx-container nginx -t # CI/CD 场景:用临时容器验证,不依赖运行中的服务 docker run --rm \ -v $(pwd)/nginx.conf:/etc/nginx/nginx.conf:ro \ nginx:1.26.2-alpine nginx -t
验证通过后再 reload,一行命令串联:
docker exec nginx-container nginx -t && docker exec nginx-container nginx -s reload
注意:Nginx 报错提示的行号有时与实际出错位置偏移几行,配合错误描述文字来定位,不要只看行号。
二、proxy_pass 末尾斜杠:行为完全不同
这是 Nginx 反向代理中被问频率最高的问题。proxy_pass 末尾有无斜杠,转发路径行为完全不同。
行为对比
# 写法 A:无斜杠
location /api/ {
proxy_pass http://backend;
}
# 写法 B:有斜杠
location /api/ {
proxy_pass http://backend/;
}
| 客户端请求路径 | 写法 A 转发到 | 写法 B 转发到 |
|---|---|---|
| /api/users | /api/users(保留前缀) | /users(去掉前缀) |
| /api/v1/order | /api/v1/order | /v1/order |
选择依据
后端路由是什么,就用哪种写法:
# 后端路由:GET /users → 用有斜杠版本
location /api/ {
proxy_pass http://backend/;
}
# 后端路由:GET /api/users → 用无斜杠版本
location /api/ {
proxy_pass http://backend;
}
常见错误场景
后端路由没有 /api/ 前缀,但 proxy_pass 用了无斜杠写法:
# 错误:后端收到 /api/users,但路由只有 /users,导致 404
location /api/ {
proxy_pass http://backend; # 应改为 http://backend/
}
三、502 Bad Gateway 三大排查路径
遇到 502,按以下顺序逐步排查,不要跳步。
路径一:确认 upstream 服务状态
从 Nginx 容器内部直接发起请求,验证网络连通性和服务可用性:
# 基础连通性测试 docker exec -it nginx-container curl -v http://backend:3000/health # 测试完整请求(带 Host 头) docker exec -it nginx-container curl -v \ -H "Host: example.com" \ http://backend:3000/api/users
如果此步骤不通,问题在网络层或后端服务本身,与 Nginx 配置无关。
路径二:检查超时配置
proxy_connect_timeout 10s; # 与 upstream 建立 TCP 连接的超时 proxy_send_timeout 60s; # 向 upstream 发送请求的超时 proxy_read_timeout 60s; # 等待 upstream 响应的超时(504 的主要来源)
常见场景对照:
| 场景 | 建议值 |
|---|---|
| 普通 API 接口 | proxy_read_timeout 30s |
| 含数据库复杂查询的接口 | proxy_read_timeout 120s |
| 文件导出/报表生成 | proxy_read_timeout 300s |
| WebSocket 长连接 | proxy_read_timeout 3600s |
路径三:检查重试范围配置
# 默认值:仅在 error 和 timeout 时重试 proxy_next_upstream error timeout; # 错误配置示例:把后端正常业务错误也当节点故障处理 # proxy_next_upstream error timeout http_404 http_500; # 会导致一次用户请求触发多次后端调用
如果发现后端接口被调用了两次,或者 POST 请求产生了重复数据,优先检查 proxy_next_upstream 是否配置范围过宽。
四、upstream keepalive 配置
Nginx 与 upstream 之间默认使用短连接,每次请求建立新的 TCP 连接。高并发场景下 TCP 握手开销显著,建议配置 keepalive 长连接。
完整配置
upstream backend {
server backend:3000;
keepalive 32; # 每个 worker 进程保持的最大空闲长连接数
}
server {
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1; # keepalive 要求 HTTP/1.1
proxy_set_header Connection ""; # 清除 Connection: close,启用长连接
}
}
重要:
proxy_http_version 1.1和proxy_set_header Connection ""必须同时配置,缺少任意一行 keepalive 均不生效。这是最常见的"配了等于没配"问题。
参数说明
keepalive 32:每个 worker 进程缓存的最大空闲连接数,不是总连接上限- 实际最大空闲连接数 =
keepalive×worker_processes - 建议值:并发量不高时设 16~32,高并发服务可适当增大
五、Host 头与真实 IP 透传
问题描述
Nginx 转发请求时,默认将 Host 头替换为 upstream 地址(如 backend:3000),将客户端 IP 替换为 Nginx 容器 IP,导致后端应用拿到错误的域名和 IP。
标准透传配置
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $http_host; # 原始 Host(含端口)
proxy_set_header X-Real-IP $remote_addr; # 直连客户端 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # IP 链
proxy_set_header X-Forwarded-Proto $scheme; # 原始协议 http/https
}
$http_host 与 $host 的区别:
| 变量 | 示例值 | 包含端口 |
|---|---|---|
| $http_host | example.com:8080 | ✅ |
| $host | example.com | ❌ |
CDN/SLB 多层代理场景
当 Nginx 前面还有 CDN 或负载均衡时,$remote_addr 是上层代理的 IP 而非客户端真实 IP,需要配置 ngx_http_realip_module:
# 声明信任的上游 IP 段(CDN/SLB 的 IP 段) set_real_ip_from 103.21.244.0/22; set_real_ip_from 103.22.200.0/22; # 从哪个请求头中提取真实 IP real_ip_header X-Forwarded-For; # 递归查找,跳过信任 IP 段,找到第一个非信任 IP real_ip_recursive on;
配置后,$remote_addr 将自动替换为真实客户端 IP。
六、文件上传 413 完整处理
6.1 Nginx 限制配置
# 全局配置(http 块)
http {
client_max_body_size 100m;
}
# 或仅对上传路由生效
location /upload/ {
client_max_body_size 500m;
client_body_timeout 300s; # 客户端上传请求体的超时(慢速上传场景)
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_pass http://backend;
}
6.2 后端框架同步修改
仅修改 Nginx 不够,后端框架通常也有独立的上传限制:
| 框架/运行时 | 配置项 | 默认值 |
|---|---|---|
| PHP | upload_max_filesize + post_max_size(php.ini) | 2M |
| Node.js(Express) | bodyParser limit 参数 | 100kb |
| Spring Boot | spring.servlet.multipart.max-file-size | 1MB |
| Nginx 本身 | client_max_body_size | 1m |
6.3 上传 buffer 优化
# 默认 buffer 只有 8k/16k,大文件上传会频繁写临时文件 client_body_buffer_size 1m; # 分级临时目录,避免单目录文件过多影响性能 client_body_temp_path /tmp/nginx_upload 1 2;
Docker 环境注意:临时目录需要确保挂载了有足够空间的卷,且 Nginx 进程有写权限。
七、CORS 跨域完整配置
7.1 完整配置模板
location /api/ {
# OPTIONS 预检请求单独处理
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods 'GET, POST, PUT, DELETE, PATCH, OPTIONS';
add_header Access-Control-Allow-Headers 'Authorization, Content-Type, X-Requested-With';
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Max-Age 86400;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
# 正常请求添加 CORS 头
add_header Access-Control-Allow-Origin $http_origin always; # always 不能漏
add_header Access-Control-Allow-Credentials true always;
proxy_pass http://backend;
}
7.2 三个常见错误
错误一:* 与 Credentials 同时使用
# 错误配置:浏览器会直接报错拒绝 add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Credentials true;
浏览器规范:Allow-Credentials: true 时,Allow-Origin 不能为 *,必须是具体域名。
多域名场景的正确处理:
set $cors_origin "";
if ($http_origin ~* ^https?://(example\.com|app\.example\.com|staging\.example\.com)$) {
set $cors_origin $http_origin;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials true always;
错误二:漏写 always 关键字
add_header Access-Control-Allow-Origin $http_origin; # 错误 add_header Access-Control-Allow-Origin $http_origin always; # 正确
不加 always:后端返回 4xx/5xx 时,Nginx 不添加 CORS 响应头,浏览器拦截响应,前端只看到 CORS 错误,无法获取真实错误信息。
错误三:未处理 OPTIONS 预检
带自定义请求头(如 Authorization)的请求,浏览器会先发 OPTIONS 预检,Nginx 需要单独响应 204,否则预检失败,实际请求不会发出。
八、location 匹配优先级
Nginx location 匹配不是从上到下顺序匹配,而是按优先级规则决定。
优先级从高到低
| 写法 | 类型 | 优先级 | 说明 |
|---|---|---|---|
| location = /path | 精确匹配 | 1(最高) | 完全相等才匹配 |
| location ^~ /prefix | 前缀锁定 | 2 | 匹配后跳过正则检查 |
| location ~ /regex | 正则(区分大小写) | 3 | |
| location ~* /regex | 正则(不区分大小写) | 3 | |
| location /prefix | 普通前缀 | 4(最低) | 最长匹配胜出 |
典型踩坑案例
# 预期:/api/v2/files/photo.jpg 走第一个 location
location /api/v2/files/ {
proxy_pass http://file-service/;
}
# 实际:正则优先级更高,.jpg 请求走这里
location ~* \.(jpg|png|gif|webp)$ {
expires 6M;
add_header Cache-Control "public";
}
解决:使用 ^~ 锁定前缀,阻止正则匹配接管
location ^~ /api/v2/files/ {
proxy_pass http://file-service/;
}
include 文件加载顺序
/etc/nginx/conf.d/*.conf 按文件名字母顺序加载,default_server 生效的是第一个加载的 server 块,与文件内容书写顺序无关。
# 推荐:用数字前缀明确控制加载顺序 00-default.conf 10-app-main.conf 20-app-admin.conf
九、alias 与 root 的路径行为差异
两个指令都用于指定静态文件根目录,但路径拼接逻辑不同,搞混会导致 404。
root:location 路径追加在 root 之后
location /static/ {
root /data;
}
# 请求 /static/img/logo.png
# 实际查找路径:/data/static/img/logo.png
# ^^^^^ ^^^^^^^^
# root location路径也包含在内
alias:location 路径替换为 alias
location /static/ {
alias /data/assets/; # 末尾斜杠必须有
}
# 请求 /static/img/logo.png
# 实际查找路径:/data/assets/img/logo.png
# ^^^^^^^^^^^^ 只有 alias 路径,location 路径被替换
try_files 注意事项
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
try_files 的路径均相对 root 拼接。如果 root 路径在容器内不存在(如挂载路径写错),try_files 一路找不到,最终返回 403 或 500。Docker 部署时需确认 volume 挂载路径与 root 配置一致。
十、排查命令速查
# 验证配置语法(必须先做) docker exec nginx-container nginx -t # 优雅重载配置 docker exec nginx-container nginx -t && \ docker exec nginx-container nginx -s reload # 查看当前已生效的完整配置(含 include 展开) docker exec nginx-container nginx -T # 实时查看错误日志 docker exec nginx-container tail -f /var/log/nginx/error.log # 从容器内测试 upstream 连通性(排查 502 第一步) docker exec nginx-container curl -v http://backend:3000/health # 测试带 Host 头的请求 docker exec nginx-container curl -v \ -H "Host: example.com" \ http://backend:3000/api/users # 测试 CORS 预检请求 curl -v -X OPTIONS \ -H "Origin: https://example.com" \ -H "Access-Control-Request-Method: POST" \ http://your-nginx/api/users
总结
本文覆盖了 Nginx 反向代理配置中的 10 类常见问题:
- 改配置先验证 → nginx -t + && 串联 reload
- proxy_pass 斜杠 → 有无斜杠决定路径前缀是否保留
- 502 排查 → 连通性 → 超时 → 重试逻辑,按顺序来
- keepalive → 必须配对 proxy_http_version 1.1 + Connection ""
- 透传头 → Host、X-Real-IP、X-Forwarded-For 标准写法
- 413 文件上传 → Nginx + 后端框架同步修改
- CORS → always 不能漏,Credentials 不能用 *
- location 优先级 → 不是顺序匹配,^~ 锁定前缀
- alias vs root → 路径拼接逻辑不同,alias 末尾要加斜杠
- try_files → 依赖 root 路径,容器内路径要和挂载一致
系列文章:
- 第一篇:Docker Nginx 容器网络与端口避坑指南
- 本篇:反向代理配置:proxy_pass、502排查、CORS、文件上传(当前)
- 第三篇:SSL/TLS 配置、WebSocket 代理与性能调优
- 第四篇:日志管理、权限配置、健康检查与生产部署
到此这篇关于Nginx 反向代理配置避坑指南(proxy_pass 斜杠、502排查、CORS、文件上传)的文章就介绍到这了,更多相关Nginx 反向代理配置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
