BLOG
Nginx强制HTTPS时怎样避免重定向循环
HTTPS跳转要识别代理头和站点实际协议,否则可能反复重定向。
强制HTTPS跳转本身并不复杂,真正麻烦的是浏览器已经通过HTTPS访问,Nginx却仍然认为请求来自HTTP,于是再次返回301跳转,形成循环。这类问题在Nginx直接对外提供服务时很少出现,一旦前面多了CDN、负载均衡器或宝塔面板的代理层,就变得常见。原因在于Nginx判断协议的依据,往往不是浏览器实际使用的协议,而是它收到的请求头。
先确认SSL终止在哪一层
排查重定向循环的第一步,不是改配置,而是弄清楚SSL证书在哪里结束。所谓SSL终止,指的是HTTPS加密连接在哪一层被解密,后续转发使用普通HTTP。
如果Nginx直接绑定443端口并配置了证书,那么$scheme变量取到的值就是https,此时写一个简单的跳转规则不会出问题。但如果Nginx跑在宝塔面板的nginx反向代理之后,或者网站接入了CDN,情况就不同了。CDN回源到源站时,通常使用HTTP协议,源站Nginx收到的请求就是明文HTTP,$scheme取到的是http。这时候如果配置了“HTTP跳转HTTPS”的规则,Nginx会认为当前请求仍然需要跳转,于是再次返回301,浏览器收到后重新发起HTTPS请求,CDN再次以HTTP回源,如此反复,直到浏览器报“重定向次数过多”。
判断SSL终止在哪一层,可以看证书部署的位置。证书装在CDN控制台或负载均衡器上,源站Nginx只监听80端口,那么SSL终止就在前置层。证书直接装在Nginx上,443端口由Nginx监听,SSL终止就在本机。还有一种混合情况,前置层和源站都装了证书,此时需要确认回源协议是HTTP还是HTTPS,这决定了源站Nginx应该信任哪个请求头。
用X-Forwarded-Proto识别真实协议
当SSL终止发生在前置层时,Nginx无法直接感知浏览器的原始协议,但前置层会在转发请求时附带标准请求头X-Forwarded-Proto,其值要么是http,要么是https,表示浏览器实际使用的协议。
Nginx的跳转判断需要改成基于这个请求头,而不是$scheme变量。常见的写法是在server块内用if判断:
```nginx
if ($http_x_forwarded_proto = "http") {
return 301 https://$host$request_uri;
}
```
这里$http_x_forwarded_proto就是Nginx读取X-Forwarded-Proto请求头的方式。当该值为http时,说明浏览器确实在用HTTP访问,此时才执行跳转。如果值为https,则跳过跳转规则,正常处理请求。
需要留意的是,有些前置层使用的请求头名称不同,比如X-Forwarded-Scheme或X-Forwarded-Proto的大小写写法差异。Nginx对请求头名称的解析规则是,将原始头名称转为小写、横线转为下划线,因此X-Forwarded-Proto对应变量就是$http_x_forwarded_proto。如果前置层发送的是自定义头,需要先确认实际名称,再写对应的变量。
只保留一处跳转规则
重定向循环的另一个常见来源是跳转规则重复。Nginx配置里可能同时存在多种跳转写法:server块内写了rewrite ^(.*)$ https://$host$1 permanent;,另一个server块又监听了443并强制跳转到带www的域名,或者宝塔面板的“强制HTTPS”按钮和手动添加的跳转规则同时生效。多个规则叠加时,每个规则都可能触发一次301,浏览器跟随跳转后,新请求又命中另一条规则,循环就出现了。
处理原则是全局只保留一处跳转入口。如果使用宝塔面板,先关闭面板自带的强制HTTPS开关,只保留手动配置的规则,或者反过来,只使用面板开关,删除手动添加的rewrite语句。检查方法是查看完整的站点配置,搜索return 301、rewrite和proxy_redirect相关指令,确认跳转逻辑只出现在一个位置。
另一个容易忽略的地方是proxy_redirect指令。Nginx作为反向代理时,如果后端应用返回的Location头是HTTP地址,Nginx默认会将其改写为当前请求的协议。但当前请求的协议如果是被前置层改写过后的HTTP,那么Location头就可能被改写成HTTP,浏览器收到后再次发起HTTP请求,又触发跳转。这种情况下,需要显式设置proxy_redirect,将后端返回的HTTP Location改写为HTTPS地址。
用curl验证跳转链路
配置修改完成后,验证不能只看浏览器是否正常打开页面。浏览器会自动跟随重定向,最终停留在某个页面,但中间经历了多少次跳转、每次返回什么状态码,浏览器不会直接显示。
使用curl可以完整看到跳转链路。在终端执行:
```bash
curl -I http://你的域名
```
-I参数表示只获取响应头,不下载页面内容。输出中会看到HTTP/1.1 301 Moved Permanently和Location: https://你的域名/,说明第一次跳转正常。接着用-L参数让curl跟随所有重定向,并查看最终状态码:
```bash
curl -IL http://你的域名
```
如果最终返回200 OK,说明跳转链路完整且没有循环。如果curl提示“exceeded max redirects”或类似错误,说明仍然存在循环。
还需要验证HTTPS请求本身不会被再次跳转。执行:
```bash
curl -I https://你的域名
```
这里期望看到200 OK,而不是另一个301。如果HTTPS请求也返回301,说明跳转规则没有正确区分协议,需要检查if判断条件是否写反,或者是否存在其他跳转规则覆盖了443端口的请求。
日志与恢复边界
验证通过后,观察Nginx的错误日志和访问日志。错误日志中如果出现rewrite or internal redirection cycle字样,说明Nginx内部检测到了重定向循环,即使浏览器端表现不明显,也需要处理。访问日志中,如果同一个URL在短时间内出现多次301记录,且每次的Location指向同一个地址,也说明规则有问题。
修改配置前,备份当前正在使用的配置文件。Nginx配置通常位于/etc/nginx/目录下,宝塔面板则存放在/www/server/panel/vhost/nginx/。备份时直接复制整个配置文件,不要只复制片段。修改后执行nginx -t检查语法,确认无误后再重载配置。如果修改后出现异常,用备份文件覆盖回去并重载即可恢复。
恢复边界要事先想清楚:哪些配置改动可以安全回退,哪些改动涉及证书路径或端口监听,回退时可能影响其他站点。只改动跳转规则的情况下,回退风险很低。但如果同时修改了监听端口或SSL证书路径,回退时就要确认这些改动是否与其他站点共享配置,避免恢复一个站点时影响另一个。
最后,在配置文件中给跳转规则加上注释,写明这条规则的作用、判断依据和修改日期。半年后再看配置时,能直接理解当初为什么这样写,而不是靠回忆或重新排查。
评论
登录后可发表评论。