没有不值得去解决的问题,也没有不值得去学习的技术!

WordPress 英文站从 /en/ 迁移到 en 子域名:兼容数千条 Nginx 旧规则并避免二次 301

【图1:浏览器开发者工具中,旧 /en/ 首页返回 301,随后成功加载 en 子域名英文首页】

WordPress CDN 与全球加速实战

图16:浏览器访问正常,网站全球访问成功

(1) 从阿里云 DNS 到 Cloudflare 全球加速的完整迁移实战记录

这一套规则遵循 Cloudflare + WordPress 标准 CDN 架构:

(2) Cloudflare SSL/TLS 与 Cache Rules 配置实战

图12:评论实时生效

(3) WordPress + Cloudflare 推荐配置与 Cache Rules 实战优化(SSL / API / 缓存闭环完整记录)

CDN上线后的boce数据

(4) CDN 上线前后性能对比实测:WordPress 缓存误判修正与真实加速效果验证

【图9:boce 测试 cn-test,灰云直连阿里云 ECS,平均响应 0.478s】

(5) Cloudflare 免费版适合中国大陆 WordPress 主站吗?从缓存 HIT 到灰云直连阿里云 ECS 的实测复盘

【图1:腾讯云 EdgeOne 产品页,选择“立即使用”】

(6) 从 Cloudflare Free 迁移到腾讯云 EdgeOne:一次 WordPress 主站国内外加速实战

【图2:EdgeOne 上线后 boce 第二次测试,平均响应 0.354s,不可访问 2 个】

(7) Cloudflare Free 迁移到 EdgeOne 后到底快了多少?WordPress CDN 三阶段 boce 与 WebPageTest 实测对比

[图2,EdgeOne 昨日 L7 访问流量,显示 6.56 GB]

(8) EdgeOne 流量成本排查:将 WordPress uploads 静态资源迁移到 Cloudflare media 子域名

【图3,EdgeOne 301 请求的 URL Path 排行,集中在 /wp-content/uploads/ 图片路径】

(9) WordPress 图片迁移到 media 子域名后,EdgeOne 仍然出现大量 301 的排查与处理记录

图4:Cloudflare Edge TTL 配置、Cloudflare Browser TTL 配置

(10) WordPress Media CDN 迁移后的 Cloudflare Cache Rules 调整记录

[图4:EdgeOne 按 URL path 开始于 /en/ 筛选后的流量截图]

(11) EdgeOne 流量成本控制决策:是否将英文站从 /en/ 迁移到 en.shuijingwanwq.com

[图3:执行核心升级时返回 524 No Reason Phrase]

(12) WordPress 核心升级反复 524 与“另一更新正在进行”的排查记录

【图 10:新证书 SAN 信息截图,显示已经包含 en.shuijingwanwq.com】

(13) OneinStack 现有 Nginx 虚拟主机追加域名并重签 SSL 证书的一次实操记录

【图 8,curl 验证 www 与 en 的 html lang、canonical 均正确】

(14) WordPress Polylang 英文站从 /en/ 迁移到 en 子域名:W3TC 与 Redis 缓存问题排查

【图 14,最终回归验证:en 首页和文章页 Cloudflare HIT,lang 与 canonical 正确】

(15) 将 Polylang 英文子域名接入 Cloudflare:HTML 缓存、登录绕过与后台入口收敛

【图1:浏览器开发者工具中,旧 /en/ 首页返回 301,随后成功加载 en 子域名英文首页】

(16) WordPress 英文站从 /en/ 迁移到 en 子域名:兼容数千条 Nginx 旧规则并避免二次 301

此前,我的 WordPress 中英文内容一直通过路径区分:

Plaintext
中文站:
https://www.shuijingwanwq.com/

英文站:
https://www.shuijingwanwq.com/en/

随着英文站逐渐独立,我将英文内容迁移到了单独的英文子域名:

Plaintext
https://en.shuijingwanwq.com/

英文子域名上线后,原来位于 /en/ 路径下的首页、文章、分类、标签和专题地址,都需要通过 301 永久重定向迁移到新域名。

如果这是一个没有历史规则的新站,可能只需要一条简单的 Nginx rewrite

