Nginx中proxy_set_header X-Forwarded-Proto $scheme;必须放在location或server块内,不能置于http全局块,因$scheme是请求级变量,在http块中未初始化、值为空,无法正确透传真实协议。

在 Nginx 的 http 块中直接用 proxy_set_header 透传客户端真实协议(即 https 或 http)**不推荐且通常无效**。原因在于:proxy_set_header 指令的作用域和变量解析时机决定了它必须在能访问到请求上下文的地方生效——而 http 块是全局配置层级,此时 $scheme 等与具体请求强相关的变量尚未初始化,取值为空或不可靠。
为什么不能只写在 http 块里
$scheme 是 Nginx 的“请求级变量”,它的值取决于当前连接使用的监听端口和 SSL 配置(如 listen 443 ssl)。http 块在 Nginx 启动时加载,不绑定任何具体请求,因此:
-
proxy_set_header X-Forwarded-Proto $scheme;在http块中会被静态展开为空字符串 - X-Forwarded-Proto 头也是空或默认值,无法反映真实协议
正确位置:必须放在 location 或 server 块内
只有在 location 或 server 块中,Nginx 才能为每个请求动态计算 $scheme 并注入头信息。典型写法如下:
✔ 正确示例(在 location 中):
location / {
proxy_pass http://127.0.0.1:3000;
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; ← 这里有效
}
该配置确保每次请求进入此 location 时,$scheme 自动取值为 http 或 https,并准确透传给后端。
如果多个 location 都要代理,怎么避免重复?
可借助 map 指令在 http 块中预定义一个“安全的协议变量”,再在 location 中引用:
✔ 折中方案(推荐):
http {
# 在 http 块中定义映射,基于端口判断协议
map $server_port $forwarded_proto {
443 "https";
default "http";
}
<pre class="brush:php;toolbar:false;">server {
listen 80;
listen 443 ssl;
# ... ssl 配置
location / {
proxy_pass http://backend;
proxy_set_header X-Forwarded-Proto $forwarded_proto; ← 引用 map 变量,安全可靠
}
}
}
这种方式既复用了逻辑,又规避了 $scheme 在全局块失效的问题。
后端必须配合信任该头
仅 Nginx 设置头还不够。后端框架需显式启用对 X-Forwarded-Proto 的信任,否则仍会按自身连接协议(通常是 http)处理:
- Express(Node.js):
app.set('trust proxy', '127.0.0.1');后req.protocol自动生效 - Spring Boot:
server.forward-headers-strategy=framework+server.tomcat.protocol-header=x-forwarded-proto - Django:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
若后端未开启信任,即使 Nginx 正确传递了 https,应用仍可能生成 http:// 链接或拒绝安全 Cookie。











