news 2026/11/4 23:29:21

Nginx请求转发配置实战:从proxy_pass到负载均衡

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nginx请求转发配置实战:从proxy_pass到负载均衡

平时接手项目,最常被问到的就是“帮我配个 Nginx 转发”。一开始我以为就是写两行proxy_pass的事,直到自己踩了几次坑——路径不对、Cookie 丢了、页面无限重定向,才意识到请求转发配置看似简单,里面的门道一点都不少。

这篇东西就是围绕Nginx 请求转发配置写的。我会从最基础的场景聊起,把 location 匹配规则、proxy_pass带不带斜杠的区别、负载均衡、Header 处理这些核心点拆开讲,最后再把我平时排查 502、504、404 这类问题的思路整理出来。适合刚接触 Nginx 的开发、运维,也适合那些后端服务越拆越多、需要靠 Nginx 统一入口的同学。读完不敢说让你成为专家,但至少能让你配完不再提心吊胆。

1. 请求转发到底在解决什么问题

1.1 不止是“换个端口”那么简单

很多人对转发配置的第一印象,是把 80 端口的请求转到 8080,像是给服务开了个“后门”。但实际上,请求转发解决的核心问题是:让多个服务对外表现成一个统一的入口。

举个例子。你有个电商项目,用户服务跑在 8081,订单服务跑在 8082,支付回调跑在 8083。客户端不可能记住这么多端口,更不可能让这些端口直接暴露在公网。这时候 Nginx 在 80 端口守着,根据请求的路径、域名或者 Header,把流量分发给对应的服务。用户访问http://xxx/api/order,Nginx 识别出来这是订单服务,自动转到 8082。

所以说白了,请求转发就是在做“流量分发”,它解决的是多服务架构下入口混乱的问题。我见过不少团队一开始所有接口都写在一个应用里,后来拆服务的时候,发现客户端代码里写死的端口一大堆,改也不是,不改也不是。最后靠 Nginx 一层转发,把新旧路径全部映射好,客户端几乎没怎么动就完成了迁移。这就是转发的价值:它对调用方是透明的,后端怎么拆,前端无感。

1.2 开发联调、多环境复用与流量入口统一

除了多服务分发,请求转发在日常开发里还有一个很实际的用途:本地开发联调。

比如前端在自己电脑上跑了一个开发服务器,端口是 3000,后端接口在测试环境。如果直接让前端代码里写“http://测试环境IP:8080/api”,跨域问题马上来。更常见的做法是,本地 Nginx 监听 80 端口,把所有/api开头的请求转发到测试环境,其他请求转发到本地 3000。前端代码里只写相对路径/api/xxx,既没有跨域,又能随时切换后端地址,只需要改 Nginx 配置里的proxy_pass。

还有多环境复用也是一样。同一套前端代码,要对接开发、测试、预发布三个环境,传统做法是打包三次。但你只要在 Nginx 里配三份转发规则,或者用变量动态指定上游地址,一套产物到处跑。

从运维视角看,转发配置更大的意义在于流量入口统一,所有请求都从固定的 80/443 进来。这样在做限流、超时控制、日志审计的时候,只需要在 Nginx 这一层处理就行,不用每个后端服务各搞一套。安全感强了很多。

2. 配置前要先想清楚的两件事

2.1 location 匹配规则:你写的那条真的会命中吗

转发配置第一道坎不在proxy_pass,而在location。很多新手上来就写:

