背景
前面一篇文章中,我已经先完成了将 en.shuijingwanwq.com 加入源站 Nginx 虚拟主机、申请并确认 SSL 证书等前置工作。
前置文章可以参考:
这一次的目标,是把英文站从原来的 Polylang 路径模式:
https://www.shuijingwanwq.com/en/
迁移为英文子域名:
https://en.shuijingwanwq.com/
中文主站仍然保持:
https://www.shuijingwanwq.com/
整体目标很简单:
中文内容:www.shuijingwanwq.com
英文内容:en.shuijingwanwq.com
WordPress 后台:仍然以 www.shuijingwanwq.com 为主
但实际操作下来,发现这里面有几个很容易被忽略的问题。
尤其是我的网站同时使用了:
Polylang
W3 Total Cache
Redis Object Cache
OneinStack Nginx
tms-extensions-polylang
所以最终排查重点并不只是 Polylang 本身,而是 Polylang 与缓存插件、对象缓存、扩展插件之间的兼容问题。
迁移前的站点结构
迁移前,英文站使用的是 Polylang 默认比较常见的目录模式:
https://www.shuijingwanwq.com/en/
这种方式的好处是配置简单,只有一个主域名,缓存规则也比较容易统一。
但缺点也比较明显:
英文站仍然依附在 www 主域名下面
中英文 URL 结构不够独立
后续如果想让英文站单独走 Cloudflare,会比较麻烦
我的中文主站当前主要走 EdgeOne,媒体资源已经拆分到了 media.shuijingwanwq.com。如果英文站继续挂在 /en/ 路径下,就无法把英文前台单独交给 Cloudflare 处理。
所以这一次我准备把英文站迁移到:
https://en.shuijingwanwq.com/
这样后续可以形成更清晰的结构:
www.shuijingwanwq.com 中文主站
en.shuijingwanwq.com 英文前台
media.shuijingwanwq.com 上传媒体资源

/en/ 路径模式】第一步:确认 Nginx 已经识别 en 子域名
在修改 Polylang 之前,我先确认源站 Nginx 虚拟主机已经包含 en.shuijingwanwq.com。
执行:
grep -R "server_name .*shuijingwanwq" /usr/local/nginx/conf/vhost /usr/local/nginx/conf/nginx.conf 2>/dev/null
确认当前 vhost 中已经包含:
server_name www.shuijingwanwq.com shuijingwanwq.com en.shuijingwanwq.com;
这一步很关键。
因为 Polylang 即使配置了英文子域名,如果 Nginx 没有把这个 Host 指向同一个 WordPress 站点,请求也无法正确进入 WordPress。

第二步:Polylang URL 模式选择
进入 WordPress 后台:
语言 → 设置 → URL 修改
一开始我的站点使用的是:
根据固定链接中的目录名称设置语言
也就是:
/en/
这次迁移子域名时,我一开始也纠结过是否应该选择:
根据固定链接中的子域名称设置语言
因为从字面看,en.shuijingwanwq.com 的确是一个子域名。
但实际测试后,我最终选择的是:
根据不同的域名设置语言
然后分别配置:
中文 zh:
https://www.shuijingwanwq.com
English en:
https://en.shuijingwanwq.com
虽然 en 是子域名,但从 Polylang 配置逻辑上看,这种方式更明确:
zh 绑定一个完整域名
en 绑定一个完整域名
后续 canonical、hreflang、home_url 的输出也更容易判断。

第三个问题:Polylang 保存时报 500
在保存 Polylang 设置时,后台提示 Polylang 无法访问一个检测 URL,大概形式是:
https://www.shuijingwanwq.com/?deactivate-polylang=1
我在服务器上直接访问这个 URL:
curl -sS -L -D - -o /dev/null --max-time 15 "https://www.shuijingwanwq.com/?deactivate-polylang=1" | sed -n '1,30p'
返回的是:
HTTP/2 500
于是继续查看 PHP 错误日志,发现问题来自 tms-extensions-polylang 插件:
PHP Fatal error: Uncaught Error: Call to undefined function pll_languages_list()
in /data/wwwroot/www.shuijingwanwq.com/wp-content/plugins/tms-extensions-polylang/includes/blocks/pll-menu-by-language/register.php
这个错误比较典型。
Polylang 保存 URL 设置时,会访问:
?deactivate-polylang=1
用于检测在 Polylang 临时停用情况下,站点 URL 是否仍然可访问。
但 tms-extensions-polylang 这个扩展插件在 Polylang 函数不可用时,仍然直接调用了:
pll_languages_list()
于是就触发了 Fatal Error。

