网站明明已经装了证书,结果一打开就报:
ERR_TOO_MANY_REDIRECTS
或者浏览器一直在 http:// 和 https:// 之间来回跳,刷新几次以后直接打不开。
这个问题我见得最多的场景是:VPS 上跑 Nginx,前面套了 Cloudflare,后面还有 WordPress、宝塔面板、Docker 反代或某个 Web 应用。每一层都觉得自己应该“帮你跳到 HTTPS”,最后就互相打架。
这篇不讲玄学,直接按链路排:浏览器 -> Cloudflare/CDN -> Nginx -> 反向代理 -> 应用。哪一层重复跳转,就在哪一层修。
不要只看浏览器提示。先用 curl 看跳转链:
curl -I http://example.com
curl -I https://example.com
如果想看完整跳转过程:
curl -IL http://example.com
重点看每一段的 Location:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/
HTTP/2 301
location: http://example.com/
如果你看到它一会儿跳 https,一会儿又跳回 http,这就是典型循环。
我一般还会加一个域名解析绕过测试:
curl -I --resolve example.com:443:源站IP https://example.com
这能帮你区分:问题是在 Cloudflare/CDN 层,还是源站 VPS 本身就配置错了。
如果你用了 Cloudflare,先看 SSL/TLS 模式。
最容易出事的是这个组合:
- Cloudflare 选择 Flexible
- 源站 Nginx 又强制 HTTP 跳 HTTPS
Flexible 的意思是:用户到 Cloudflare 是 HTTPS,但 Cloudflare 到你的 VPS 源站用 HTTP。
于是会发生这种事:
- 用户访问
https://example.com - Cloudflare 用
http://example.com请求源站 - Nginx 看到 HTTP,跳到 HTTPS
- Cloudflare 再用 HTTP 去请求源站
- 又被 Nginx 跳回 HTTPS
- 循环开始
这种情况下,别在 Nginx 里乱改半天,先把 Cloudflare SSL/TLS 改成:
Full
更推荐的是:
Full (strict)
前提是源站证书有效。可以用 Let’s Encrypt,也可以用 Cloudflare Origin Certificate。
如果你的源站还没证书,临时用 Flexible 能跑起来,但不适合长期用。只要你源站也做 HTTPS 跳转,就很容易循环。
Nginx 本身也很容易写出重复跳转。
常见配置是:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
这段没问题,意思是 HTTP 统一跳 HTTPS。
但如果你在 HTTPS 的 server 块里又写了奇怪的跳转,比如:
if ($scheme = http) {
return 301 https://$host$request_uri;
}
或者:
return 301 http://example.com$request_uri;
就可能出事。
先把完整配置打印出来:
sudo nginx -T | grep -n "return 301\|rewrite\|server_name\|listen"
再测配置:
sudo nginx -t
一个干净的 HTTPS 站点通常是这样:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.html index.php;
}
如果你要统一去掉 www,也只保留一个方向,不要两个方向互跳。
比如统一到裸域:
server {
listen 443 ssl http2;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
不要同时又在裸域里跳回 www。
很多应用部署在 Nginx 后面,比如:
用户 -> Nginx HTTPS -> Node / Django / Laravel / WordPress
外面已经是 HTTPS,但后端应用收到的是 Nginx 转发过来的 HTTP。如果你没告诉应用“原始请求其实是 HTTPS”,它可能会自己再发一次跳转。
Nginx 反代时建议带上这些头:
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;
}
如果前面还有 Cloudflare,有些场景可以更明确:
proxy_set_header X-Forwarded-Proto https;
但别无脑改。先看你的链路:如果 Nginx 自己就是 HTTPS 入口,$scheme 通常没问题;如果 Cloudflare 到源站仍是 HTTP,那就回到前面说的 SSL 模式问题。
应用层也要信任代理头。
比如 Laravel 要正确配置 trusted proxies;Django 要看 SECURE_PROXY_SSL_HEADER;Express 要设置:
app.set('trust proxy', true)
否则应用看不到真实协议,就可能自己乱跳。
WordPress 是重定向循环高发区。
先看后台设置里的两个地址:
WordPress Address (URL)
Site Address (URL)
它们应该和你最终访问的地址一致,比如:
https://example.com
如果后台进不去,可以直接查数据库:
wp option get home
wp option get siteurl
没有 WP-CLI 的话,用 MySQL 查:
SELECT option_name, option_value
FROM wp_options
WHERE option_name IN ('home', 'siteurl');
修正:
wp option update home 'https://example.com'
wp option update siteurl 'https://example.com'
如果你用了缓存插件、安全插件、SSL 插件,也可能重复做 HTTPS 跳转。排查时可以先临时停插件:
wp plugin deactivate --all
确认问题消失后,再一个个启用回来。
宝塔、1Panel、Nginx Proxy Manager 这类面板通常都有“强制 HTTPS”开关。
问题是,很多人同时在三个地方开:
- Cloudflare Always Use HTTPS
- 面板强制 HTTPS
- 应用插件强制 HTTPS
单独看每个设置都合理,叠在一起就可能循环。
我的建议是:只保留一层负责主跳转。
比较稳的做法:
- Cloudflare 用 Full/Full strict
- 源站 Nginx 负责 HTTP -> HTTPS
- 应用只认最终站点 URL,不再额外跳
如果你要让 Cloudflare 负责跳转,也可以,但源站就别再写一堆重复规则。关键不是哪一层跳,而是不要多层互相抢。
有些网站开过 HSTS,浏览器会强制把 HTTP 变成 HTTPS。
这时你在浏览器里怎么试都像是自动跳 HTTPS,但不一定是服务器现在还在跳。
排查时尽量用:
curl -IL http://example.com
而不是只靠浏览器。
也可以用无痕窗口、换浏览器,或者清理 HSTS 缓存。
Chrome 可以打开:
chrome://net-internals/#hsts
输入域名删除本地 HSTS 记录。
不过如果你站点已经对外启用了 HSTS,不要随便改线上策略。先用 curl 判断服务器真实响应。
遇到 HTTPS 跳转循环,我会按这个顺序来:
- 看跳转链:
curl -IL http://example.com
curl -IL https://example.com
- 如果用了 Cloudflare,先看 SSL/TLS 模式:
Flexible -> 高风险
Full / Full strict -> 更合理
- 绕过 CDN 测源站:
curl -I --resolve example.com:443:源站IP https://example.com
- 查 Nginx 跳转规则:
sudo nginx -T | grep -n "return 301\|rewrite"
- 查反代头:
sudo nginx -T | grep -n "X-Forwarded-Proto\|proxy_set_header"
- 查应用后台 URL,比如 WordPress:
wp option get home
wp option get siteurl
- 临时关掉重复跳转层,比如 SSL 插件、面板强制 HTTPS、Cloudflare Always Use HTTPS。
| 现象 | 优先怀疑 | 先看哪里 |
|---|---|---|
| Cloudflare 开 Flexible 后循环 | Cloudflare 到源站协议不一致 | SSL/TLS 模式 |
| HTTP 和 HTTPS 互跳 | Nginx return/rewrite 写反 | nginx -T |
| 反代应用一直跳 HTTPS | 后端不知道原始协议 | X-Forwarded-Proto |
| WordPress 后台进不去 | home/siteurl 不一致 | wp_options |
| 只有浏览器跳,curl 正常 | HSTS 或浏览器缓存 | 无痕、HSTS 缓存 |
| 开了多个强制 HTTPS | 多层重复跳转 | CDN、面板、应用插件 |
HTTPS 跳转循环最烦的地方是:每一层看起来都“配置得很安全”。
Cloudflare 想帮你跳 HTTPS,Nginx 想帮你跳 HTTPS,WordPress 插件也想帮你跳 HTTPS。结果不是更安全,而是互相打架。
排查时别一上来重签证书,也别反复 reload Nginx。先把跳转链打印出来,看清楚到底是哪一层把请求从哪里跳到哪里。
只要你记住一句话,基本就不会跑偏:
整条链路里,只让一层负责 HTTP 到 HTTPS 的主跳转,其他层负责识别真实协议,不要重复抢活。