location /api { proxy_pass http://backend; }

以为“以 /api 开头”的请求都能命中。但实际 Nginx 的匹配规则不是这么简单。它有一套优先级:

匹配类型写法示例优先级
精确匹配location = /api最高
前缀匹配(^~)location ^~ /api/高
正则匹配location ~ \.php$中
普通前缀匹配location /api低
默认匹配location /最低

可以这么理解:Nginx 先看有没有精确匹配,有就直接用;再看有没有^~前缀匹配,有就直接用;然后从头到尾扫正则,命中就用;最后才看普通前缀匹配,然后挑最长的那个。

我记得有个项目就是这么出问题的。配了一个location ^~ /api/,又配了一个location ~ \.(js\|css)$。本来想让静态资源走文件系统,接口走转发。结果因为正则匹配的优先级高于普通前缀匹配,导致某些以/api/开头的请求里带了.js后缀,被正则规则抢走了,直接返回 404。排查了半天,最后在^~前面加了一个^~解决了。

所以配置转发之前,一定要先搞清楚规则命中的顺序。我的建议是:能用^~就用^~,它能直接跳过正则,避免规则打架;能不写正则就不写正则,正则越多越难排查。

2.2 proxy_pass 带不带斜杠,结果天差地别

这是整个转发配置里最容易翻车的地方,没有之一。同样是proxy_pass,后面带不带路径,转发过去的结果完全不一样。

# 方式 A:不带 URI(不带斜杠) location /api/ { proxy_pass http://backend; } # 方式 B:带 URI(带斜杠) location /api/ { proxy_pass http://backend/; }

方式 A 是“原样转发”。请求/api/order/list到了后端,还是/api/order/list,前缀不会丢。

方式 B 是“替换转发”。Nginx 会把 location 匹配到的/api/部分,替换成proxy_pass里带的/,也就是请求到后端变成/order/list,前缀被吃掉了。

这么说有点抽象,我打个比方。方式 A 相当于你叫外卖,你告诉外卖员“到 A 座 301 室”,外卖员原话传给厨房,后端收到的是完整地址。方式 B 相当于外卖员自己先往前走了一层,到厨房门口直接说“301 室”,后端看到的就是去掉楼层后的房间号。

这个区别在前后端分离项目里尤其明显。比如后端接口路径本身是有/api前缀的,你在proxy_pass里多写了一个斜杠,然后所有接口全部 404。更恶心的是,有些请求本来能通,但带了文件后缀的静态请求又通了,让你误以为配置没问题,其实只是巧合。

我个人的习惯是:如果后端接口设计时带了/api前缀,proxy_pass就不带斜杠,保持原样;如果后端路径本来就没有前缀、或者需要剥离前缀,才用带斜杠的方式。而且每次改完配置,我都会用curl实际打一次请求看路径,绝不在浏览器里靠猜。

3. 一份能直接抄作业的请求转发配置

3.1 最基础的场景:按路径转发到后端服务

先来一个最简单的完整示例。场景是这样的:前端静态文件放在/var/www/html,后端接口跑在127.0.0.1:8080,需要让所有/api前缀的请求转发到后端,其余请求直接读本地静态文件。

server { listen 80; server_name example.com; root /var/www/html; location /api/ { proxy_pass http://127.0.0.1:8080; 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; } location / { try_files $uri $uri/ /index.html; } }

这里有几个点值得解释一下。proxy_pass http://127.0.0.1:8080;不带斜杠,所以/api/order会原样传给后端。proxy_set_header Host $host;是让后端看到原始请求的域名,不然后端拿到的是127.0.0.1:8080,如果后端用域名做路由或者生成链接,就会出错。

X-Real-IP和X-Forwarded-For是为了传递真实客户端 IP。因为 Nginx 转发后,后端看到的连接来源是 Nginx 本身的 IP,如果不加这两个头,后端的日志里永远只能看到 127.0.0.1,做风控和审计就麻烦了。

这个配置里我用了try_files $uri $uri/ /index.html;,这是给前端 history 路由用的,刷新子页面时不至于 404。如果只是简单的静态站点,可以只用index index.html;。

3.2 路径前缀剥离与改写:前端说接口多了一层

很多场景里,后端接口路径是不带/api的。比如后端实际路由是/order/list,而你希望对外暴露成/api/order/list。这时候就需要在转发时把/api剥掉。

location /api/ { proxy_pass http://127.0.0.1:8080/; }

注意proxy_pass末尾的/,这就是上一节讲的“带 URI”写法。请求/api/order/list进来,Nginx 把/api/这一截替换成/,后端收到的就是/order/list。

但如果只是想改一部分路径,而不是整体剥离前缀,就需要用rewrite。比如你想把/api/old/开头的请求,转发成后端/new/开头:

location /api/old/ { rewrite ^/api/old/(.*)$ /new/$1 break; proxy_pass http://127.0.0.1:8080; }

这里的写法是:先用正则把/api/old/后面捕获到的部分塞进/new/后面,break表示停止处理 rewrite,然后把改写后的路径通过proxy_pass转发出去。

为什么有时候用 rewrite、有时候直接用斜杠就行?我的经验是:如果只是去掉固定前缀,用斜杠最简单;如果要做前缀替换、或者前后路径结构不一样,就得用 rewrite。另外,rewrite 里的正则我要提醒一句:别贪多,尽量用非贪婪匹配,不然很容易把 query string 也吞进去。正常来说 query string 是 Nginx 自动传递的,不需要在 rewrite 里处理。

3.3 多后端负载均衡:upstream 的正确写法

服务规模大了,一台后端扛不住,就得用upstream配置一组后端服务器。

upstream backend_pool { server 192.168.1.10:8080 weight=3; server 192.168.1.11:8080 weight=1; server 192.168.1.12:8080 backup; } server { listen 80; server_name example.com; location /api/ { proxy_pass http://backend_pool; proxy_set_header Host $host; } }

weight参数控制权重,权重越高分配到的流量越多。backup是备用节点,平时不接收流量,只有其他节点都挂了才会启用。

还有一个很多人忽略的参数叫keepalive。在高并发场景下,如果 Nginx 与后端之间频繁建立 TCP 连接,性能会受影响。可以在 upstream 里配置:

upstream backend_pool { server 192.168.1.10:8080; keepalive 32; }

然后在 location 里加两行:

proxy_http_version 1.1; proxy_set_header Connection "";

这个操作能让 Nginx 和后端之间的连接复用,实测在高 QPS 场景下能减少不少握手开销。我第一次做压测的时候,开了 keepalive 后,请求耗时平均值降了差不多 20%。

需要说明的是,upstream默认的负载均衡策略是轮询。想要更均匀或者更智能的分配,可以加least_conn,让 Nginx 把请求分给当前连接数最少的节点;想要同一用户的请求总打到同一台后端,就用ip_hash。有状态场景(比如带 session 的单体应用)用 ip_hash 能减少不少麻烦,但过度依赖它也不是好习惯,能用 Redis 之类的集中会话还是往那边走。

3.4 转发时的头部处理:host、真实IP和超时

转发过程中最容易忽略的就是 Header,而 Header 恰恰决定了后端能不能正确工作。我见过因为没转发Host头,后端做重定向时把用户导到了一个完全不对的域名上。

所以我在转发配置里基本都会固定带上这几行:

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 $host是让后端知道域名。X-Forwarded-Proto $scheme是告诉后端原始请求是 http 还是 https,否则后端在生成链接、做 HTTPS 重定向的时候会出错。

还有一个容易踩的坑是WebSocket 转发。WebSocket 协议升级需要额外两个头:

proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";

不配这两行,WebSocket 握手会一直失败,前端控制台报各种奇怪的错。我记得有一次查了半天,日志里一直报 400,最后才想起来忘了放这两个头。

超时参数也很关键。默认的proxy_read_timeout是 60 秒,如果后端处理一个请求超过了 60 秒,Nginx 就会直接掐断连接,前端收到 504。处理大文件导出、批量计算这类场景,建议把超时调大一点:

proxy_connect_timeout 10s; proxy_send_timeout 120s; proxy_read_timeout 120s;

connect_timeout别调太大,默认 60 秒已经很久了。后端连不上,等几十秒才报错,用户早跑了。一般 10 秒够用。read_timeout才是按业务需求调的,要看后端的接口最长处理时间是多少。

4. 常见问题与排查技巧实录

4.1 502 和 504 到底是谁的问题

这是转发配置里遇到最多的两类错误,很多人一看到 502/504 就慌,其实排查思路很固定。

502 Bad Gateway,意思是你 Nginx 连接不上后端。先查后端进程到底起没起:

ps -ef | grep java

再确认端口通不通:

curl http://127.0.0.1:8080/api/test

如果 curl 能通,Nginx 还是 502,那就看proxy_pass写对了没有,特别是 upstream 里的 IP 和端口是不是写错了。还有一种情况是防火墙挡了,Nginx 所在机器访问不到后端机器的端口,也会报 502。下行链路的问题和上行链路的问题要分清楚,直接用curl -v看连接过程最快。

504 Gateway Timeout,意思是 Nginx 连上了后端,但后端在超时时间内没返回结果。先按上一节提到的,把proxy_read_timeout调大试试。如果调大还是 504,那基本可以断定是后端接口本身太慢,比如 SQL 没优化、第三方接口响应慢、线程池满了在排队。这时候与其纠结 Nginx 配置,不如去看后端日志。

我自己的排查顺序是:先 curl 后端接口测耗时,如果接口本身要 30 秒,Nginx 默认 60 秒应该够;如果接口要 90 秒,就得调proxy_read_timeout。如果 curl 也报错,那就是后端的问题,Nginx 只是个传话的,别冤枉它。

4.2 转发后一直 404,先查路径再查规则

转发后 404 是另一个高频问题。原因无非两种:路径不对,或者规则不对。

路径不对的情况,多半是proxy_pass带不带斜杠没搞清楚。我在第 2 节已经详细讲过,这里再给一个快速自查方法:打开 Nginx 的 access log,看看到底转发给了谁、转发路径是什么。

tail -f /var/log/nginx/access.log

如果日志显示转发的路径是/api/order,而你的后端接口实际是/order,那就说明前缀没剥掉,检查proxy_pass末尾的斜杠。

规则不对的情况,是 location 匹配到了错误的位置。比如你写了两个 location:

location /api { proxy_pass http://backend; } location ^~ /api/admin { proxy_pass http://admin_backend; }

本来想让/api/admin走管理后端,但因为^~优先级高于普通前缀,这个配置其实是对的。但如果把第二个写成不带^~的普通前缀,两个规则到底谁的路径长谁就赢,你可能根本分不清到底匹配了谁。所以我的建议是:规则多的时候,一定要用nginx -T把最终生效的配置打印出来看,再配合curl验证。

4.3 页面无限重定向,多半是 Host 头在捣乱

还有一个很经典的场景:转发配置好了,页面能打开,但登录后疯狂重定向,最后浏览器提示“重定向次数过多”。

这种情况十有八九是后端返回的重定向地址和实际访问地址不一致。举个例子,用户访问的是http://example.com/login,Nginx 转发给后端http://192.168.1.10:8080/login。后端处理完后,生成一个重定向地址http://192.168.1.10:8080/home返回给浏览器。浏览器拿到这个地址,直接去访问192.168.1.10:8080,而这个地址只有内网能通,或者根本没配转发,于是各种报错、反复跳转。

解决办法就是我在第 3 部分强调的proxy_set_header Host $host;。让后端认为用户访问的就是example.com,生成的链接自然就对了。

如果后端用的是 HTTPS 协议,还需要同步配置X-Forwarded-Proto $scheme,否则后端会认为进来的是 http,生成重定向链接时又跑回 http,和站点的 HTTPS 强制跳转互相打架,也是一样的死循环。

4.4 定位问题的三板斧:日志、curl 和 nginx -T

最后我把平时排查问题的工具和思路整理成一个表格,遇到问题照着走就行。

排查手段用途常用命令
实时日志看请求实际转发到哪个地址、状态码多少tail -f /var/log/nginx/access.log
错误日志看连接失败、超时、DNS 解析错误等底层原因tail -f /var/log/nginx/error.log
curl 测试模拟请求,带上 Host 头验证后端接口是否正常curl -v -H "Host: example.com" http://127.0.0.1:8080/api/test
配置检查确认当前生效的配置内容,防止配置文件多份导致混乱nginx -T
验证配置语法改完配置先检查,再 reloadnginx -t && nginx -s reload

这里特别说一下nginx -T。很多线上环境 Nginx 配置被拆成很多个小文件,nginx -T能把所有配置合并输出,比较清楚地看到最终内容里到底有没有proxy_pass写错、location优先级是否如预期。每次改完配置,我都要跑一遍nginx -t,语法不对就直接报错,别等 reload 挂了才后悔。

还有一个技巧,把 access log 的格式稍微改一下,加上$upstream_addr和$upstream_response_time,就能看到每次请求实际打到哪台后端、后端响应花了多少毫秒:

log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for" ' 'upstream_addr=$upstream_addr upstream_response_time=$upstream_response_time';

有了这个日志,调负载均衡的时候能看到每台机器的响应时间,排查慢请求也直观得多。

平时配置转发出问题,绝大多数不是什么高级故障,就是路径、Header、超时这三块没有处理好。按我上面说的思路,先确认后端接口本身可用,再看 Nginx 是不是把请求原样传对了,然后看 Header 有没有让后端误解,最后看超时时间够不够,基本都能解决。还是那句老话:Nginx 只是一个传话人,别让它替你背锅,但也别让它把话传歪了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/4 20:18:13

昆山停业企业税务零申报管理一站式流程,闲置老板必读

昆山很多老板都有过这样一个动作:生意不做了,把营业执照往抽屉里一放,心里想着"反正也没收入,不用交税,放着就放着吧"。 两三年后想重新创业,一查才发现——公司早已被列入税务非正常户&#xff…

作者头像 李华
网站建设 2026/11/5 2:28:40

无锡芯健细胞:管理健康,先读懂身体

无锡芯健细胞:管理健康,先读懂身体 很多人一提到健康管理,第一反应是"我要做什么项目"。但真正做过一次系统评估的人会发现:比起急着"做",更该先"懂"。管理健康,前提是先读懂自己的身体。 为什么"读懂"这一步不能跳过? 因为身体是一个…

作者头像 李华
网站建设 2026/11/5 7:29:54

ApkToolBox实战:APK查壳与反编译的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/2 23:20:46

AgentCausal:面向大模型智能体的反事实因果调试评测基准

AgentCausal:面向大模型智能体的反事实因果调试评测基准 原文网页:https://arxiv.org/html/2610.13886v1 PDF链接:https://arxiv.org/pdf/2610.13886v1 arXiv编号:arXiv:2610.13886v1 摘要 大模型智能体执行失败时,调试器需要回答因果问题:如果修改某一步动作,最终任务结…

作者头像 李华