但我的站点中已经积累了数千条标签 slug 转换、旧标签合并和专题路径调整规则。直接增加一条通用的 /en/* 跳转,不仅可能绕过原来的 slug 映射,还可能产生两次 301。

本文记录这次迁移的完整思路、最终 Nginx 结构、测试过程,以及今后应该如何分别维护 www 主域名和 en 英文子域名的 301 规则。

相关背景可以参考此前整理的文章:


一、迁移前后的 URL 结构

迁移前,英文首页和普通文章地址分别是:

Plaintext
https://www.shuijingwanwq.com/en/

https://www.shuijingwanwq.com/en/2026/07/05/18871/

迁移后,对应地址变为:

Plaintext
https://en.shuijingwanwq.com/

https://en.shuijingwanwq.com/2026/07/05/18871/

因此,最基础的迁移目标是:

Plaintext
https://www.shuijingwanwq.com/en/

https://en.shuijingwanwq.com/

以及:

Plaintext
https://www.shuijingwanwq.com/en/2026/07/05/18871/

https://en.shuijingwanwq.com/2026/07/05/18871/

迁移时需要移除路径开头的 /en/,同时完整保留后面的文章路径和查询参数。

【图1:浏览器开发者工具中,旧 /en/ 首页返回 301,随后成功加载 en 子域名英文首页】
【图1:浏览器开发者工具中,旧 /en/ 首页返回 301,随后成功加载 en 子域名英文首页】

二、为什么不能只添加一条通用 rewrite

如果站点没有其他历史跳转规则,可以考虑类似下面的写法:

Nginx
rewrite ^/en(?:/(.*))?$ https://en.shuijingwanwq.com/$1 permanent;

但我的站点中已经存在大量旧 URL 映射,主要用于:

  1. 中文标签 slug 合并;
  2. /en/ 路径下的中文标签 slug 转换为英文 slug;
  3. 旧专题路径合并到新专题路径;
  4. 部分重复标签和历史路径统一。

这些规则原本使用 Nginx map 维护,结构类似:

Nginx
map $uri $new_tag_uri {
    default "";

    ~^/tag/权限/?$ "/tag/permission/";
    ~^/en/tag/权限/?$ "/en/tag/permission/";

    ~^/tag/30-天试用/?$ "/tag/30-day-trial/";
    ~^/en/tag/30-天试用/?$ "/en/tag/30-day-trial-en/";

    ~^/series/15-year-backend-dev-survival-notes-in-ai-era/?$
        "/series/ai-era-developer-monetization-system/";

    ~^/en/series/15-year-backend-dev-survival-notes-in-ai-era-en/?$
        "/en/series/ai-era-developer-monetization-system-en/";
}

在英文站迁移以前,server 中可以直接使用映射结果:

Nginx
if ($new_tag_uri != "") {
    return 301 $new_tag_uri;
}

问题在于,如果把通用 /en/ 跳转放在历史映射之前,那么下面这个地址:

Plaintext
https://www.shuijingwanwq.com/en/tag/30-天试用/

可能会直接变成:

Plaintext
https://en.shuijingwanwq.com/tag/30-天试用/

原来负责将中文标签 slug 转换为英文 slug 的规则就被绕过了。

如果先执行旧映射,再单独执行域名迁移,则可能形成:

Plaintext
https://www.shuijingwanwq.com/en/tag/30-天试用/

https://www.shuijingwanwq.com/en/tag/30-day-trial-en/

https://en.shuijingwanwq.com/tag/30-day-trial-en/

最终地址虽然正确,却多经过了一次 301。

更理想的结果应该是:

Plaintext
https://www.shuijingwanwq.com/en/tag/30-天试用/

https://en.shuijingwanwq.com/tag/30-day-trial-en/

也就是在一次 301 中,同时完成:

Plaintext
旧标签 slug 转换
+
英文域名迁移

由于最终配置上线后,这个二次 301 场景已经无法在线复现,因此本文不再安排对应截图,只保留路径流程说明。


三、最终采用的分层处理逻辑

最终方案没有逐条修改现有的数千条映射,而是在原有映射结果之后增加了一层“最终域名转换”。

整体逻辑如下:

Plaintext
请求进入 Nginx

检查是否命中旧标签或旧专题规则

命中后生成标准目标路径

判断标准路径是否以 /en/ 开头

以 /en/ 开头:
移除 /en/,跳转到 en 英文子域名

不以 /en/ 开头:
继续跳转到 www 中文主域名

如果没有命中旧路径映射

检查原始请求是否为普通 /en/ 页面

统一迁移到 en 英文子域名

这样可以同时满足:

  • 原有标签和专题映射继续生效;
  • 旧英文标签一次跳到最终英文地址;
  • 中文旧标签继续保留在 www 主域名;
  • 普通英文文章、页面和分类统一迁移;
  • 不需要逐条改写已有规则。

四、第一层:继续保留原有旧路径映射

原来的 $new_tag_uri 继续负责把历史 URL 转换为标准路径。

例如:

Nginx
~^/tag/30-天试用/?$ "/tag/30-day-trial/";

表示:

Plaintext
/tag/30-天试用/

/tag/30-day-trial/

英文旧路径则继续保留 /en/ 标记:

Nginx
~^/en/tag/30-天试用/?$ "/en/tag/30-day-trial-en/";

表示:

Plaintext
/en/tag/30-天试用/

/en/tag/30-day-trial-en/

这里生成的只是一个标准目标路径,还没有直接返回给浏览器。

继续保留 /en/ 前缀的意义在于:下一层可以根据这个前缀判断,最终地址应该属于英文子域名。


五、第二层:将标准路径转换为最终域名

在原有映射文件中增加第二个 map

Nginx
# 仅处理 www/裸域旧规则的映射结果
# en.shuijingwanwq.com 后续使用独立的英文标签跳转规则
map "$host:$new_tag_uri" $www_final_redirect_uri {
    default "";

    # www 旧 /en/ 路径:去掉 /en/,跳转到英文子域名
    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/en/(.*)$
        "https://en.shuijingwanwq.com/$1";

    # www 中文旧路径:继续留在 www 主域名
    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/(.*)$
        "https://www.shuijingwanwq.com/$1";
}

这个 map 同时使用:

Plaintext
$host
$new_tag_uri

因此可以区分:

Plaintext
www.shuijingwanwq.com:/en/tag/...

和:

Plaintext
www.shuijingwanwq.com:/tag/...

当第一层生成:

Plaintext
/en/tag/30-day-trial-en/

第二层会将其转换成:

Plaintext
https://en.shuijingwanwq.com/tag/30-day-trial-en/

当第一层生成:

Plaintext
/tag/30-day-trial/

则会转换成:

Plaintext
https://www.shuijingwanwq.com/tag/30-day-trial/

这样,旧英文标签和旧中文标签就可以在同一套历史映射中完成正确分流。


六、普通 /en/ 页面使用兜底规则

旧标签和旧专题映射只能覆盖已经登记过的历史路径。

普通英文文章、页面和分类通常不会命中 $new_tag_uri,因此还需要一个普通 /en/ 路径的兜底映射:

Nginx
# www/裸域普通 /en/ 路径迁移到英文子域名
# 仅作为旧标签、旧专题映射之后的兜底规则
map "$host:$uri" $www_en_migration_uri {
    default "";

    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/en/?$
        "https://en.shuijingwanwq.com/";

    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/en/(.*)$
        "https://en.shuijingwanwq.com/$1";
}

它负责处理:

Plaintext
https://www.shuijingwanwq.com/en/
Plaintext
https://www.shuijingwanwq.com/en/2026/07/05/18871/
Plaintext
https://www.shuijingwanwq.com/en/category/wordpress/

以及其他没有命中历史映射的 /en/ 地址。


七、server 中的执行顺序非常重要

在 Nginx server 中,必须先使用旧路径的精确映射结果,再执行普通 /en/ 兜底迁移:

Nginx
# 旧标签、旧专题先映射,再一次跳转到最终中文或英文域名
if ($www_final_redirect_uri != "") {
    return 301 $www_final_redirect_uri$is_args$args;
}

# 未命中旧规则的普通 /en/ 页面迁移到英文子域名
if ($www_en_migration_uri != "") {
    return 301 $www_en_migration_uri$is_args$args;
}

顺序不能反过来。

如果普通 /en/ 兜底规则放在前面,旧英文标签会提前被迁移到英文子域名,原来的标签 slug 转换规则就没有机会执行。

当前顺序可以概括为:

Plaintext
先处理精确的旧标签和旧专题映射
再处理通用的 /en/ 域名迁移

这是整套方案中最关键的一点。最终虚拟主机中也确实按照这个顺序执行,随后才检查英文子域名自身的独立规则。


八、保留原请求的查询参数

测试过程中,我发现路径可以正确跳到最终英文地址,但最初的写法没有保留测试查询参数。

例如:

Plaintext
?utm_source=redirect-test&utm_medium=nginx

为了保留 UTM、广告归因参数和其他查询字符串,最终在 return 中增加:

Nginx
$is_args$args

完整写法为:

Nginx
return 301 $www_final_redirect_uri$is_args$args;

以及:

Nginx
return 301 $www_en_migration_uri$is_args$args;

其中:

  • $args 表示原请求的查询参数;
  • $is_args 在存在参数时输出 ?
  • 不存在查询参数时,$is_args 为空。

测试命令:

Bash
curl -4 -sS -I --max-time 20 "https://www.shuijingwanwq.com/en/tag/30-%E5%A4%A9%E8%AF%95%E7%94%A8/?utm_source=redirect-test&utm_medium=nginx" | grep -Ei "HTTP/|location:|server:|eo-cache-status"

实际返回:

Plaintext
HTTP/2 301
server: nginx
location: https://en.shuijingwanwq.com/tag/30-day-trial-en/?utm_source=redirect-test&utm_medium=nginx
eo-cache-status: MISS

可以看到,旧标签完成 slug 转换和域名迁移后,查询参数仍然完整保留。

【图2:旧英文标签完成 301 后,utm_source 和 utm_medium 查询参数仍然完整保留】
【图2:旧英文标签完成 301 后,utm_sourceutm_medium 查询参数仍然完整保留】

九、英文子域名使用独立规则文件

迁移完成后,我单独创建了:

Plaintext
/usr/local/nginx/conf/include/shuijingwan_en_redirect.conf

这个文件不用于处理历史上的 /en/ 地址。

例如:

Plaintext
https://www.shuijingwanwq.com/en/tag/旧地址/

来源属于旧 www 站点,仍然应该由:

Plaintext
shuijingwan_nginx_redirect.conf

处理。

而下面这种地址:

Plaintext
https://en.shuijingwanwq.com/tag/旧地址/

来源本身已经属于新的英文子域名,才应该由:

Plaintext
shuijingwan_en_redirect.conf

处理。

en.shuijingwanwq.com 是这次迁移时才正式上线的新域名,目前并不存在数千条英文子域名历史 URL。

因此,没有必要把原来的数千条 /en/tag/... 映射复制一遍,再删除其中的 /en/

英文规则文件目前只保留空框架:

Nginx
# en.shuijingwanwq.com 独立旧路径跳转规则
# 以后英文域名自身出现需要合并或变更的网址时,在这里添加
map $uri $en_new_uri {
    default "";
}

后续如果英文子域名上的某个标签需要合并,可以增加:

Nginx
~^/tag/old-slug/?$ "/tag/new-slug/";

然后通过下面的 map 生成完整目标地址:

Nginx
# 仅在 en.shuijingwanwq.com 上执行英文旧路径跳转
map "$host:$en_new_uri" $en_final_redirect_uri {
    default "";

    ~*^en\.shuijingwanwq\.com:(/.*)$
        "https://en.shuijingwanwq.com$1";
}

server 中对应执行:

Nginx
if ($en_final_redirect_uri != "") {
    return 301 $en_final_redirect_uri$is_args$args;
}

最终英文规则文件确实只保留了空映射框架和域名限制,不再复制原 /en/ 下的数千条历史规则。


十、两份规则文件的职责划分

迁移完成后,两份配置文件的职责已经明确区分。

shuijingwan_nginx_redirect.conf

负责处理来源属于:

Plaintext
www.shuijingwanwq.com

或者:

Plaintext
shuijingwanwq.com

的历史地址。

包括:

Nginx
# 中文主站旧地址
~^/tag/旧中文标签/?$ "/tag/新中文标签/";

# 历史上位于 www /en/ 下的英文旧地址
~^/en/tag/旧英文标签/?$ "/en/tag/新英文标签/";

第二种规则虽然仍然写在 www 的历史规则文件中,最终会由第二层 map 自动转换到:

Plaintext
en.shuijingwanwq.com

shuijingwan_en_redirect.conf

只处理来源已经属于:

Plaintext
en.shuijingwanwq.com

的新历史地址。

目前其中没有实际路径映射。等英文子域名上线后真正发生标签合并、专题改名或路径调整时,再向其中添加。

可以简单记为:

Plaintext
www 或旧 www/en/ 来源
→ shuijingwan_nginx_redirect.conf

en 子域名来源
→ shuijingwan_en_redirect.conf
【图3:Nginx include 目录中的两份正式规则文件,以及迁移完成后的备份文件】
【图3:Nginx include 目录中的两份正式规则文件,以及迁移完成后的备份文件】

十一、逐项验证跳转结果

配置修改完成后,我分别测试了旧英文首页、旧英文标签、普通英文文章、中文旧标签和旧英文专题。

为了避免复制多行 Bash 命令时,反斜杠后意外带入空格,下面全部使用单行命令。

1. 验证旧英文首页

Bash
curl -4 -sS -I -L --max-time 30 "https://www.shuijingwanwq.com/en/" | grep -Ei "HTTP/|location:|server:|eo-cache-status|cf-cache-status"

预期跳转链:

Plaintext
HTTP/2 301
location: https://en.shuijingwanwq.com/

HTTP/2 200

浏览器开发者工具中也可以看到,旧 /en/ 请求先返回 301,随后成功加载英文子域名首页。


2. 验证旧英文标签

包含中文字符的路径,建议先使用 URL 百分号编码:

Bash
curl -4 -sS -I -L --max-time 30 "https://www.shuijingwanwq.com/en/tag/30-%E5%A4%A9%E8%AF%95%E7%94%A8/" | grep -Ei "HTTP/|location:|server:|eo-cache-status|cf-cache-status"

实际结果:

Plaintext
HTTP/2 301
server: nginx
location: https://en.shuijingwanwq.com/tag/30-day-trial-en/
eo-cache-status: MISS

HTTP/2 200
server: cloudflare
cf-cache-status: MISS

整个过程只有一次 301。

【图4:旧英文标签一次 301 后,直接到达英文子域名下的最终英文标签页面】
【图4:旧英文标签一次 301 后,直接到达英文子域名下的最终英文标签页面】

3. 验证普通英文文章

Bash
curl -4 -sS -I -L --max-time 30 "https://www.shuijingwanwq.com/en/2026/07/05/18871/" | grep -Ei "HTTP/|location:|server:|eo-cache-status|cf-cache-status"

实际结果:

Plaintext
HTTP/2 301
server: nginx
location: https://en.shuijingwanwq.com/2026/07/05/18871/
eo-cache-status: MISS

HTTP/2 200
server: nginx

说明普通英文文章正确命中了 /en/ 兜底迁移规则。

【图5:普通 /en/ 英文文章的完整跳转链为 301 → 200】
【图5:普通 /en/ 英文文章的完整跳转链为 301 → 200

4. 验证中文旧标签

Bash
curl -4 -sS -I -L --max-time 30 "https://www.shuijingwanwq.com/tag/30-%E5%A4%A9%E8%AF%95%E7%94%A8/" | grep -Ei "HTTP/|location:|server:|eo-cache-status|cf-cache-status"

实际结果:

Plaintext
HTTP/2 301
server: nginx
location: https://www.shuijingwanwq.com/tag/30-day-trial/
eo-cache-status: MISS

HTTP/2 200
server: nginx
eo-cache-status: MISS

中文旧标签仍然保留在 www 中文主域名,没有被错误迁移到英文子域名。

【图6:中文旧标签仍然跳转到 www 主域名下的最终中文标签地址】
【图6:中文旧标签仍然跳转到 www 主域名下的最终中文标签地址】

5. 验证旧英文专题

Bash
curl -4 -sS -I -L --max-time 30 "https://www.shuijingwanwq.com/en/series/15-year-backend-dev-survival-notes-in-ai-era-en/" | grep -Ei "HTTP/|location:|server:|eo-cache-status|cf-cache-status"

实际结果:

Plaintext
HTTP/2 301
server: nginx
location: https://en.shuijingwanwq.com/series/ai-era-developer-monetization-system-en/
eo-cache-status: MISS

HTTP/2 200
server: cloudflare
cf-cache-status: MISS

说明旧专题规则也能先完成 slug 合并,再一次跳转到英文子域名的最终地址。

【图7:旧英文专题一次 301 迁移到英文子域名的最终专题地址】
【图7:旧英文专题一次 301 迁移到英文子域名的最终专题地址】

十二、为什么复制多行 curl 命令后会报错

测试过程中,我曾复制类似下面的命令:

Bash
curl -4 -sS -I -L \ "https://www.shuijingwanwq.com/en/tag/..."

随后 curl 报错:

Plaintext
curl: (1) Protocol " https" not supported or disabled in libcurl

问题并不是服务器不支持 HTTPS,而是反斜杠后面带了一个空格:

Plaintext
\ 

在 Shell 中,反斜杠会转义紧随其后的字符。

当它转义的是空格时,curl 最终收到的 URL 实际以空格开头:

 https://www.shuijingwanwq.com/...

curl 因此把协议识别成:

 https

而不是正常的:

Plaintext
https

为避免 WordPress、聊天窗口或代码块复制时引入隐藏空格,本文中的测试命令全部改成了单行形式。

如果必须使用多行 Bash 命令,反斜杠必须是该行最后一个字符,后面不能再出现空格或其他字符。


十三、curl 直接请求未编码中文 URL 可能返回 400

测试包含中文标签的地址时,我曾直接执行:

Bash
curl -I "https://en.shuijingwanwq.com/tag/30-天试用/"

在这次测试环境中,结果返回:

Plaintext
HTTP/2 400

这时不能直接判断 Nginx 映射失效,因为命令中的中文路径还没有经过 URL 百分号编码。

将中文路径编码后:

Bash
curl -4 -sS -I --max-time 20 "https://en.shuijingwanwq.com/tag/30-%E5%A4%A9%E8%AF%95%E7%94%A8/"

请求便可以正常发出。

浏览器通常会自动对中文路径进行编码,因此通过浏览器访问时,不容易遇到相同的问题。

在命令行中测试包含中文字符的 URL 时,最好始终使用百分号编码后的地址。


十四、为什么把 301 放在 Nginx,而不是全部放到 CDN

目前中文主域名和英文子域名使用的 CDN 接入方式并不完全相同:

Plaintext
www.shuijingwanwq.com
→ EdgeOne

en.shuijingwanwq.com
→ Cloudflare

EdgeOne 和 Cloudflare 都可以配置重定向,但这次迁移仍然选择把 Nginx 作为 301 的最终规则来源,主要有以下原因:

  1. 旧标签和旧专题规则原本就在 Nginx;
  2. 路径标准化与域名迁移需要组合判断;
  3. 把规则拆散到两个 CDN 后,排查优先级会更加困难;
  4. 将来更换 CDN 时,源站规则仍然可以继续工作;
  5. 数千条历史映射不适合在多个控制台中重复维护。

CDN 可以缓存部分 301 响应,但规则本身仍然由源站统一管理。

这样更容易长期维护,也更容易在一套配置中看清完整的跳转逻辑。


十五、最终实现效果

本次迁移完成后,以下场景均已验证。

普通英文页面

Plaintext
www.shuijingwanwq.com/en/...

en.shuijingwanwq.com/...

带旧 slug 的英文标签

Plaintext
www.shuijingwanwq.com/en/tag/旧-slug/

en.shuijingwanwq.com/tag/最终-slug/

只经过一次 301。

中文旧标签

Plaintext
www.shuijingwanwq.com/tag/旧-slug/

www.shuijingwanwq.com/tag/最终-slug/

继续保留在中文主域名。

英文子域名以后产生的新规则

Plaintext
en.shuijingwanwq.com/旧路径/

en.shuijingwanwq.com/新路径/

单独写入英文规则文件,不与历史 /en/ 迁移规则混用。


十六、总结

将 WordPress 英文站从 /en/ 迁移到独立子域名,看起来只需要删除一个路径前缀。

但当站点已经积累了大量标签合并、slug 翻译和专题重定向规则后,一条简单的通用 rewrite 很可能带来:

  • 旧规则被绕过;
  • slug 没有完成转换;
  • 两次 301;
  • 查询参数丢失;
  • 中文和英文规则互相干扰;
  • 后续维护时难以判断应该修改哪个文件。

这次最终采用的是分层映射方式:

Plaintext
历史路径映射

最终域名转换

普通 /en/ 兜底迁移

同时将:

Plaintext
旧 www 和旧 www/en/ 规则

与:

Plaintext
英文子域名上线后产生的新规则

分开维护。

这样既保留了现有数千条规则,也让旧英文地址能够一次跳转到最终英文子域名,为后续继续调整标签和专题路径留下了清晰的扩展空间。


附录一:去敏后的 Nginx 虚拟主机配置

下面保留与本文相关的完整虚拟主机结构。

证书路径、日志路径、WordPress 物理目录和 PHP-FPM Socket 已替换为通用占位值,不能不加修改地直接用于生产环境。

Nginx
# map 必须位于 http 上下文。
# 当前环境中的 vhost 文件由 nginx.conf 在 http 块内引入。

# 引入 www 主域名及历史 /en/ 路径规则
include /usr/local/nginx/conf/include/shuijingwan_nginx_redirect.conf;

# 引入 en.shuijingwanwq.com 独立旧路径规则
include /usr/local/nginx/conf/include/shuijingwan_en_redirect.conf;

server {
    limit_req zone=site_limit burst=50 nodelay;
    limit_conn conn_limit 50;
    limit_req_status 429;

    listen 80;
    listen [::]:80;
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    ssl_certificate /path/to/ssl/example.com.crt;
    ssl_certificate_key /path/to/ssl/example.com.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;

    ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256;

    ssl_conf_command Ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
    ssl_conf_command Options PrioritizeChaCha;

    ssl_prefer_server_ciphers on;
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;
    ssl_buffer_size 2k;

    add_header Strict-Transport-Security max-age=15768000;

    ssl_stapling on;
    ssl_stapling_verify on;

    server_name
        www.shuijingwanwq.com
        shuijingwanwq.com
        en.shuijingwanwq.com;

    access_log /path/to/logs/example.com.access.log combined;

    index index.html index.htm index.php;
    root /path/to/wordpress;

    if ($ssl_protocol = "") {
        return 301 https://$host$request_uri;
    }

    if ($host = shuijingwanwq.com) {
        return 301 $scheme://www.shuijingwanwq.com$request_uri;
    }

    # 非 www 域名访问 WordPress 后台时,统一跳回 www 主域名。
    set $redirect_non_www_admin "";

    if ($host != www.shuijingwanwq.com) {
        set $redirect_non_www_admin "${redirect_non_www_admin}H";
    }

    if ($uri ~* "^/(wp-admin(?:/|$)|wp-login\.php$)") {
        set $redirect_non_www_admin "${redirect_non_www_admin}U";
    }

    if ($redirect_non_www_admin = "HU") {
        return 301 https://www.shuijingwanwq.com$request_uri;
    }

    include /usr/local/nginx/conf/rewrite/wordpress.conf;

    location ~ .*\.(wma|wmv|asf|mp3|mmf|zip|rar|jpg|gif|png|swf|flv|mp4)$ {
        valid_referers
            none
            blocked
            *.shuijingwanwq.com
            www.shuijingwanwq.com
            shuijingwanwq.com;

        if ($invalid_referer) {
            return 403;
        }
    }

    # 所有 /amp 后缀链接跳转到对应规范页面。
    location ~* ^(.+)/amp/?$ {
        return 301 $1/;
    }

    # 旧标签、旧专题先完成映射,
    # 再一次跳转到最终中文或英文域名。
    if ($www_final_redirect_uri != "") {
        return 301 $www_final_redirect_uri$is_args$args;
    }

    # 未命中旧规则的普通 /en/ 页面,
    # 统一迁移到英文子域名。
    if ($www_en_migration_uri != "") {
        return 301 $www_en_migration_uri$is_args$args;
    }

    # en 子域名以后产生的独立旧路径规则。
    if ($en_final_redirect_uri != "") {
        return 301 $en_final_redirect_uri$is_args$args;
    }

    location ~ [^/]\.php(/|$) {
        fastcgi_pass unix:/path/to/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_read_timeout 150s;
        include fastcgi.conf;
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|mp4|ico)$ {
        expires 30d;
        access_log off;
    }

    location ~ .*\.(js|css)?$ {
        expires 7d;
        access_log off;
    }

    location ~ /(\.user\.ini|\.ht|\.git|\.svn|\.project|LICENSE|README\.md) {
        deny all;
    }

    location /.well-known {
        allow all;
    }
}

附录二:www 与旧 /en/ 历史跳转规则

生产环境中的 $new_tag_uri 包含数千条标签和专题映射。

如果在正文中逐条展开,不仅会让文章体积过大,也会掩盖真正需要理解的两层转换逻辑。

因此,下面保留实际文件结构和具有代表性的规则;中间大量相同形式的映射使用注释省略。

Nginx
map $uri $new_tag_uri {
    default "";

    # 中文标签合并
    ~^/tag/权限/?$ "/tag/permission/";
    ~^/tag/菜单/?$ "/tag/menu/";
    ~^/tag/安装/?$ "/tag/installation/";

    # 原 /en/ 路径下的英文标签标准化
    ~^/en/tag/权限/?$ "/en/tag/permission/";
    ~^/en/tag/菜单/?$ "/en/tag/menu/";
    ~^/en/tag/安装/?$ "/en/tag/installation/";

    # 同一个中文标签在中英文站使用不同的最终 slug
    ~^/tag/30-天试用/?$ "/tag/30-day-trial/";
    ~^/en/tag/30-天试用/?$ "/en/tag/30-day-trial-en/";

    ~^/tag/在线商店/?$ "/tag/online-store/";
    ~^/en/tag/在线商店/?$ "/en/tag/online-store-en/";

    ~^/tag/多个-php-版本/?$ "/tag/multiple-php-versions/";
    ~^/en/tag/多个-php-版本/?$ "/en/tag/multiple-php-versions-en/";

    ~^/tag/chrome扩展/?$ "/tag/chrome-extension/";
    ~^/en/tag/chrome扩展/?$ "/en/tag/chrome-extension-en/";

    # ……中间省略数千条同类标签映射……

    # 中文专题合并
    ~^/series/15-year-backend-dev-survival-notes-in-ai-era/?$
        "/series/ai-era-developer-monetization-system/";

    # 原 /en/ 下的英文专题合并
    ~^/en/series/15-year-backend-dev-survival-notes-in-ai-era-en/?$
        "/en/series/ai-era-developer-monetization-system-en/";
}


# 仅处理 www/裸域旧规则的映射结果
# en.shuijingwanwq.com 后续使用独立的英文标签跳转规则
map "$host:$new_tag_uri" $www_final_redirect_uri {
    default "";

    # www 旧 /en/ 路径:
    # 去掉 /en/,一次跳转到英文子域名
    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/en/(.*)$
        "https://en.shuijingwanwq.com/$1";

    # www 中文旧路径:
    # 继续留在中文主域名
    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/(.*)$
        "https://www.shuijingwanwq.com/$1";
}


# www/裸域普通 /en/ 路径迁移到英文子域名
# 仅作为旧标签、旧专题映射之后的兜底规则
map "$host:$uri" $www_en_migration_uri {
    default "";

    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/en/?$
        "https://en.shuijingwanwq.com/";

    ~*^(?:www\.shuijingwanwq\.com|shuijingwanwq\.com):/en/(.*)$
        "https://en.shuijingwanwq.com/$1";
}

以后如果来源地址属于:

Plaintext
www.shuijingwanwq.com

或者旧的:

Plaintext
www.shuijingwanwq.com/en/

仍然在这份文件中增加映射。


附录三:英文子域名独立跳转规则

en.shuijingwanwq.com 是本次迁移时才正式上线的新域名,因此当前不存在需要批量迁移的英文子域名历史 URL。

最终文件只保留规则框架:

Nginx
# en.shuijingwanwq.com 独立旧路径跳转规则
# 以后英文域名自身出现需要合并或变更的网址时,在这里添加
map $uri $en_new_uri {
    default "";

    # 示例:
    # ~^/tag/old-slug/?$ "/tag/new-slug/";
}


# 仅在 en.shuijingwanwq.com 上执行英文旧路径跳转
map "$host:$en_new_uri" $en_final_redirect_uri {
    default "";

    ~*^en\.shuijingwanwq\.com:(/.*)$
        "https://en.shuijingwanwq.com$1";
}

以后只有来源地址本身已经属于:

Plaintext
https://en.shuijingwanwq.com/

时,才在这份文件中增加映射。

将 Polylang 英文子域名接入 Cloudflare:HTML 缓存、登录绕过与后台入口收敛

需要长期技术维护或远程问题排查?

我是拥有 15+ 年经验的 PHP / Go 后端工程师,长期关注已有系统维护、Bug 修复、性能优化、服务器排查、WordPress 网站维护和小功能迭代。

如果你的项目遇到以下情况,可以先从一次小问题排查开始合作:

  • ✅ PHP / Laravel / Yii2 老项目无人维护
  • ✅ Go / Gin 后端接口需要排查或优化
  • ✅ WordPress 网站访问慢、报错或插件冲突
  • ✅ Nginx / MySQL / Redis / Linux 服务器异常
  • ✅ CDN / Cloudflare / DNS / HTTPS 配置问题
  • ✅ 需要长期远程技术支持或兼职维护

更多介绍请查看:关于我 & 合作

微信:13980074657
邮箱:shuijingwanwq@gmail.com
Telegram:@shuijingwan
GitHub:https://github.com/shuijingwan

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理