Nginx 反代裸路径 301 带出后端端口的问题
记录时间:2026-09-16
环境:前置 Nginx(/AppHome/nginx2021,8443 块为外网入口,外层安恒 WAF + 华为云 CDN);后端 Nginx 节点(/AppHome/nginx,listen 56780)
一、问题现象
我想把 reverse-proxy-gzgd-https.conf 里 /gmet 的两段式配置(exact-match 301 + 斜杠块)合并成单个无斜杠 location,参考了网上两篇讲 absolute_redirect 的文章。改完访问 https://gz.example.cn/gmet,页面打不开,抓了下响应头:
HTTP/1.1 301 Moved Permanently
Location: http://gz.example.cn:56780/gmet/
Location 里带了后端端口 56780,协议还降级成了 http,外网只有 443 入口,这个地址根本访问不了。
二、排查过程
2.1 尝试 1:单块透传 + absolute_redirect off(无效)
网上两篇文章(文末参考资料)给的解法是 absolute_redirect off(1.11.8+),让 301 的 Location 从绝对地址变成相对路径。我按这个思路把 /gmet 改成:
location /gmet {
absolute_redirect off;
proxy_redirect off;
proxy_set_header Host $host;
...
proxy_pass http://10.0.0.112:56780;
}
上线还是老现象,这里我犯了两个判断错误:
absolute_redirect off只管 Nginx 自己发的重定向。自动补斜杠 301 只对location /gmet/这种带斜杠的前缀触发,改成无斜杠 location 后 Nginx 根本不发 301,请求原样透传给了后端,这个 301 是后端发的。proxy_redirect off帮了倒忙,它关掉的正是”Nginx 改写后端 Location 头”的兜底行为。
2.2 尝试 2:块内 rewrite 补斜杠(推演通过,实测不行)
为了让后端永远收到带斜杠的路径,我改成:
location /gmet {
rewrite ^/gmet$ /gmet/ break;
...
proxy_pass http://10.0.0.112:56780;
}
推演是最优解(服务端改写、客户端零跳转),实测页面异常。gmet 前端是 hash 路由 SPA,入口是 https://gz.example.cn/gmet#/,fragment 不发给服务器;rewrite 后浏览器地址栏永远停在 /gmet,前端路由和资源解析就错了。这个应用必须真实跳转一次。
2.3 翻到真凶:后端也是一台 Nginx
绕了一圈后看了后端节点的配置,发现后端根本不是应用服务器直接响应,也是一台 Nginx:
server {
listen 56780;
server_name localhost;
location /gmet {
alias /AppHome/csptt/services/gmet;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
...
}
坏 301 的四个组成部分全部对上了:
| Location 片段 | 来源 |
|---|---|
http: |
后端 Nginx 自己的监听协议 |
gz.example.cn |
前端 proxy_set_header Host $host 透传过去的 Host 头 |
:56780 |
port_in_redirect 默认 on,带出后端自己的 listen 端口 |
/gmet/ |
location /gmet + alias 指向目录,裸路径触发目录补斜杠 301 |
三、根因分析
裸路径 /gmet 从前端单块裸透传到后端 Nginx,后端 alias 指向的是一个目录,URI 没有以斜杠结尾,触发静态目录自动补斜杠 301。这个 301 是后端 Nginx 自产的,构造 Location 时协议取自己的 http、域名取透传的 Host 头、端口取自己的 56780。响应原样穿透前端(absolute_redirect 管不到,proxy_redirect off 还把改写关了)回给浏览器,浏览器去访问外网不存在的 56780 端口。
四、解决方案
从源头改后端。
4.1 后端节点
在 56780 的 server 块加一行:
server {
listen 56780;
server_name localhost;
absolute_redirect off; # 目录补斜杠 301 改为相对路径 Location: /gmet/
...
}
后端仍然发 301,但 Location 变成相对的 /gmet/,前端原样透传,浏览器按当前地址栏的 origin(https://gz.example.cn)补全,fragment 保留,地址栏落到 /gmet/#/。
前提是后端 Nginx 版本 >= 1.11.8,先 nginx -V 确认。这个 server 块是 56780 独立端口,影响面可控。
4.2 前端节点
/gmet 改成单块裸透传:
location /gmet {
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_set_header X-Forwarded-Scheme $scheme;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-Port $server_port;
proxy_pass http://10.0.0.112:56780;
access_log /AppLogs/ngx_acc_logs/gmet.gz.example.cn.log.json aka_logs;
}
proxy_redirect 保持默认(不写):相对 Location 不匹配改写规则、原样透传;万一后端哪天又回绝对地址,default 模式还能按 proxy_pass 地址前缀兜底改写。
各配置项说明:
| 配置项 | 值 | 说明 |
|---|---|---|
| proxy_set_header Host | $host | 透传原域名,后端 301 的 Location 用它拼域名 |
| proxy_set_header X-Real-IP | $remote_addr | 透传客户端真实 IP |
| proxy_set_header X-Forwarded-For | $proxy_add_x_forwarded_for | 追加代理链 IP |
| proxy_set_header X-Forwarded-Proto / X-Forwarded-Scheme | $scheme | 告知后端原始协议(当前静态 Nginx 用不上,留作以后换应用服务) |
| proxy_set_header REMOTE-HOST / X-Forwarded-Port | $remote_addr / $server_port | 历史兼容透传 |
| proxy_pass | http://10.0.0.112:56780 | 不带 URI 部分,原样透传请求路径 |
| access_log | aka_logs | 独立按业务切分的 JSON 访问日志 |
五、验证
后端生效验证(后端节点上):
curl -i http://10.0.0.112:56780/gmet
预期输出:
HTTP/1.1 301 Moved Permanently
Location: /gmet/
实测返回的就是纯相对路径,无域名无端口。
前端部署后待验证:同步配置 + nginx -t + nginx -s reload,华为云 CDN 刷新 https://gz.example.cn/gmet(测试期的坏 301 可能被边缘缓存),外网 curl 确认 Location: /gmet/,浏览器无痕访问 /gmet#/ 应落到 /gmet/#/。
六、注意事项
- 单块无斜杠 location 会连 /gmetxxx 这类粘连前缀一起匹配,透传给后端后 404,实际无碍。
- 后端若撤销 absolute_redirect off,前端单块会复现坏跳转,两侧存在耦合,前端配置处已加注释说明。