if ($scheme = http) 存在隐患,因 nginx 中 if 执行不可控且官方警告“is evil”;可靠做法是用两个独立 server 块分离 http/https 流量,或用 map 预定义跳转变量,post 安全跳转应选 307/308 状态码。

为什么 $scheme = http 这种写法看似合理却存在隐患
很多配置直接写 if ($scheme = http) { return 301 https://$host$request_uri; },语法上 Nginx 能解析,但实际运行中容易埋下两类问题:一是 if 在 location 块外使用时,可能被忽略或行为不可控;二是官方明确警告 “If Is Evil”,因为 if 的判断逻辑在 Nginx 的请求处理阶段(如 rewrite 阶段)中属于“非标准流程”,它不保证执行顺序,也不支持嵌套条件,更无法与 proxy_pass、fastcgi_pass 等指令安全共存。一旦后续加入 auth_basic、limit_req 或自定义 header,就可能出现跳过重定向、重复跳转,甚至 500 错误。
真正可靠的做法:用两个独立的 server 块隔离协议
这是 Nginx 官方推荐、生产环境最稳妥的方式。核心思路是:让 HTTP 和 HTTPS 流量天然走不同 server 上下文,彻底避免条件判断带来的不确定性。
- 监听 80 端口的 server 块只做一件事:无条件 301 跳转到 HTTPS,不包含任何 location 或代理逻辑
- 监听 443 端口的 server 块专注处理 HTTPS 请求,加载 SSL 证书、配置 proxy_pass 或 root 路径等全部业务逻辑
- 两个 server 块的 server_name 必须完全一致,否则域名匹配失败会导致跳转失效或 444 错误
示例配置:
server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; }
server { listen 443 ssl; server_name example.com www.example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/key.pem; ... # 其他 HTTPS 业务配置 }
如果必须用单 server 块,如何安全实现条件跳转
某些场景受限于部署结构(如容器化平台统一入口、CDN 回源策略),只能用一个 server 同时监听 80 和 443。此时不能依赖 if,而应改用 map 指令预计算跳转开关——它是 Nginx 变量系统中唯一被设计为“安全、高效、可复用”的条件映射机制。
在 http 块顶部添加:
map $scheme $redirect_to_https { default ""; http "1"; }
然后在 server 块中使用:
if ($redirect_to_https) { return 301 https://$host$request_uri; }
这种方式把判断提前到变量初始化阶段,规避了 if 的执行时序缺陷,且 map 支持多条件组合(比如再加 $request_method 判断是否放过 POST),比手动拼接字符串(如 $rc="hg")更清晰、可维护。
POST 请求不丢参的关键:选对状态码
仅控制跳转时机还不够,还要防止浏览器把 POST 改成 GET。301 和 302 会强制将非 GET 请求降级,而 307 和 308 才能完整保留原始方法和请求体。
- 若需临时跳转(如灰度期),用 return 307 https://$host$request_uri;
- 若确认永久切换 HTTPS,用 return 308 https://$host$request_uri;
- 注意:308 是 HTTP/1.1 新增状态码,老版本客户端(如 IE11)兼容性略差,但现代浏览器及微信内置浏览器均支持
不复杂但容易忽略











