proxy_redirect 并不能把 NGINX 接到你的 VPN 主机上
proxy_redirect 只是改写已经抵达的响应里的头部,从不选择上游。先查 proxy_pass,再查通往后端的路由,最后才看头部。
名字里带着 redirect,于是当 VPN 后面的后端不可达时,它就被塞进 location 块。结果什么都没变,显而易见的结论是这条指令坏了,或者需要别的语法。真实发生的事更简单,也没那么体面:proxy_redirect 处理的是已经回来的响应,而在这次部署里,根本没有请求走到那一步。
两条指令,一个请求里的两个时刻
经过反向代理的 HTTP 请求会依次经过若干判断,这两条指令位于该序列的两端。一条决定请求发往哪里。另一条改写回来那份响应里的某些头部。
| Directive | When it acts | What it can change |
|---|---|---|
proxy_pass | Before any bytes leave the proxy | Which upstream receives the request |
proxy_redirect | After the upstream responds | Matching Location and Refresh text |
这个先后顺序就是全部答案。如果第一行失败导致第二行从未执行,第二条指令并没有配置错误,它只是永远到不了。无论怎么改写第二行,都不会凭空造出它本该修改的响应。从第一行往外排查,令人困惑的现象就会归结为路由缺失或连接被拒。
选择上游是 proxy_pass 的事
假设 NGINX 运行在一台公网入口主机上,并通过自己的 VPN 能到达 10.40.0.20:8080 上的应用。挑选该应用的片段,在已有的 server 块里大致如下:
location / {
proxy_pass http://10.40.0.20:8080;
}
请换成真实的协议、地址和端口,并把它当作片段而不是完整部署。入口主机还必须持有 VPN 路由、被允许的转发路径,以及到该服务可用的回程流量,因为代理在纸面上能到达某地址,仍需要内核点头。
proxy 模块参考把上游选择与响应头改写分开说明,并指出带 URI 的 proxy_pass 值会改变匹配到的 location 前缀的替换方式。不核对应用期望的 URL 就增删路径片段,是稳定制造一个看起来像路由故障的 404 的可靠办法。
proxy_redirect 修的是泄露内网地址的响应
流量跑通之后,后端可能回一个 Location 头,里面写着它的内网地址。浏览器跟着这个头走,就会离开公网入口,去访问它解析不了的东西。特意配置的响应改写把内网 URL 映射到公网 URL:
proxy_redirect http://10.40.0.20:8080/ https://app.example.com/;
两边都换成真实值。也要理解范围的狭窄:匹配到的 Location 与 Refresh 头文本会被改写,而 HTML、JavaScript 或下载文件里嵌的 URL 不会被改写。
如果应用到处生成绝对链接,长久的办法是它自身的外部 URL 配置,而不是在代理上越堆越多的改写规则。一路加规则直到输出看着对,得到的是没人测过的第一个页面就崩的配置。应用里一个正确的设置能替掉整份集合。
头部改写修不了什么
好几个相邻问题看着像重定向问题,其实不是。发往上游的 Host 头可能需要匹配后端的虚拟主机。TLS 上游可能需要指定服务器名加证书校验,而改写响应头对这两件事都无能为力。
Cookie 的作用域和域属性属于应用及其配置。代理可以整天丢弃或改写头部,却仍然产出一个在第二次请求就失败的会话,因为 Cookie 是发给浏览器从未访问过的主机的。这种失败看起来时有时无,所以它在工单队列里能活那么久。读一下 Cookie 的域,通常一分钟就能收工。
认证和后端选择也要限制在预期的那一个应用上。公网反向代理应当通过服务器 VPN 只暴露一个应用,而不是变成通往该主机能到达的每个内网地址的通用网关。一个通用 location 把调用方要什么都转发出去,就把一项对外服务变成了你内网的地图。请明确写出上游,并让它保持在那里。
按请求经过的顺序测
依次确认三件事:NGINX 选中了预期的 location、完成了到后端的请求、浏览器跟随后续重定向并加载了子资源。每一步都有自己的日志行,按顺序读,含糊的 502 就变成具体结论。
你要区分的是上游连接失败,和浏览器被送去一个解析不了的内网名。前者出现在代理错误日志里;后者表现为请求根本没到,浏览器抱怨解析不了的主机。两个不同的问题,两个不同的文件,只有一个跟 proxy_redirect 有关。