一篇文章讲清 NGINX 反向代理:location、proxy_pass、负载均衡与排错

前端请求到了 NGINX,却在后端变成了另一个路径;两台服务都正常,流量却总落在一台;浏览器报 502,改大超时仍然无效。这些问题通常要沿着同一条请求链路排查。本文从反向代理的工作方式讲到 location、proxy_pass 和 upstream,最后给出一份可改造的配置示例与验证顺序。

本文讨论 HTTP 反向代理,示例假设 NGINX 与两台应用实例位于同一可信网络。配置是根据官方文档编写的演示示例,未在当前环境执行;部署时要按实际域名、端口、网络边界和版本调整。

反向代理到底代理了什么

客户端访问 https://shop.example.com/api/orders/42,看到的是统一域名。NGINX 接收这个请求,按配置决定交给哪台应用服务器,再把上游响应送回客户端。应用可以只监听内部地址,不必直接暴露在公网。反向代理并不会神奇地"加速所有请求";它首先解决的是入口、路由、协议和后端实例管理的问题。

一次 HTTP 请求至少要回答四个问题:命中了哪个虚拟主机、哪个 location 处理这个 URI、上游看到的 URI 和请求头是什么、最终选中了哪台实例。遇到 404、登录跳转错误或 502 时,逐个回答比盲目修改配置有效。

图 1:订单请求经过 NGINX 的四层决策,/api/orders/42 在代理阶段变为 /orders/42。

图中的订单请求先由 NGINX 按域名进入对应的 server,再由 /api/ 规则选中代理配置。此处假设上游真正提供的是 /orders/42,所以代理目标写成带 / 的 URI;随后 upstream 在应用实例中选择一个连接目标。若后端实际要求保留 /api/ 前缀,路径置换规则就要改,不能照搬这张图。

server、location、upstream 分别负责什么

server 负责一组监听地址与虚拟主机规则,常见条件是 listen 和 server_name;location 在选定的 server 内按请求 URI 选择处理方式;upstream 给一组后端服务器起名,供 proxy_pass 引用。可以把它们理解为"入口站点 → 路由规则 → 可选的后端集合",但真实匹配规则比这个比喻更严格。

普通前缀 location 通常先比较最长匹配前缀;如果没有 ^~ 阻止继续检查,正则 location 可能覆盖这个前缀结果。精确匹配 location = /health 会优先处理完全相同的 URI。关于完整的匹配次序,应以 NGINX 官方 location 规则为准;不要把配置文件中的书写顺序简单当作优先级。

例如,location /api/ 匹配 /api/orders/42,不会匹配 /apix/orders/42。而 location /api 的范围更宽,也可能匹配以 /api 开头但不是 API 路径的 URI。API 前缀通常写成带尾部斜杠的 /api/,并决定 /api 本身要返回什么或是否重定向。

proxy_pass 末尾的斜杠为什么会改变请求路径

这段规则的关键不是"少写一个字符",而是 proxy_pass 后面有没有 URI。官方文档规定:有 URI 时,用它替换被 location 匹配的规范化 URI 部分;没有 URI 时,通常把原请求 URI 传给上游。具体遇到 rewrite、正则 location 或变量时还有例外,不能把下面的表格套到所有配置。官方 proxy_pass 说明给出了完整规则。

假设请求 URI 是 /api/orders/42?full=1,location /api/ 命中:

配置 上游接收到的路径 适用情况
proxy_pass http://app; /api/orders/42?full=1 后端本身暴露 /api/ 前缀
proxy_pass http://app/; /orders/42?full=1 后端只暴露 /orders/,入口移除 /api/
proxy_pass http://app/v1/; /v1/orders/42?full=1 入口 /api/ 映射到上游 /v1/

图 2:在普通前缀 location /api/ 下,对比 proxy_pass 不带 URI 与带 / 时的转发路径。

这张对照图的前提是普通前缀 location /api/、没有 rewrite 和变量。拿到真实项目时,先问后端路由到底注册在 /api/orders/42 还是 /orders/42,再决定 proxy_pass 后是否附带 URI。出现 404 时查看上游访问日志中的实际路径,通常能立即判断是不是前缀被保留或被替换了。

一份双实例配置如何组织

下面是放在 NGINX http {} 内的演示配置。api_backend 是上游组名,两个应用实例分别监听 127.0.0.1:8081 和 127.0.0.1:8082;若是不同主机,应换成可达的内网地址。示例只涉及 HTTP,不包含证书和生产域名设置。