修复 tms-extensions-polylang 的兼容问题
找到出错文件:
nl -ba /data/wwwroot/www.shuijingwanwq.com/wp-content/plugins/tms-extensions-polylang/includes/blocks/pll-menu-by-language/register.php | sed -n '25,50p'
原来的逻辑类似:
$tepll_menu_by_language_editor_languages = array();
$tepll_menu_by_language_polylang_slugs = pll_languages_list( array( 'fields' => 'slug' ) );
$tepll_menu_by_language_polylang_names = pll_languages_list( array( 'fields' => 'name' ) );
这里的问题是:它默认 pll_languages_list() 一定存在。
但在 ?deactivate-polylang=1 这个检测场景中,这个函数可能不存在。
所以先备份文件:
cp wp-content/plugins/tms-extensions-polylang/includes/blocks/pll-menu-by-language/register.php \
wp-content/plugins/tms-extensions-polylang/includes/blocks/pll-menu-by-language/register.php.bak.$(date +%Y%m%d%H%M%S)
然后把相关逻辑改成带 function_exists() 判断:
$tepll_menu_by_language_editor_languages = array();
$tepll_menu_by_language_polylang_slugs = array();
$tepll_menu_by_language_polylang_names = array();
if ( function_exists( 'pll_languages_list' ) ) :
$tepll_menu_by_language_polylang_slugs = pll_languages_list( array( 'fields' => 'slug' ) );
$tepll_menu_by_language_polylang_names = pll_languages_list( array( 'fields' => 'name' ) );
endif;
再检查 PHP 语法:
php -l wp-content/plugins/tms-extensions-polylang/includes/blocks/pll-menu-by-language/register.php
输出:
No syntax errors detected
这样 Polylang 保存 URL 设置时,就不会再因为扩展插件调用不存在的函数而 500。
这里需要注意:这是直接修改插件文件。后续如果插件升级,这个修改可能会被覆盖。比较稳妥的方式是记录下来,后续如果再次遇到同类问题,可以快速回滚或重新修复。
第四个问题:en 子域名被 301 回 www
Polylang 设置保存后,按理说访问:
https://en.shuijingwanwq.com/
应该输出英文首页。
但实际测试时发现,它仍然被 301 回:
https://www.shuijingwanwq.com/
响应头里能看到:
HTTP/2 301
location: https://www.shuijingwanwq.com/
x-redirect-by: WordPress
这个现象很容易误判为 Polylang 配置没有生效,或者 WordPress canonical redirect 在强制跳转。
但后面通过临时 mu-plugin 抓取 wp_redirect 调用栈,确认真正触发跳转的是 W3 Total Cache 的 Page Cache 模块。
调用栈中能看到:
W3TC\PgCache_Plugin->redirect_on_foreign_domain
也就是说,不是 Polylang 主动跳转,而是 W3TC Page Cache 把 en.shuijingwanwq.com 当成了 foreign domain。

W3TC 为什么会把 en 判断为 foreign domain
继续查看 W3 Total Cache 源码:
grep -RIn "function redirect_on_foreign_domain\|redirect_on_foreign_domain" /data/wwwroot/www.shuijingwanwq.com/wp-content/plugins/w3-total-cache | head -n 20
找到:
PgCache_Plugin.php
相关代码逻辑大致是:
$request_host = Util_Environment::host();
$home_url = get_home_url();
$parsed_url = @wp_parse_url( $home_url );
if ( isset( $parsed_url['host'] ) &&
strtolower( $parsed_url['host'] ) !== strtolower( $request_host ) ) {
...
}
这里的问题是:
get_home_url() 返回 www.shuijingwanwq.com
当前请求 Host 是 en.shuijingwanwq.com
于是 W3TC 判断:
当前请求不是 home_url 所在域名
然后触发:
redirect_on_foreign_domain
最终把英文子域名跳回了 www。
这在单域名 WordPress 站点里是合理的保护逻辑,但在 Polylang 多域名模式下,就会误伤合法的语言域名。
用 mu-plugin 修复 W3TC 与 Polylang 多域名兼容问题
我没有直接修改 W3 Total Cache 插件源码,因为插件升级很容易覆盖。
最终采用的是添加一个 mu-plugin,在 init 早期检查 Polylang 配置中的合法语言域名。如果当前 Host 属于 Polylang 的语言域名,就移除 W3TC 的 redirect_on_foreign_domain 动作。
新建文件:
/data/wwwroot/www.shuijingwanwq.com/wp-content/mu-plugins/w3tc-polylang-domain-fix.php
内容如下:
<?php
/*
Plugin Name: W3TC Polylang Domain Fix
Description: Prevents W3 Total Cache Page Cache from redirecting valid Polylang language domains back to the main home_url.
*/
add_action('init', function () {
$host = $_SERVER['HTTP_HOST'] ?? '';
if ($host === '') {
return;
}
$polylang = get_option('polylang');
$domains = $polylang['domains'] ?? array();
$allowed_hosts = array();
foreach ((array) $domains as $domain) {
if (!empty($domain)) {
$parsed = wp_parse_url($domain);
if (!empty($parsed['host'])) {
$allowed_hosts[] = strtolower($parsed['host']);
}
}
}
if (!in_array(strtolower($host), $allowed_hosts, true)) {
return;
}
global $wp_filter;
if (empty($wp_filter['init']) || empty($wp_filter['init']->callbacks)) {
return;
}
foreach ($wp_filter['init']->callbacks as $priority => $callbacks) {
foreach ($callbacks as $callback) {
$function = $callback['function'] ?? null;
if (
is_array($function)
&& isset($function[0], $function[1])
&& is_object($function[0])
&& $function[1] === 'redirect_on_foreign_domain'
&& get_class($function[0]) === 'W3TC\PgCache_Plugin'
) {
remove_action('init', $function, $priority);
}
}
}
}, 0);
检查语法:
php -l /data/wwwroot/www.shuijingwanwq.com/wp-content/mu-plugins/w3tc-polylang-domain-fix.php
输出:
No syntax errors detected
这个补丁的思路比较克制:
只处理 Polylang 已配置的合法语言域名
只移除 W3TC 的 foreign domain 跳转
不影响 W3TC Page Cache 其他功能
不影响非 Polylang 域名

