LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

这些 Nginx 反向代理的坑,我替你踩完了(反向代理与负载均衡)

admin
2026年3月27日 15:59 本文热度 52

本文是「Nginx 避坑」系列第二篇,本系列共四篇

后续篇目:HTTPS、安全与跨域 → 进阶与生产实践

这些 Nginx 反向代理的坑,我替你踩完了(反向代理与负载均衡)

https://oa22.cn/bbs.asp?id=38197

你的Nginx正在裸奔:配了HTTPS却从没真正安全过

https://oa22.cn/bbs.asp?id=38196

写了几年Nginx配置,这些错误我见过N多次了

https://oa22.cn/bbs.asp?id=38195

同事改了一行Nginx配置,大促当天系统崩—这5个教训,每一个都是真金白银换来的

https://oa22.cn/bbs.asp?id=38194

上周有个同事找我排查一个 502,搞了快两个小时,最后发现是 proxy_pass 末尾多了个斜杠。就这一个字符,两个小时。

类似的故事我见过太多了。反向代理是 Nginx 用得最多的场景,也是坑最集中的地方——不是因为 Nginx 难,而是因为这些坑看起来太不起眼了,很多人用了好几年都没意识到自己踩着。

今天把我见过的、踩过的、帮人排查过的反向代理坑全部整理出来。对照着检查一遍你现在的配置,大概率能挖出几个隐患。


先说那个折磨过最多人的坑

proxy_pass 末尾加不加斜杠,差别大了去了

说这是 Nginx 史上坑了最多人的配置,应该没有争议。

症状很典型:后端服务直接访问完全没问题,但套了一层 Nginx 代理之后,某些路径就 404 了。后端日志、应用代码挨个查,啥问题都没有。最后才发现,是 Nginx 把路径传错了。

根本原因是这个:

# 写法一:proxy_pass 末尾没有斜杠
location /api {
    proxy_pass http://backend;
}
# 请求 /api/user → 后端收到 /api/user  ✅ 路径原样传过去

# 写法二:proxy_pass 末尾加了斜杠
location /api {
    proxy_pass http://backend/;
}
# 请求 /api/user → 后端收到 //user  ❌ 路径直接乱了

# 写法三:location 和 proxy_pass 末尾都有斜杠
location /api/ {
    proxy_pass http://backend/;
}
# 请求 /api/user → 后端收到 /user  ✅ /api/ 被替换成了 /

规律其实不复杂:proxy_pass 里带了 URI 部分(哪怕只是一个斜杠),Nginx 就会把 location 匹配到的那段路径"剪切"掉,用 proxy_pass 里的 URI 替换。不带就原样传。

记不住规律也没关系,用 rewrite 显式控制,不猜:

location /api {
    rewrite ^/api(/.*)$ $1 break;
    proxy_pass http://backend;
}

从此再也不用靠记忆,看配置就知道后端会收到什么。


三个"看起来没问题,其实一直在坑你"的配置

一、Host 头悄悄被换掉了

后端用了 Spring Boot 或者 Django,本来应该跳转到 https://example.com,结果用户收到的重定向链接变成了 http://127.0.0.1:8080

这不是后端的 bug,是 Nginx 默认把 Host 头替换成了 proxy_pass 里的地址。

加三行就解决了:

location / {
    proxy_pass http://backend;
    proxy_set_header Host              $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;
}

顺带说一句:$host 和 $http_host 大部分时候结果一样,但 $host 更稳,能处理请求里没有 Host 头的边界情况,优先用 $host


二、超时时间设成了一刀切

两种极端都很常见:

极端一:保留默认的 60 秒,后端有个导出报表的接口要跑 3 分钟,结果用户收到 504,但后端日志里这个请求是正常完成的。

location /api/export {
    proxy_pass http://backend;
    proxy_read_timeout    300s;  # 这类慢接口单独放大
    proxy_send_timeout    300s;
    proxy_connect_timeout  10s;  # 连接超时不用放大,快速失败更好
}

极端二:嫌麻烦,全局直接改成 300 秒甚至更大——然后某天后端宕机了,Nginx 傻等 5 分钟,连接积压满了,把自己也拖垮了。

正确姿势:全局保持合理默认值,已知的慢接口单独开小灶。不要一刀切。


三、X-Forwarded-For 被用户伪造了

后端根据客户端 IP 做限速、做安全审计,但有人发现只要在请求头里塞一个假的 X-Forwarded-For,后端就会把这个假 IP 当真的用。

原因很简单:Nginx 默认会把客户端传来的 X-Forwarded-For 原样拼上去,没有任何校验。

解决也简单,分两种情况:

# 情况一:Nginx 直接面向公网(没有 CDN 或上游代理)
proxy_set_header X-Forwarded-For $remote_addr;
# 直接用 $remote_addr,客户端传来的头完全忽略

