Nginx 配置 HTTPS 后出现无限重定向,通常是 Nginx、后端应用或 CDN 等多层之间,对请求协议(HTTP 或 HTTPS)的判断不一致导致的。
简单来说,就是“A 觉得该用 HTTPS,B 觉得该用 HTTP”,双方互相推诿,导致请求在两者间无限循环。
以下是几种最常见的原因和对应的排查、解决方法。
一、原因一:后端应用未正确识别 HTTPS(最常见)
这是最典型的情况。用户通过 HTTPS 访问,Nginx 将请求通过内部 HTTP 转发给后端。如果后端应用没有正确识别 `X-Forwarded-Proto` 头,会误以为收到的是 HTTP 请求,于是自身再发送一个 301 重定向到 HTTPS。这个重定向请求回到 Nginx,再次被转发,形成循环。
解决方案:
1. 让后端应用信任代理头:这是最彻底的解决方法。让后端程序读取 Nginx 传递的 `X-Forwarded-Proto` 头来判断真实协议。
Flask: 使用 `werkzeug.middleware.proxy_fix.ProxyFix` 中间件。
Express (Node.js): 启用 `trust proxy` 配置。
Django: 设置 `SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')`。
Spring Boot: 配置 `server.forward-headers-strategy=framework`。
2. 在 Nginx 中强制设置协议头:如果无法修改后端代码,可以在 Nginx 的 `location` 块中强制指定协议为 HTTPS。
```nginx
location / {
proxy_pass http://backend_server;
proxy_set_header X-Forwarded-Proto https;
# 其他 proxy_set_header ...
}
```
但更推荐传递真实协议,而非强制写死:
```nginx
proxy_set_header X-Forwarded-Proto $scheme;
```
`$scheme` 变量会自动获取 Nginx 收到的请求协议(HTTPS 或 HTTP)。
二、原因二:Nginx 自身的 HTTPS 配置错误
如果 Nginx 配置不当,也可能自己制造出循环。
在 443 端口的 server 块中写了重定向:这是典型的错误。如果在监听 443 端口的 HTTPS 配置里也写了 `return 301 https://...`,那么所有 HTTPS 请求都会被重定向到 HTTPS,自己重定向自己,形成死循环。
解决方案:强制跳转(从 HTTP 到 HTTPS)的逻辑,应该只写在监听 80 端口的 server 块中。
正确的配置示例:
HTTP 站点 (80端口):负责将请求重定向到 HTTPS。
```nginx
server {
listen 80;
server_name example.com;
# 所有HTTP请求都重定向到HTTPS
return 301 https://$server_name$request_uri;
}
```
HTTPS 站点 (443端口):处理加密请求,里面不要写重定向。
```nginx
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
location / {
# 你的网站配置,如 proxy_pass 或 root
}
}
```
注意 `$server_name` 和 `$host` 的区别:中提到使用 `$host`;中提到使用 `$server_name` 可能因 CDN 等问题导致跳转地址错误,建议直接硬编码域名或使用 `$host`。
三、原因三:CDN(如 Cloudflare)配置问题
当使用 Cloudflare 等 CDN 时,问题可能出在 CDN 的 SSL/TLS 模式上。
Flexible 模式陷阱:如果 Cloudflare 设置为 Flexible 模式,它用 HTTPS 连接用户,但用 HTTP 连接你的源服务器。如果你的 Nginx 配置了强制 HTTPS 跳转,就会收到来自 Cloudflare 的 HTTP 请求,然后 301 重定向到 HTTPS,Cloudflare 再用 HTTP 转发,无限循环。
解决方案:
1. 修改 Cloudflare SSL/TLS 模式:在 Cloudflare 控制台,将 SSL/TLS 模式改为 Full (strict)(完全严格)。此模式要求你的源服务器必须配置有效的 SSL 证书。
2. 优化 Nginx 跳转规则:在 80 端口的 server 块中,增加对 `$http_x_forwarded_proto` 的判断,避免对已通过 CDN 加密的请求再次跳转。
```nginx
server {
listen 80;
server_name example.com;
# 如果 CDN 透传的协议是 http,才进行跳转
if ($http_x_forwarded_proto = "http") {
return 301 https://example.com$request_uri;
}
# 对于直连的 HTTP 请求,也进行跳转
return 301 https://example.com$request_uri;
}
```
四、原因四:多重重定向规则叠加
你的应用可能有多处都设置了重定向逻辑,比如 Nginx 设了一次,后端应用(如 WordPress、Django)又设了一次。
解决方案:统一由 Nginx 负责 HTTPS 跳转,关掉后端应用内部的重定向功能。
五、原因五:浏览器缓存与 HSTS 影响
浏览器会强缓存301 永久重定向。即使你修复了服务器配置,浏览器可能依然“顽固”地执行旧的跳转规则。
解决方案:
1. 使用无痕/隐私模式访问网站,这是最快的验证方法。
2. 清除浏览器缓存和所有历史数据。
3. 清除 HSTS 设置:在 Chrome 地址栏输入 `chrome://net-internals/#hsts`,在“Delete domain security policies”中输入你的域名并删除。
4. 在重定向响应中添加禁用缓存的头(可选):
```nginx
add_header Cache-Control "no-cache, no-store, must-revalidate";
```
六、排查步骤总结
1. 查看浏览器开发者工具:打开 Network 面板,观察请求链路,确认是 301/302 重定向,并查看 `Location` 地址是否在 HTTP 和 HTTPS 间反复横跳。
2. 审查 Nginx 配置文件:
检查443 端口的 server 块内是否有 `return 301` 或 `rewrite...` 重定向指令。
检查80 端口的 server 块是否正确配置了跳转到 HTTPS。
3. 检查后端应用配置:确认后端应用(如 Java、Python、Node.js 应用)是否开启了“信任代理”或正确读取了 `X-Forwarded-Proto` 头。
4. 检查 CDN 设置:如果使用 Cloudflare 等 CDN,确认 SSL/TLS 模式不是“Flexible”。
5. 使用 `curl` 命令测试:用 `curl -I http://your-domain.com` 和 `curl -I https://your-domain.com` 直接查看响应头,可以绕过浏览器缓存。
总结
解决 HTTPS 无限重定向的核心,是确保从浏览器到 Nginx 再到后端应用,整个链路对“当前请求是 HTTPS”这件事达成一致。
排查时请遵循“先看浏览器,再看 Nginx,然后查后端,最后考虑 CDN 和缓存”的顺序,通常能快速定位问题。