第五个问题:Redis Object Cache 缓存了旧的 Polylang 配置
W3TC 的 301 问题解决后,en 不再跳回 www。
但访问英文子域名时,又出现了另一个问题:
<html lang="zh-CN">
canonical: https://www.shuijingwanwq.com/
这说明请求虽然进了 en.shuijingwanwq.com,但 WordPress 运行时仍然把它识别成中文主站。
进一步检查发现,数据库里的 Polylang 配置已经是新的:
force_lang = 3
domains[en] = https://en.shuijingwanwq.com
domains[zh] = https://www.shuijingwanwq.com
但通过 HTTP 请求读取到的运行时配置却仍然是旧的:
force_lang = 1
domains[en] =
domains[zh] = https://www.shuijingwanwq.com
pll_home_url_en = https://www.shuijingwanwq.com/en/
这说明对象缓存里还残留着旧的 Polylang option。
我的站点使用 Redis 作为 Object Cache,所以这里其实不是 Polylang 没保存成功,而是 Redis 中的旧缓存还在影响运行时。

清理 Redis 后恢复正常
因为这个 Redis 主要用于当前站点缓存,我最终直接清空 Redis:
redis-cli FLUSHALL
输出:
OK
清理后再次检查,运行时 Polylang 配置恢复正常:
force_lang = 3
domains[en] = https://en.shuijingwanwq.com
domains[zh] = https://www.shuijingwanwq.com
pll_home_url_en = https://en.shuijingwanwq.com/
pll_home_url_zh = https://www.shuijingwanwq.com/
这里要特别说明一下:
如果 Redis 是多个站点或多个业务共用的,不建议直接执行 FLUSHALL。这种命令会清空整个 Redis 实例。
我这里之所以这样处理,是因为当前 Redis 主要就是给这个 WordPress 站点做对象缓存,清空后影响可控。
重新启用 W3TC Object Cache 与 Page Cache
Redis 清理后,我重新启用了:
W3TC Object Cache
W3TC Page Cache
然后再次验证:
www 首页:zh-CN + www canonical
en 首页:en-US + en canonical
en 英文文章页:en-US + en canonical
这说明:
W3TC Object Cache 可以继续使用
W3TC Page Cache 也可以继续使用
前提是保留前面的 w3tc-polylang-domain-fix.php 兼容补丁
验证 en 首页与英文文章页
为了避免经过 CDN 或外部缓存造成误判,我在源站上使用 --resolve 直接请求本机 Nginx:
for u in \
"https://www.shuijingwanwq.com/" \
"https://en.shuijingwanwq.com/" \
"https://en.shuijingwanwq.com/2026/07/05/18871/"
do
echo "================ first request: $u ================"
curl -k -sS -D - -o /tmp/w3tc-page-test.html --max-time 15 \
--resolve www.shuijingwanwq.com:443:127.0.0.1 \
--resolve en.shuijingwanwq.com:443:127.0.0.1 \
"$u" | sed -n '1,18p'
grep -o '<html[^>]*>' /tmp/w3tc-page-test.html | head -n 1
grep -o '<link[^>]*rel=["'\''"]canonical["'\''"][^>]*>' /tmp/w3tc-page-test.html | head -n 1
echo
done
验证结果符合预期。
中文首页:
HTTP/2 200
<html lang="zh-CN">
<link rel="canonical" href="https://www.shuijingwanwq.com/" />
英文首页:
HTTP/2 200
<html lang="en-US">
<link rel="canonical" href="https://en.shuijingwanwq.com/" />
英文文章页:
HTTP/2 200
<html lang="en-US">
<link rel="canonical" href="https://en.shuijingwanwq.com/2026/07/05/18871/" />