# 情况二:CDN → Nginx → 后端,Nginx 是中间层
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 追加真实 IP,不覆盖已有链路

负载均衡:算法选错,代价很大

你用的是哪种算法?

默认是轮询,大多数场景没问题。但这两种情况会让你后悔:

场景一:后端接口响应时间差异明显,有的 10ms,有的 500ms。轮询不管三七二十一均分请求,慢接口那台机器越积越多,压垮。

upstream backend {
    least_conn;  # 改成最少连接数,谁空闲谁接活
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

场景二:应用有 Session,没做共享,用轮询导致同一个用户的请求被分到不同机器,登录状态丢失。

upstream backend {
    ip_hash;  # 同一个 IP 固定打同一台机器
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
}

但是ip_hash 有个隐藏问题:如果用户都走同一个出口 IP(公司 NAT、CDN 节点),那所有请求会打到同一台机器,负载完全不均衡。这时候最好的解法是让后端做 Session 共享(Redis),然后用普通轮询,根本上解决问题。


backup 机器 + ip_hash,这个组合会让你查半天

upstream backend {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080 backup;  # ⚠️ 这行不会生效
}

backup 和 ip_hash(以及 hash 指令)不兼容。配了也没用,甚至可能报错。踩过这个坑的人都说:为什么 backup 机器从来不接流量……


健康检查:开源版只有"被动发现"

开源版 Nginx 没有主动健康检查——它不会主动去 ping 后端,只会在请求失败之后才知道那台机器挂了。

实际影响是:在发现某台机器挂掉之前,已经有真实用户的请求失败了

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}

这是能做到的最好配置了:30 秒内失败 3 次,才把这台标记为不可用,等 30 秒后再试。

想要主动健康检查,选项有三个:Nginx Plus(付费)、OpenResty + lua-resty-healthcheck、或者直接用 K8s 让 kube-proxy 来做健康检查。


一个被忽视最多的配置:Nginx 到后端的长连接

没开 keepalive,TIME_WAIT 会把你搞崩

默认情况下,Nginx 对后端使用的是短连接:每来一个请求,新建一条 TCP 连接,用完就关。高并发场景下,这些关掉的连接会进入 TIME_WAIT 状态,等待 2MSL(大概 60 秒)才释放。

量一大,本机端口就耗尽了,后端开始拒绝连接,业务报错。

开启长连接,三行配置:

upstream backend {
    server 192.168.1.10:8080;
    keepalive 32;  # 每个 worker 维持 32 个到后端的空闲长连接
}

location / {
    proxy_pass http://backend;
    proxy_http_version 1.1;         # HTTP/1.1 才支持 keepalive
    proxy_set_header Connection "";  # 清掉 Connection: close,否则后端会主动关
}

补充一点:keepalive 32 不是限制最大连接数,而是空闲连接池的大小。实际并发可以超过这个数,超出的连接用完就关,32 以内的会保留复用。


如果后端 IP 会变,这个坑可能要命

微服务环境、K8s 容器重启、弹性伸缩——这些都会导致后端 IP 变化。但 Nginx 只在启动时解析 DNS,之后不再解析

后端换了 IP,Nginx 还在往老地址打流量,结果就是 502。

# 解决方案:用变量 + resolver,强制每次重新解析 DNS
resolver 8.8.8.8 valid=30s;
resolver_timeout 5s;

location / {
    set $backend "backend.internal:8080";
    proxy_pass http://$backend;
}

proxy_pass 里用了变量,Nginx 就不会缓存 DNS 结果了。


一份完整的 upstream 配置,别漏参数

upstream backend {
    server 192.168.1.10:8080 weight=1 max_fails=3 fail_timeout=30s max_conns=1000;
    server 192.168.1.11:8080 weight=1 max_fails=3 fail_timeout=30s max_conns=1000;

    keepalive 32;
    keepalive_requests 1000;  # 单个长连接最多处理 1000 个请求
    keepalive_timeout 60s;    # 空闲长连接超过 60s 关闭
}

max_conns 这个参数特别容易被忽略:它限制了 Nginx 到单台后端的最大并发连接数,防止某台机器被压垮。需要 Nginx 1.11.5 以上,高并发场景下非常值钱。


最后说一句

这些坑里,有的是一个字符的问题(斜杠),有的是一行配置的遗漏(keepalive),有的是算法选错了方向(ip_hash + NAT)。没有一个是高深的技术问题,但每一个都能在生产上造成真实事故。

对照检查一遍你现在的配置,比什么都值。

下一篇聊 HTTPS、安全加固和跨域——有个能让你陷入重定向死循环的坑,还有一个 add_header 的继承陷阱,估计很多人从来没意识到自己中招了。

如果这篇对你有帮助,转发给还在踩这些坑的同事吧。


阅读原文:原文链接


该文章在 2026/3/27 16:36:52 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved