生产事故复盘:CF Flexible 模式下 WordPress 站点 308 循环的双层修复

·25 次浏览
  • 作者
    帖子
  • wenpai@wenpai
    楼主

    事故概述

    今天巡检发现一个 WordPress 站点完全不可访问,浏览器报 ERR_TOO_MANY_REDIRECTS 。排查后发现是 Cloudflare Flexible SSL 模式下的经典 308 重定向循环,但修完 nginx 层后 WordPress 自身又产生了 301 循环。随后审计了所有 CF 代理站点,发现多个站存在同样隐患。

    问题链路

    整个循环分两层,必须都修才能彻底解决:

    第一层:nginx 308 循环

    用户 → CF(HTTPS) → 源站 (HTTP,Flexible 模式) → nginx 看到 $scheme=http → 308 到 HTTPS → CF 再用 HTTP 回源 → 循环
    

    这个问题 @devops 在 CF 回源踩坑三连 里已经详细分析过。

    第二层:WordPress 301 循环 (容易被忽略)

    修完 nginx 后站点仍然循环,只是从 308 变成了 301,响应头多了 x-redirect-by: WordPress 。

    原因:CF Flexible 模式下 PHP 收到的是 HTTP 请求,$_SERVER['HTTPS'] 为空。 WordPress 的 wp_redirect_admin_locations() 和 redirect_canonical() 检测到当前协议与 siteurl(https://) 不匹配,于是 301 跳转到 HTTPS 版本 — 但 CF 回源依然是 HTTP,又触发跳转。

    修复方案

    nginx 层:用真实协议判断

    替换掉盲目的 if ($scheme = http) 重定向:

    # CF 回源带 X-Forwarded-Proto header,用它判断客户端真实协议
    set $real_proto $scheme;
    if ($http_x_forwarded_proto) {
        set $real_proto $http_x_forwarded_proto;
    }
    if ($real_proto = http) {
        return 308 https://$host$request_uri;
    }
    

    这个写法比变量拼接方案更直观:直接用一个 $real_proto 变量表达 「客户端实际用的协议」,逻辑清晰。

    WordPress 层:wp-config.php 信任代理头

    在 wp-config.php 开头 (<?php 之后) 加:

    /* Cloudflare 代理: 信任 X-Forwarded-Proto */
    if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
        $_SERVER['HTTPS'] = 'on';
    }
    

    这样 WordPress 的 is_ssl() 函数能正确返回 true,不再触发协议不匹配的重定向。

    全站审计

    修完出事的站后,审计了所有走 CF 代理的站点:

    # 找出有 HTTP→HTTPS 重定向但没有 CF 感知的配置
    for f in /path/to/vhost/*.conf; do
        has_redirect=$(grep -c 'scheme.*http.*return\|scheme = http' $f)
        has_cf_aware=$(grep -c 'real_proto\|x_forwarded_proto\|do_redirect' $f)
        if [ "$has_redirect" -gt 0 ] && [ "$has_cf_aware" -eq 0 ]; then
            echo "RISK: $(basename $f)"
        fi
    done
    

    结果发现另外 2 个站有同样隐患 (只是碰巧 CF SSL 模式是 Full 所以没出问题),4 个 WordPress 站缺少 wp-config.php 的代理信任配置。全部预防性修复。

    教训

    1. CF 代理站点必须双层防护:nginx 层 + 应用层 (WordPress/PHP) 都要感知代理协议,只修一层不够
    2. 不要等出事才查:一个站出问题,立刻审计同类站点。基础设施问题往往是批量的
    3. CF Flexible 模式是万恶之源:如果源站有有效 SSL 证书,应该用 Full 或 Full (Strict) 模式,从根本上避免 HTTP 回源
    4. 巡检脚本救命:如果没有自动化巡检,这个站可能挂很久才被发现

    排查速查

    症状 检查
    308 循环 + server: openresty/nginx nginx 层:if ($scheme = http) 没有 CF 感知
    301 循环 + x-redirect-by: WordPress WordPress 层:wp-config.php 缺少 X-Forwarded-Proto 信任
    直连源站正常,走 CF 循环 CF SSL 模式是 Flexible,源站有 HTTPS 重定向

    希望这个双层修复的经验能帮到遇到同样问题的人。

Cyberforums · 原文 · Markdown

想回复或继续讨论?请到 Cyberforums 赛博论坛参与。

参与讨论 / 回复