验证 hreflang 输出
英文文章页还需要确认 hreflang 是否正常。
最终输出类似:
<link rel="alternate" href="https://www.shuijingwanwq.com/2026/07/05/18860/" hreflang="zh" />
<link rel="alternate" href="https://en.shuijingwanwq.com/2026/07/05/18871/" hreflang="en" />
这说明 Polylang 已经正确识别:
中文版本在 www
英文版本在 en
canonical 和 hreflang 都是搜索引擎比较关注的信号,所以这一步比单纯页面能打开更重要。
验证 W3TC 是否为 en 单独生成缓存文件
最后还需要确认 W3TC Page Cache 是否真的为 en.shuijingwanwq.com 单独生成缓存文件,而不是把缓存混到 www 目录下。
执行:
find wp-content/cache/page_enhanced/en.shuijingwanwq.com -maxdepth 8 -type f 2>/dev/null | head -n 50
能看到类似:
wp-content/cache/page_enhanced/en.shuijingwanwq.com/2026/07/05/18871/_index_slash_ssl.html
wp-content/cache/page_enhanced/en.shuijingwanwq.com/2026/07/05/18871/_index_slash_ssl.html_gzip
wp-content/cache/page_enhanced/en.shuijingwanwq.com/_index_slash_ssl.html
wp-content/cache/page_enhanced/en.shuijingwanwq.com/_index_slash_ssl.html_gzip
这说明:
en 首页有独立页面缓存
en 英文文章页有独立页面缓存
www 与 en 的缓存目录已经分开

最终状态
到这里,Polylang 英文站从 /en/ 迁移到 en 子域名的源站侧配置已经完成。
当前状态是:
Polylang URL 模式:
根据不同的域名设置语言
中文:
https://www.shuijingwanwq.com
英文:
https://en.shuijingwanwq.com
W3TC Page Cache:
启用
W3TC Object Cache:
启用
Redis:
已清理旧 Polylang option 缓存
W3TC 兼容补丁:
wp-content/mu-plugins/w3tc-polylang-domain-fix.php 保留
最终验证结果:
www 首页:
200
lang = zh-CN
canonical = https://www.shuijingwanwq.com/
en 首页:
200
lang = en-US
canonical = https://en.shuijingwanwq.com/
en 英文文章页:
200
lang = en-US
canonical = https://en.shuijingwanwq.com/2026/07/05/18871/
hreflang:
zh 指向 www
en 指向 en
W3TC 页面缓存:
www 与 en 分目录生成
这次排查的几个经验
这次迁移让我比较明显地感受到,Polylang 多域名模式并不只是后台改一个设置。
真正容易出问题的地方有几个。
第一,Polylang 扩展插件必须能兼容 ?deactivate-polylang=1 这种检测场景。否则 Polylang 保存配置时,主插件还没出问题,扩展插件先 Fatal 了。
第二,W3 Total Cache 的 Page Cache 在单域名站点中没有问题,但在 Polylang 多域名模式下,可能会把合法语言域名误判为 foreign domain,然后 301 回主域名。
第三,Redis Object Cache 可能缓存旧的 Polylang option。即使数据库已经保存了新配置,即使执行了清除所有缓存的操作,运行时仍然可能读到旧值,导致页面继续输出旧的 /en/ 路径或错误的 zh-CN。
第四,迁移完成后不能只看页面是否能打开,还要检查:
HTTP 状态码
html lang
canonical
hreflang
W3TC 缓存目录
是否仍然 301 回主域名
这些验证都通过之后,才算真正完成了源站侧迁移。
下一步,我会继续处理 en.shuijingwanwq.com 接入 Cloudflare、启用 HTML 缓存、验证登录用户绕过缓存、REST API 绕过缓存,以及将非 www 的后台入口统一收敛到 www 的问题。
需要长期技术维护或远程问题排查?
我是拥有 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

发表回复