nginx 复制代码
upstream api_backend {
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

server {
    listen 8080;
    server_name localhost;

    location /api/ {
        proxy_pass http://api_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;

        proxy_connect_timeout 3s;
        proxy_read_timeout 30s;
    }
}

假设应用暴露 /orders/42,请求 http://localhost:8080/api/orders/42 会以 /orders/42 送到某一实例。proxy_set_header Host $host 将客户端请求中的主机名交给后端;默认并不等价于原样保留客户端 Host。X-Real-IP 与 X-Forwarded-For 常用于传递客户端地址,但它们是 HTTP 头。后端只有在确认请求来自可信代理、并明确定义信任链后,才应把这些头当作真实客户端身份依据。

proxy_connect_timeout 控制建立上游连接的等待;proxy_read_timeout 控制相邻两次读取操作之间的等待,并非整个响应允许的总耗时。若业务是长时间不输出数据的任务,单纯增加读超时可能让错误请求占用资源更久;更合理的做法可能是异步任务、轮询进度或流式响应。超时语义可查 官方 proxy 模块文档。

upstream 怎样选择后端

没有显式选择算法时,upstream 按带权轮询分配请求;两个默认权重相同的服务器,会按轮询策略被选择,但不能据此断言每秒请求数严格各占一半。实际结果还受失败重试、连接状况、请求并发等因素影响。NGINX upstream 文档定义了权重和可选算法。

如果实例能力不同,可以给 server 设置 weight,例如 8081 weight=2、8082 weight=1;这是选择比例的配置意图,不是对每一小段流量的强制配额。请求耗时差异较大时可以考虑 least_conn,它选择当前活动连接更少的实例,并考虑权重:

nginx 复制代码
upstream api_backend {
    least_conn;
    server 127.0.0.1:8081;
    server 127.0.0.1:8082;
}

ip_hash 使同一客户端 IP 更倾向于落在同一后端,但它不等于可靠的会话存储:NAT 后面可能有多个用户共享 IP,实例故障和扩缩容也会影响映射。对于登录状态,更稳妥的设计通常是让服务无状态化,或把会话数据放到共享存储,而不是依赖某台进程的内存。选择算法要对应实际状态管理和流量特点。

max_fails 和 fail_timeout 能做被动失败判定,不等于主动健康探测。它们依据请求中的失败情况暂时调整选路;当只有一台服务器时,这些参数还有特殊行为。不要把"配置了 upstream"理解成已获得完整的健康检查、高可用和跨机房容灾能力。

请求失败时,重试会不会造成重复操作

当上游连接失败或超时,NGINX 可能尝试下一个实例;但是重试有明确边界:只要响应已经发给客户端,就无法再把这次请求无缝转交另一台。默认情况下,已经发送给上游的 POST 等非幂等请求通常也不会再转给下一个实例,除非显式开启 non_idempotent 选项。官方 proxy_next_upstream 说明列出这些条件。

即使代理没有重试,客户端、网关或调用方也可能因超时再次发起同一笔创建请求。对于下单、扣款等有副作用的接口,应用层仍需设计幂等键、唯一约束或可追踪的操作状态。代理层的失败转移不能代替业务幂等,也不能仅凭客户端看到 504 就断定上游没有执行成功。

404、502、504 应按什么顺序排查

先确认现象发生在哪一层。404 可能来自 NGINX 的路由,也可能是后端收到错误路径后返回;502、504 常与上游连接、响应或网关处理有关,但单凭状态码无法直接定位根因。建议沿请求链路收集证据:

  1. 检查配置是否被 NGINX 接受。 在安装了 NGINX 的机器上运行 nginx -t。通过后再按部署方式 reload;nginx -s reload 是常见方式,但在 systemd 或容器环境中也可能由服务管理器负责。
  2. 确认真正命中的配置。 用 nginx -T 查看展开后的有效配置,核对 listen、server_name、location 和 proxy_pass。注意输出可能包含敏感配置,不要直接贴到公开工单。
  3. 检查上游连通性。 从 NGINX 所在环境访问后端地址和实际 URI,例如 curl -i http://127.0.0.1:8081/orders/42。容器中的 127.0.0.1 指向容器自身,不能想当然地指向宿主机或其他容器。
  4. 对比入口和上游路径。 再请求 curl -i http://localhost:8080/api/orders/42,查看两边响应以及后端日志。路径不一致先检查斜杠、rewrite 和 location 匹配。
  5. 阅读错误日志与超时阶段。 连接被拒绝、连接超时、等待响应头超时,是不同故障;定位到具体上游地址后,再看应用进程、端口、网络、线程池或慢查询,而不是一律增大 proxy_read_timeout。

如果需要看到每次究竟选了哪台上游,可在访问日志格式中加入 $upstream_addr、$upstream_status、$request_time、$upstream_response_time 等变量,按实际部署配置日志采集。排查完成后再决定是否要调整超时、重试、实例容量或应用接口设计。

最后记住这条链路

NGINX 反向代理最容易被误解的地方,是把入口 URL、转发后的 URI 和后端实例混成一个概念。读配置时按"server 选入口、location 选规则、proxy_pass 决定上游地址与路径、upstream 选实例"的顺序追踪,404 和流量分配问题会清楚许多。上线前用 nginx -t、入口与上游两侧的请求、以及日志核对配置;业务是否可安全重试,则由应用和接口契约共同决定。

参考资料

相关推荐
w3296362714 小时前
飞书接单到月度结算:我用一个 DSH 插件跑通鞋类采购 WMS,附 22 项验收
pdf·node.js·飞书·后端开发·ai agent
李纲明14 小时前
WordPress 打开页面直接报 500 而不是白屏:先核伪静态与文件权限,再查服务器错误日志
nginx·wordpress·文件权限·伪静态·错误日志·devdiy·500错误
Csvn2 天前
Nginx 配置语法与上下文:一次搞懂继承、变量与用 map 替掉 if
后端·nginx
挖掘狂人2 天前
负载均衡到底在干嘛?7 个场景给你说明白
架构·负载均衡
codigger2 天前
负载均衡到底在干嘛?7 个场景给你说明白
网络·架构·负载均衡
秋饼3 天前
GPT-Live 全双工语音 API Java 服务端落地实战:事件协议、会话恢复与生产踩坑
java·ai·技术分享·后端开发
秋饼4 天前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
Csvn5 天前
安装、升级与目录结构
后端·nginx
木头左5 天前
【架构设计】教育类 App 服务端化改造:AI 上移、题库分层与个人题库归属
android·架构设计·系统设计·后端开发·数据库设计