1. 先把问题摊开聊这两个参数为什么总是一起出现后端开发的日常里nginx 出现频率最高的场景就是反向代理而 proxy_pass 和 proxy_redirect 又是反向代理配置里最容易被忽略、也最容易坑人的两个参数。我见过太多同事把 proxy_pass 一通乱配页面能出来就觉得完事了结果一遇到后端返回 302 重定向客户端直接跳到一个内网 IP 或者带端口号的地址上浏览器一片白屏半天找不到原因。这种问题本质不是你代码写错了而是 nginx 在转发过程中没有把响应的 Location 头改掉这就是 proxy_redirect 的活。这两个参数的分工很明确proxy_pass 决定请求往哪儿转proxy_redirect 决定后端返回的重定向响应怎么改回对外可见的地址。前者管的是“请求方向”后者管的是“响应里的地址修正”。很多教程把它们分开讲但实际项目里它们必须放在一起理解因为代理场景下请求路径和响应地址是一体两面。你只有把两者的作用边界搞清楚才能写出在真实环境里稳得住的 nginx 配置。这篇文章我会直接拿工作中最常见的场景拆开讲路径拼接规则、斜杠带来的差异、302 Location 头的改写机制、正则和多规则并存时怎么排优先级。全程用的是 nginx 配置口语个别地方会补充原理你可以直接抄走改一改再用。2. proxy_pass请求转发到哪、路径怎么拼全看这一个参数2.1 理解“转发”的本质nginx 替你当中间人在拆细节之前先建立一张地图。当你配置了 proxy_pass实际上就是告诉 nginx收到匹配这个 location 的请求后不要再在自己这里找文件直接把这个请求原封不动或者经过 rewrite 修改后发给配置里指定的后端地址。nginx 在这里扮演的不是“页面转发器”而是“请求搬运工”。而这个“搬运”最关键的分水岭就是 proxy_pass 后面到底有没有 URI也就是路径部分。有路径和没有路径nginx 拼接行为完全不一样。太多事故是出在“我以为它会这样做”但 nginx 的官方文档对这块写得比较收敛新手经常读不出来。我这里先给出一条经验法则如果你不确定要不要加斜杠优先先把场景拆出来分情况测试不要凭感觉写。2.2 不带 URI原始请求路径原样下沉当 proxy_pass 后面只写到主机和端口没有任何路径时nginx 会把原始请求的 URI 整体传给后端。举个例子location /api/ { proxy_pass http://backend_server; }这时候客户端请求http://example.com/api/user/listnginx 实际转发到后端的地址是http://backend_server/api/user/list。注意后端收到的路径和客户端请求的路径一模一样前面的/api/前缀仍然保留着。这种写法适合后端接口本身就有/api前缀的情况。比如你用 Java Spring Boot 写了一套接口context-path 设的是/api那 nginx 这边就应该用这种不带 URI 的写法让路径原样传下去。如果这时候你在 proxy_pass 后面多加了一个/等于把/api吃掉了后端就会 404因为后端根本找不到不带/api前缀的接口。2.3 带 URI用指定路径替换匹配部分这是最容易踩坑的写法。当 proxy_pass 的 URL 末尾带了路径哪怕只是一个/nginx 会把 location 中匹配到的部分“替换”成 proxy_pass 的路径。location /api/ { proxy_pass http://backend_server/; }客户端请求http://example.com/api/user/list这时候 nginx 不是原样转发而是把 URI 里的/api/这一截替换成/所以后端实际收到的是http://backend_server/user/list。再扩展一下如果 proxy_pass 写成/v2/呢location /api/ { proxy_pass http://backend_server/v2/; }请求/api/user/list会变成后端地址/v2/user/list。注意替换规律location 匹配掉的/api/部分被 proxy_pass 里的/v2/替换掉后面剩余的部分user/list原封不动拼上去。这个行为经常让人疑惑因为看起来像是“拼接”但准确的描述应该是“替换”。当你配置 nginx 时只要在脑子里过一遍这个规则匹配掉前缀换成 proxy_pass 的路径剩余部分不动。确认后端实际路由后再动手。还有一个细节必须提醒如果 location 用的是精确匹配也就是不带末尾斜杠例如location /api { proxy_pass http://backend_server/; }请求/api/user时nginx 匹配到/api然后替换成/实际转发路径是/user。但请求/api没有任何后缀时nginx 匹配到的/api替换成/后端收到的是/。这种边界情况在自测时要特别留意很多 404 就是这么来的。2.4 带变量的 proxy_pass直接指定地址的情况还有一种场景proxy_pass 地址不是写死的而是由变量拼出来的。典型例子是配合 map 指令根据请求头或路径动态选择后端map $http_host $backend { default http://default_backend; api.example.com http://api_server; } server { listen 80; server_name example.com; location / { proxy_pass $backend; proxy_set_header Host $http_host; } }这种情况下nginx 的规则和前面静态写法不同。当 proxy_pass 里含有变量时nginx 不会帮你做“替换”而是把你变量计算出来的地址直接作为转发目标然后会把原始请求的 URI 原样带上。也就是说如果变量解析出来是http://api_server请求路径是/api/user后端收到的就是http://api_server/api/user不会再经历“匹配替换”的过程。这里有个非常隐蔽的问题如果变量里带了 URI比如http://api_server/base那 nginx 会把原始 URI 追加上去变成http://api_server/base/api/user。这种拼接经常不是你想要的效果所以带变量的 proxy_pass 配置建议把路径精确定位到只写到主机加端口URI 部分不要写在变量里。2.5 正则 location 的硬性约束不能带 URI另一个容易踩的点如果 location 用的是正则匹配或者用了命名 locationproxy_pass 后面的地址是不能带 URI 的。原因很容易理解——正则匹配到的部分不是一个固定前缀nginx 无法确定该替换哪一段所以它规定这种场景下 proxy_pass 只能写主机加端口转发时 URI 原样传过去。比如这种写法是合法且常见的location ~ ^/api/ { proxy_pass http://backend_server; }但如果你写成location ~ ^/api/ { proxy_pass http://backend_server/; }nginx 在启动时直接报错提示proxy_pass cannot have URI part in this context。这种错误通常在 reload 的时候才暴露生产环境 reload 失败是最麻烦的所以写正则 location 时顺手也检查一下 proxy_pass 是不是干净的。2.6 别忘了 Host 头转发不光是路径的事很多时候路径拼接对了代理还是有问题因为后端通过Host头判断访问域名。nginx 默认把原始请求的 Host 头传给后端但如果你在 proxy_pass 里指定了 IP 地址后端浏览器请求的是example.com后端收到的 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;这几行和 proxy_redirect 是配合着用的。为什么因为后端应用如果知道自己被代理了很多框架会在响应里返回“正确”的绝对地址这就要靠代理头来判断。你把 X-Forwarded-Proto 传过去后端返回的重定向 URL 才会是 https而不是隐藏在后面的 http。这也是为什么 proxy_pass 和 proxy_redirect 要一起理解的根本原因——它们一环扣一环。3. proxy_redirect后端说了句“去这里”但你不能直说3.1 后端返回的 302 为什么会让浏览器“走丢”想象一个典型场景后端接口未登录时返回 302Location 指向登录页。正常情况下客户端拿到 302 后会自动访问 Location 里的地址。但后端在生成这个 Location 时用的地址往往是它自己视角下的地址比如http://192.168.1.5:8080/login这个地址只有内网能访问或者根本是另一个端口。浏览器如果直接跟随这个重定向就会请求一个无法到达的地址。这不是后端 bug而是代理拓扑带来的必然问题。后端不知道用户是通过https://example.com/api访问的它只知道自己接收请求的地址是http://192.168.1.5:8080。nginx 作为反向代理有责任把响应里的 Location 头改成客户端能访问的对外地址。这就是 proxy_redirect 的定位。3.2 default 参数它到底默认做了什么很多人第一次看到 proxy_redirect 的配置时默认就是public或default但不太明白default是什么意思。nginx 文档的原话比较抽象翻译成大白话当后端返回的 Location 头里的协议、主机和端口与 nginx 转发请求时使用的请求地址一致时默认规则会执行替换。更具体地说nginx 会把后端返回的 Location 中与自身代理目标proxy_pass 指向的主机和端口相同的部分替换为客户端请求时的地址$host、$scheme 等。举个例子客户端请求https://example.com/api/usernginx 配置里 proxy_pass 指向http://192.168.1.5:8080。后端收到请求后返回 302Location 是http://192.168.1.5:8080/login。如果 proxy_redirect 没有显式配置默认行为就是把它改写成https://example.com/login——即将http://192.168.1.5:8080proxy_pass 的目标替换成https://example.com客户端请求的原始地址。所以你会发现在很多简单的场景里不用特意配置 proxy_redirect问题也不会暴露。因为 nginx 的默认行为已经帮你挡掉了一部分坑。但一旦出现以下变化默认规则就不够用了后端返回的 Location 不是 proxy_pass 里的地址比如后端自己拼接了一个域名后端返回的 Location 是相对路径后端返回多个不同域名的重定向地址你需要把重定向地址改写成一个完全不同的地址3.3 手动改写用一条规则把 Location 变成你想要的样子先看最常用的写法proxy_redirect http://192.168.1.5:8080/ /;这条配置的意思是把后端返回的 Location 中http://192.168.1.5:8080/这一段替换成/。替换成根路径的好处是浏览器会拿着/加上当前请求的 host 去访问天然就是对外地址。这么写比替换成完整域名更有优势。因为 nginx 不写死对外域名只要客户端用什么域名访问重定向就会自动跟随该域名。你在多个环境测试环境、预发环境、生产环境用同一份配置也不会出错。类似地如果后端重定向到的是http://192.168.1.5:8080/user/login上面的规则会把它改写成/user/login浏览器再结合当前 host最终访问http://你的域名/user/login。整个过程非常优雅。另一种常见场景是后端使用了特地的路径前缀proxy_redirect http://backend_server/app/ /;这条配置把后端返回的任何以http://backend_server/app/开头的 Location都替换成根路径。如果后端是 Java 应用context-path 是/app内部重定向经常带着/app前缀但对外你希望用户看到的是一个干净的 URL这种替换就很有用。3.4 匹配规则不止支持精确字符串还支持正则proxy_redirect 也支持正则表达式这是做复杂改写时的利器。写法上参数分两部分第一部分是匹配规则第二部分是替换结果。如果匹配规则用~开头就表示后面的内容是正则表达式。例如proxy_redirect ~^http://[^/]/(.*)$ /$1;这条的意思是只要 Location 是以http://开头不管后面域名是谁都把域名部分去掉只保留路径然后在路径前加一个/作为新的相对地址。假设后端返回http://192.168.1.5:8080/api/login?next/home改写后就是/api/login?next/home。这里有一个正则细节要注意正则表达式里的小括号是捕获组替换部分用$1、$2引用。这和很多编程语言里的正则替换是一致的。但 nginx 的proxy_redirect正则用的是$1而不是\1别写混了。还有一种常见需求多个后端节点重定向域名各不相同。你可以在同一层里写多条 proxy_redirect 规则nginx 会按顺序匹配第一条命中就生效。所以你可以把后端可能出现的各种域名都列出来proxy_redirect http://node1.internal/ /; proxy_redirect http://node2.internal/ /; proxy_redirect ~^http://[^/]/ /;最后一条作为兜底确保任何后端域名都能被改写成相对路径。3.5 什么时候要有意关闭 proxy_redirect有些场景下后端返回的 Location 头本身就是正确的对外地址不需要 nginx 参与修改。这时可以写下proxy_redirect off;典型的场景是后端已经部署在公网环境或者后端框架知道你设置的对外域名比如通过 X-Forwarded-Host 头它自己生成的重定向地址就是正确的。这时候让 nginx 再改一遍反而是画蛇添足可能把原本正确的域名改坏。但我不建议无脑设置 off。即便后端自己拼了域名只要这个域名是内网域名或者带端口对外客户端一样访问不了。所以在决定 off 之前先抓包确认后端返回的 Location 到底长什么样再判断要不要让它原样透传。3.6 和 upstream 一起用时的注意点使用 nginx upstream 做负载均衡时proxy_pass 指向的是 upstream 组名比如upstream backend_cluster { server 192.168.1.10:8080; server 192.168.1.11:8080; } server { ... location /api { proxy_pass http://backend_cluster; proxy_redirect http://192.168.1.10:8080/ /; proxy_redirect http://192.168.1.11:8080/ /; } }这种配置下建议把每个节点可能使用的地址都写上不然如果 192.168.1.10 返回的重定向是http://192.168.1.11:8080/login你只写了 10 的规则这条响应就漏过去了。我习惯用一条正则兜底来减少遗漏proxy_redirect ~^http://[^/]:8080/ /;这样无论哪个节点产生的重定向只要端口是 8080都会被改写成相对路径。4. 完整实操示例一个典型的 302 跳转修复4.1 场景描述我们做一个后端服务部署在http://192.168.1.100:8080对外入口 nginx 绑定在https://api.example.com。后端是一个 Spring Boot 应用未登录时访问任何接口都会返回 302Location 指向http://192.168.1.100:8080/user/login。前端页面在另一个域名https://web.example.com下前端用 fetch 请求接口如果直接跟随重定向就会失败因为浏览器无法访问192.168.1.100这个内网地址。4.2 nginx 配置server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/nginx/ssl/api.example.com.crt; ssl_certificate_key /etc/nginx/ssl/api.example.com.key; location /api/ { proxy_pass http://192.168.1.100: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; proxy_redirect http://192.168.1.100:8080/ /; proxy_redirect http://api.example.com/ /; } }这里为什么第一行 proxy_redirect 要把后端地址改成/因为客户端通过https://api.example.com/api/xxx访问nginx 替换后 Location 变成相对路径/user/login浏览器会根据当前请求地址自动补全为https://api.example.com/user/login。这个地址对外可达问题解决。第二行写http://api.example.com/是为了防止后端在某些情况下返回一个已经拼好的对外域名。比如后端某些代码里硬编码了http://api.example.com那这个重定向地址本身是可访问的但如果不统一改成相对路径前端拿到的 Location 始终带着http://在 https 页面下会触发混合内容警告所以干脆也一起替换掉。4.3 验证方法不用敲代码也能看效果写完配置后先校验语法再 reloadnginx -t nginx -s reload然后直接用 curl 发起请求看返回头curl -I -k https://api.example.com/api/user/info重点看返回里的 Location 字段。如果配置生效它应该是一个相对路径或者是可公网访问的完整地址而不再带有内网 IP。也可以用-L参数让 curl 自动跟随重定向观察最终是否请求成功curl -L -k https://api.example.com/api/user/info如果看到页面最终能到登录页且 URL 是正确的对外地址说明 proxy_redirect 的改动起作用了。我在实际调试中习惯配合curl -D -把响应头全打出来这样能同时看到 Location 和 Set-Cookie 等信息排查链条更完整。5. 常见问题与排查技巧实录5.1 现象与根因速查表现象大概率原因排查方向代理后后端接口 404proxy_pass 带了 URI把前缀替换掉了确认后端实际路由前缀决定 proxy_pass 是否要保留 location 匹配前缀首页能开接口全部报错proxy_pass 后路径拼接错误打印后端访问日志看实际收到的请求路径302 后跳转到内网 IPproxy_redirect 未配置或默认规则未命中抓包看响应头 Location确认后端返回的完整地址跳转后端口不见了/多了端口后端传回的地址和实际代理地址不一致检查 proxy_set_header Host 和 proxy_redirect 目标地址网页能访问但登录后跳到 https 之外的 httpX-Forwarded-Proto 没有正确设置补上 proxy_set_header X-Forwarded-Proto $scheme;nginx reload 报错 about proxy_pass正则 location 里 proxy_pass 带了 URI去掉 proxy_pass URL 中的路径部分这张表基本覆盖了我经历过的大部分代理类问题。实际工作里我自己排障的顺序是固定的先用curl -I或者浏览器开发者工具看响应头确认是否真的返回了 302再确认 Location 的具体内容。第二步确认 nginx 转发前后实际路径变化这一步往往要开后端访问日志或者在后端代码临时打印一下请求的完整 URL。最后才是改 proxy_redirect 规则。5.2 一个容易被忽略的坑proxy_pass 带变量时的编码问题如果你在 proxy_pass 里用了$request_uri或者$uri这样的变量要小心路径中的特殊字符。$request_uri是原始请求的 URI包含参数它不会经过解码里面的%XX编码会原样保留。而$uri是经过 nginx 解码后的路径两者不一致可能导致后端收到不同的参数形态。举个例子客户端请求/search?q%E6%B5%8B%E8%AF%95。$request_uri的值是/search?q%E6%B5%8B%E8%AF%95而$uri的值是/search参数没包含在内。如果你在 proxy_pass 里写了$uri参数就丢了如果用$request_uri参数正常。但如果后端是严格按解码后的值解析你也可能因为双重编码导致数据不正确。这是另一个层面的坑遇到类似问题时先确认变量选型。5.3 关于 307、308 状态码proxy_redirect 依然适用很多人的印象里只有 302 需要 proxy_redirect。其实 301、307、308 也同样适用因为 proxy_redirect 处理的是响应头里的 Location 字段并不区分是哪个状态码产生的。307 和 308 会保留原始请求方法和 body后端同样会返回 Locationnginx 同样需要改写。所以你在实盘里发现 307 重定向跳错地址排查思路和 302 完全一样。5.4 用云负载均衡时还要多留一个心眼如果你在 nginx 前面还有一层云负载均衡比如阿里云 SLB、腾讯云 CLB那么原始请求到达 nginx 时实际看到的客户端 IP 可能是负载均衡的内网 IP。这时候$remote_addr不准要用X-Forwarded-For或者X-Real-IP。proxy_redirect 的改写逻辑不受影响但要小心配置real_ip模块否则日志和限流模块全都会被误导。我这里给一个参考写法就是把云上转发链路的头信息一并处理好set_real_ip_from 100.64.0.0/10; real_ip_header X-Forwarded-For; real_ip_recursive on;这里只是举例实际 IP 段以云厂商文档为准。nginx 的 set_real_ip_from 意思是“来自这个网段的请求真实 IP 要从后面的 real_ip_header 里提取”。这样配合起来$remote_addr 才能真实反映客户端来源。5.5 经验总结两个参数写之前先问自己三个问题配置之前先确认三件事后端接口的路由前缀是否和 location 前缀一致这决定了 proxy_pass 要不要带 URI、带什么样的 URI。后端返回的重定向地址是基于什么生成的如果基于它自己拿到的请求地址那大概率是内网地址需要 proxy_redirect 改写。客户端实际访问的对外域名和端口是什么这决定了改写后的 Location 应该保持相对路径还是写死完整域名。把这三个问题过一遍写出来的配置才是稳的。我在实际项目里经过反复调整后养成的最关键习惯是proxy_redirect 能写相对路径就写相对路径不要在配置文件里写死环境相关的域名。因为一套配置要在多个环境间流转写死域名就是给自己埋雷。相对路径方案虽然看起来“不高大上”但胜在通用性和稳定性远超写死域名的方案。这个思路也一样适用于项目里其他的 nginx 配置。比如你同时配了多个 server 块、多个 location不同层的 proxy_redirect 生效优先级要心里有数。遇到奇怪的重定向问题先看是不是上层 server 块里设置了全局的 proxy_redirect把里面的规则注释掉再测试一条条排除往往很快就能定位到问题根因。