关键在于透传并校验x-forwarded-proto头:nginx需用proxy_set_header传递$real_scheme_secure,结合可信ip校验;后端须主动读该头而非自身连接协议。

关键不是 Nginx 自己用没用 HTTPS,而是最外层客户端连的是 HTTP 还是 HTTPS。Nginx 默认只看自己的连接协议($scheme),如果它前面还有负载均衡、CDN 或 WAF,那么它收到的其实是上游代理转来的 HTTP 请求,$scheme 就会变成 http,后端就误以为用户没走加密通道。
必须透传 X-Forwarded-Proto 头
在 Nginx 的 location 块中显式设置:
-
proxy_set_header X-Forwarded-Proto $scheme;—— 这是最基础且必需的一行,确保后端能拿到原始协议 - 如果 Nginx 是 HTTPS 终结点(即证书配在这里),
$scheme值就是https,这行就能如实传递 - 不要写死成
https,否则 HTTP 请求进来也会被标记为 HTTPS,导致混合内容或重定向异常
多层代理时要区分真实来源
当 Nginx 前面还有可信代理(比如云厂商 SLB),不能直接信 X-Forwarded-Proto,得先校验来源是否可信:
- 用
map指令结合set_real_ip_from配置的可信 IP 段,判断请求是否来自已知代理 - 定义一个安全变量,例如:
map $http_x_forwarded_proto $real_scheme_secure {<br> default "http";<br> "https" "https";<br>}
再配合if ($remote_addr ~ ^192\.168\.10\.) { set $real_scheme_secure "https"; }做双重保障 - 最终用
proxy_set_header X-Forwarded-Proto $real_scheme_secure;向后端传递校验后的结果
后端必须主动读这个头,不能依赖自身连接协议
很多框架默认只看自己接收到的协议(如 Tomcat 的 request.isSecure()),这在代理后是错的:
- Spring Boot:配置
server.forward-headers-strategy=framework,并设TomcatServletWebServerFactory.setRemoteIpHeader("X-Forwarded-For")和setProtocolHeader("X-Forwarded-Proto") - Flask:用
ProxyFix(app, x_for=1, x_proto=1),明确告诉它从第 1 层代理取X-Forwarded-Proto - Node.js Express:启用
app.set("trust proxy", true)后,req.protocol才会基于X-Forwarded-Proto判断 - PHP:检查
$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https',而不是$_SERVER['HTTPS']
不复杂但容易忽略











