拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Nginx proxy_pass 路径改写与反向代理配置详解

我第一次在location里写proxy_pass的时候犯过一个特别蠢的错location /api/ { proxy_pass http://127.0.0.1:8080/; }看起来人畜无害结果前端请求/api/user/list后端接口收到的却是/user/list一截/api就这么凭空消失了。当时后端代码又对路径有强校验接口直接 404排查了大半天才意识到是proxy_pass尾部那个斜杠在作怪。Nginx 的proxy_pass指令本质上就是反向代理里的转发动作它决定一个请求被交到哪台服务器、以什么路径交过去。这篇内容就围绕proxy_pass的日常使用展开适合刚把 Nginx 跑起来、准备给后端服务统一入口的人也适合那些配置过反向代理但被路径改写、404、502 搞得头大的同学。1. 先搞明白proxy_pass 转发的是请求还是路径改写1.1 一次请求在 Nginx 里的完整旅程proxy_pass不是独立运转的它必须配合location才有意义。一个 HTTP 请求到达 Nginx 后大致会走这几步先按端口匹配到server块再按 URI 从高到低匹配location最后执行location内的指令。proxy_pass就是那个“执行”环节它告诉 Nginx把当前请求转给某个上游服务器。但问题恰恰出在“转给”这两个字上。很多人以为只要写上proxy_pass http://127.0.0.1:8080;请求就会原封不动地到127.0.0.1:8080。实际上Nginx 在转发前会做一次 URI 拼接拼接规则取决于proxy_pass后面有没有带 URI也就是斜杠后面的那部分。这就像快递转寄地址可以照着原包裹改也可以把收件人信息整个换掉区别就在你填转寄单的时候有没有多写一个“收件人”。1.2 为什么有人配完没事有人一配就 404两种写法的行为差异是很多“幽灵 404”的根源proxy_pass后面不带 URI请求的 URI 原样透传给上游proxy_pass后面带 URI哪怕只有一个/location 匹配到的那一段前缀会被替换掉。举个例子。你有个后端服务接口路径本身是/user/list。如果 Nginx 配置成location /api/ { proxy_pass http://127.0.0.1:8080; }不带 URI后端收到的还是/api/user/list。要是后端根本没有/api这个前缀路由自然 404。反过来如果你想让前端用/api访问、后端去掉前缀写成location /api/ { proxy_pass http://127.0.0.1:8080/; }后端收到的是/user/list正好匹配。两种配置只有尾部一个斜杠之差结果天壤之别。1.3 什么时候用变量会改变一切还有一种写法是proxy_pass使用变量比如location / { proxy_pass $backend_url; }一旦用了变量Nginx 就不做 URI 替换了直接把原始请求 URI 透传上去而且运行时需要resolver来解析域名。这个坑我后面单独讲反正记住一句话能用静态写法就别用变量动态转发场景才需要它。2. location 与 proxy_pass 连用时的 URL 变化对照建议收藏这一节我直接给结论、给表格方便你排查时对照。先说核心规则判断标准只有一个——proxy_pass后面有没有 URI斜杠及之后的内容。有就把location匹配到的那段前缀替换掉没有就原样透传。2.1 五种写法的实际结果假设请求路径都是http://example.com/api/user/listlocation是/api/location 配置proxy_pass 写法上游实际收到的 URI说明location /api/http://backend/api/user/list不带 URI原样透传location /api/http://backend//user/list带/替换匹配前缀location /api/http://backend/new//new/user/list/api/被替换成/new/location /api/http://backend/api//api/user/list替换后看起来和原来一样location /api无尾斜杠http://backend//api/user/list会变/user/list但/api单独访问会触发 301注意 location 尾斜杠差异很多人栽在最后一行location /api和location /api/不是一回事。/api不带斜杠既能匹配/api也能匹配/api/user/list而location /api/只匹配以/api/开头的路径。配置location /api时如果顺手写了proxy_pass http://backend/那https://example.com/api这种不带尾斜杠的访问会被 Nginx 301 到/api/而/api/user/list会被剥掉/api前缀变成/user/list。路径一变后端路由就乱了。2.2 正则 location 的特殊限制用~或~*写的正则 locationproxy_pass后面不能带 URI否则 Nginx 启动直接报错proxy_pass cannot have URI part in location given by regular expression原因不难理解正则匹配的不是固定前缀Nginx 不知道该怎么“替换匹配段”。如果非要在正则 location 里做路径改写就得加变量或者改用rewrite先改路径再代理。日常项目里我建议能用前缀 location 解决的问题就不要上正则配置可读性和可维护性都会好很多。2.3 自己动手验证转发结果判断配置到底转成了什么最直接的办法是看后端日志。不过后端正忙的时候不好翻我更推荐先用curl本地模拟curl -H Host: example.com http://127.0.0.1/api/user/list -v然后在后端服务打印收到的请求路径一眼就能看出 URI 被改成了什么。如果后端不方便打日志还可以临时在后端机器上用nc -l 8080监听端口再发起一次请求看原始请求行里的路径。这个土办法在排查 404 时特别好用。3. 三类典型场景配置从入门到能上生产3.1 最简单的端口转发把 3000 端口挂到 80 上很多人的第一个代理需求就是把本机某个服务端口暴露到 80。比如 Node 服务跑在127.0.0.1:3000想用http://example.com/app访问server { listen 80; server_name example.com; location /app/ { proxy_pass http://127.0.0.1:3000/; 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_pass http://127.0.0.1:3000/;会剥掉/app/前缀后端收到/开头的路径。如果你希望后端也保留/app/前缀就把斜杠去掉改成proxy_pass http://127.0.0.1:3000;。为什么建议加上三行proxy_set_header因为后端拿到请求后经常需要知道真实客户端 IP 和原始 Host。如果不传后端看到的来源永远是 Nginx 自己的地址做日志分析、限流、用户追踪都会出现问题。3.2 去掉前缀的转发前端带/api后端不含/api这是前后端分离项目最常见的诉求。前端统一请求/api/...后端接口却只认/...于是location /api/ { proxy_pass http://backend_server:8080/; }/api/user/login到了后端变成/user/login。这个例子看起来简单但我见到的生产事故里至少有三分之一是有人多加了一层路径导致后端 404。比如后端接口其实是/user/login你却在proxy_pass里写了http://backend_server:8080/api/那后端收到的就是/api/user/login又回到原点了。3.3 多个后端共用一个 Nginx 入口服务器上往往不止一个服务。Nginx 通常作为统一入口按路径分发到不同后端。这块配置建议把所有上游先定义好upstream user_service { server 127.0.0.1:8081; keepalive 32; } upstream order_service { server 127.0.0.1:8082; keepalive 32; } server { listen 80; server_name example.com; location /user/ { proxy_pass http://user_service/; proxy_http_version 1.1; proxy_set_header Connection ; } location /order/ { proxy_pass http://order_service/; proxy_http_version 1.1; proxy_set_header Connection ; } }这里把user_service和order_service定义成 upstream好处是以后扩容只要在 upstream 里加一行server就行不用到处改proxy_pass。另外一个细节是proxy_http_version 1.1和Connection 没有这两行upstream 里配的keepalive根本不生效因为默认 HTTP/1.0 的短连接无法复用。Nginx 到后端的连接频繁重建高并发下会看到大量 TIME_WAIT性能明显下滑。3.4 代理到 HTTPS 上游现在很多内网服务也上了 HTTPSNginx 代理到上游时要注意证书校验问题。如果上游是自签名证书Nginx 默认会校验并失败需要在location里配location /secure/ { proxy_pass https://internal-server/; proxy_ssl_server_name on; proxy_ssl_verify off; }生产环境里除非是内部可控证书否则不建议关掉校验。proxy_ssl_server_name on的作用是让 Nginx 在 TLS 握手时发送 SNI上游如果按域名做虚拟主机就必须开。这个字段虽然不在proxy_pass本身但代理 HTTPS 服务时逃不掉顺便说一句。4. 生产环境踩坑实录三个和 proxy_pass 有关的故障排查全过程4.1 502 Bad Gateway问题不一定在后端有一次服务半夜告警前端全部 502。我第一反应是后端挂了systemctl status一看后端进程活得好好的端口也在监听。再手动curl http://127.0.0.1:8080/health响应正常。那问题就在 Nginx 和后端之间。打开 Nginx 错误日志通常在/var/log/nginx/error.log看到这么一行connect() failed (111: Connection refused) while connecting to upstream“Connection refused”说明连接被拒绝也就是后端端口虽然是监听状态但拒绝新连接。这种情况在处理高并发时很典型后端连接数打满或 Nginx 配置的proxy_pass连到了错误端口。后来一查是部署脚本把后端服务的实际端口从 8080 改成了 8081Nginx 配置没同步更新。排查 502 的顺序我建议固定下来先看后端进程和端口再curl后端本机地址最后看 Nginx 错误日志。如果错误日志里是upstream prematurely closed connection那通常是后端主动断开常见原因是后端超时设置比 Nginx 的proxy_read_timeout短或者 keepalive 配置不匹配。这时候调整proxy_read_timeout、proxy_send_timeout往往就解决了。4.2 404 的根源是路径被悄悄改写另一个印象深刻的故障是前端请求/api/v1/order/detail后端日志里看到的是/v1/order/detail导致路由匹配不上直接 404。这就是典型的proxy_pass尾部斜杠问题——location /api/配了proxy_pass http://backend//api/前缀被剥掉了。事后复盘配置的人本意是想转发到/api/v1/order/detail而不是剥掉前缀。正确的写法有两个方向如果后端接口确实带/api/v1那应该写proxy_pass http://backend;不带 URI原样透传如果后端不想要/api只想要/v1/order/detail那就应该调整location的匹配粒度而不是让proxy_pass尾斜杠去做删除操作。排查这类问题最快的办法是看后端访问日志里的请求行确认实际到达的路径。很多 404 并不是代码 bug而是路径在 Nginx 这一层被改得不符合预期。每次改完proxy_pass我都习惯性用nginx -t先做语法检查再实际请求一次根本不费多少时间。4.3 无限 301proxy_pass 指回了自己还有一种特别隐蔽的坑配置了proxy_pass后浏览器访问某个路径地址栏一直跳转刷几次都停不下来最后浏览器报“重定向次数过多”。打开 Nginx access log 一看全是 301Location 指向的还是当前域名。最典型的成因有两个。一是location写法问题。比如location /app { proxy_pass http://127.0.0.1:8080/; }当用户访问http://example.com/app不带尾斜杠时Nginx 发现location /app匹配到了但代理后又想保留原路径于是先 301 到/app/再进入代理逻辑。可如果后端的路由根本不含/app/跳完还是 404甚至有的配置会在/app/和/app之间反复横跳。二是proxy_pass的域名解析指向了 Nginx 自己。比如配置location / { proxy_pass http://example.com; }example.com恰好也解析到这台服务器请求进来后又代理回 Nginx 本身而 Nginx 又没匹配到合适的location就会陷入循环。排查这种问题看响应头里的 Location 字段最直观如果 Location 里的域名还是自己的域名基本就是代理链路里出现了自我指涉。解决方法是明确区分对外域名和内网代理地址或者用 upstream 指向实际后端 IP。4.4 变量写法在运行时才报错no resolver definedproxy_pass用变量会引入额外限制我前文提到过。实际遇到的报错类似这样no resolver defined to resolve backend.service.consul原因简单Nginx 启动时不会立刻解析变量里的域名等请求到来它才去解析可你没配resolver它就不知道怎么查 DNS。解决方法是加上resolver 127.0.0.53 valid30s;或者干脆把域名换成 IP。如果不是做服务发现、动态路由这种场景我非常不建议用变量写proxy_pass——它绕过了 Nginx 推荐的安全校验还容易踩 resolver 的坑出了问题也难排查。5. 让 proxy_pass 更稳的搭配这几个配套指令别漏掉5.1 代理头信息Host 与真实 IP一个完整的反向代理配置基本都会带上这几行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;为什么这么重要后端做日志统计、限流、权限校验时通常依赖真实客户端 IP。如果 Nginx 不做X-Forwarded-For透传后端拿到的全是 Nginx 内网地址几万用户看起来就像同一个 IP 在访问限流策略直接被击穿。Host头也很关键很多后端服务按域名做虚拟主机路由如果Host没传对请求会落到错误的站点上。5.2 让 Nginx 与后端保持长连接对高并发服务Nginx 与后端之间频繁建立 TCP 连接非常浪费资源。我习惯给每个 upstream 加keepalive然后在location里配合两个指令upstream backend { server 127.0.0.1:8080; keepalive 32; } location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ; }keepalive 32表示 Nginx 会为每个 worker 进程保持最多 32 个空闲长连接到后端。Connection 是清空请求头里的 Connection 字段避免默认的close。加了这两处后端建立的连接可以复用压测下 QPS 能提升不少TCP 连接数也会明显下降。5.3 proxy_redirect处理后端返回的重定向后端接口偶尔会返回 301/302Location 头里带着后端自己的地址。如果后端地址是内网地址客户端拿到后会直接访问内网地址当然访问不到。这时用proxy_redirect重写proxy_redirect http://backend:8080/ /;把后端返回的Location: http://backend:8080/some/path改写成相对路径/some/path客户端就会乖乖走 Nginx 入口。这个指令平时容易被忽略但一旦后端做登录跳转、OAuth 回跳没配它基本上必出问题。5.4 我落地的几个小习惯写proxy_pass这几年我沉淀了一些固定习惯聊当参考写每一条proxy_pass前先在纸上把 URL 走一遍用户输入 →location匹配 →proxy_pass改写 → 后端收到的路径。不要凭感觉写。每份配置落库前必跑nginx -t语法检查再用nginx -T查看最终生效的完整配置防止加载的不是你以为的那份文件。本地测试时用curl -H Host: example.com模拟真实域名访问因为不同server_name下proxy_pass行为完全可能不一样。后端路由比较“挑”的时候尽量让proxy_pass不带 URI路径改写用rewrite或专门的网关层处理避免在 Nginx 里做太多隐式操作。最后分享一个个人体会proxy_pass看起来只是几行配置但它决定了你的服务是被正确送达还是拐进死胡同。很多线上问题追根究底都是对“带不带 URI”这个细节理解不透。花半小时把本文这个规则刻进脑子里配合curl、日志和nginx -T这几个工具以后再遇到反向代理的怪问题基本都能快速定位。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门