名字里带着 redirect,于是当 VPN 后面的后端不可达时,它就被塞进 location 块。结果什么都没变,显而易见的结论是这条指令坏了,或者需要别的语法。真实发生的事更简单,也没那么体面:proxy_redirect 处理的是已经回来的响应,而在这次部署里,根本没有请求走到那一步。

两条指令,一个请求里的两个时刻

经过反向代理的 HTTP 请求会依次经过若干判断,这两条指令位于该序列的两端。一条决定请求发往哪里。另一条改写回来那份响应里的某些头部。

DirectiveWhen it actsWhat it can change
proxy_passBefore any bytes leave the proxyWhich upstream receives the request
proxy_redirectAfter the upstream respondsMatching 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 有关。

相关文章