根本原因是后端未识别真实协议,需在nginx中配置proxy_set_header x-forwarded-proto $scheme;并启用后端对应解析逻辑(如spring boot设server.forward-headers-strategy=framework),同时正确透传host头、合理使用proxy_redirect处理绝对重定向url。

后端误判 $scheme 导致跳转到 http:// 而不是 https://
典型现象是:用户访问 https://example.com/login,后端却返回 Location: http://example.com/dashboard,浏览器被重定向到 HTTP 地址,接着又被 Nginx 强制 301 到 HTTPS,形成死循环。根本原因是后端(如 Spring Boot、Next.js、Django)没识别出真实协议,只看 request.scheme 或 req.protocol,而它读到的是反向代理传过去的原始值(常为 http),不是用户实际用的 https。
必须让后端能区分真实协议:
-
proxy_set_header X-Forwarded-Proto $scheme;—— 这行不能少,且必须写在location块里、proxy_pass之后 - 后端应用需启用对应解析逻辑:Spring Boot 要设
server.forward-headers-strategy=framework;Next.js 需在getServerSideProps或headers()中检查X-Forwarded-Proto;Django 要开SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') - 避免同时设置
proxy_set_header X-Forwarded-Proto https;(硬编码),否则多层代理或 HTTP 入口时会出错
proxy_redirect 无法覆盖绝对 URL 重定向时的兜底方案
即使后端已正确识别协议,仍可能返回形如 Location: https://localhost:8080/finish 的绝对地址——Nginx 默认不改写这类响应头,导致客户端跳转失败或绕过代理。
用 proxy_redirect 显式重写:
- 匹配并替换完整协议+主机+端口:
proxy_redirect https://localhost:8080/ $scheme://$host/; - 更健壮的正则写法(适配多种后端地址):
proxy_redirect ~^https?://[^/]+(/.*)$ $scheme://$host$1; - 注意顺序:
proxy_redirect必须放在proxy_pass之后,否则不生效 - 若后端返回的是相对路径(如
Location: /login),默认行为已足够,无需额外配置
Host 头透传错误引发的二次代理循环
现象是 Nginx 日志中出现大量重复的 X-Forwarded-For IP、请求耗时飙升、最终返回 413 Request Entity Too Large 或 500 Internal Redirection Cycle。本质是后端把 Nginx 当作普通客户端,又把请求打回给 Nginx 自己,形成套娃。
关键点在 Host 请求头处理:
- 不要写
proxy_set_header Host $http_host;—— 它会把用户原始 Host(如example.com:8443)传给后端,后端生成跳转 URL 时可能带错端口 - 应改为
proxy_set_header Host $host;(不含端口)或更稳妥的proxy_set_header Host $proxy_host;(即 upstream 的 Host) - 如果后端依赖 Host 做路由(如多租户 SaaS),可显式写死:
proxy_set_header Host backend.example.com; - 配合
proxy_set_header X-Forwarded-Host $host;,供后端做辅助判断
非根路径代理(如 /admin/)下跳转路径错位
例如 Nginx 配置 location /admin/ { proxy_pass http://127.0.0.1:3000/; },后端返回 Location: /dashboard,结果浏览器跳转到 https://example.com/dashboard(丢了 /admin 前缀),404。
这不是协议问题,而是路径上下文丢失:
- 后端需配置基础路径(base URL),如 Next.js 的
basePath: '/admin',Spring Boot 的server.servlet.context-path=/admin - 若后端无法改,可用
proxy_redirect补救:proxy_redirect / /admin/;(把所有相对路径重写为带前缀) - 注意
proxy_pass末尾斜杠:写成http://127.0.0.1:3000/才会剥离/admin/;写成http://127.0.0.1:3000会保留,后端收到/admin/login - 避免混用:不要一边靠后端拼路径,一边靠 Nginx 重写,容易冲突
最易被忽略的是 X-Forwarded-Proto 和 Host 头的组合效果——单独设对一个没用,两个都错才爆雷;而日志里往往只显示最终跳转结果,看不到中间哪一层改写了哪个头。建议用 curl -v 抓响应头,再对照 Nginx access log 确认每段代理是否真传了预期的头。











