一些 WordPress 网站在最初部署时,会直接放在主站的一个子目录下,例如:
https://www.example.test/articles/
随着网站长期运行,博客逐渐成为一个相对独立的应用,此时将其迁移到独立子域名:
https://blog.example.test/
通常能够获得更清晰的系统边界。
但这并不是简单修改一个链接。
对于已经运行多年的 WordPress,子目录迁移到子域名通常会涉及:
- DNS
- TLS
- 前置反向代理
- Apache/Nginx VirtualHost
- WordPress
home和siteurl .htaccess- 数据库中的绝对 URL
- PHP serialized data
- WordPress GUID
- Cookie
- 页面缓存
- Object Cache
- Permalink
- 旧地址退役
- 完整回退方案
本文记录一种风险相对较低的迁移方法:
公网地址迁移到新的子域名,但 WordPress 的物理目录保持不变。
这样能够最大限度减少与域名迁移无关的文件系统变化。
一、迁移目标
假设原来的 WordPress 地址为:
https://www.example.test/articles/
希望迁移到:
https://blog.example.test/
WordPress 原本安装在服务器上的某个目录:
/var/www/site/wordpress-data/
迁移后这个目录仍然保持不变。
也就是说,只改变:
外部访问身份
而不改变:
WordPress 实际文件位置
最终架构大致为:
Internet
│
│ HTTPS
▼
Reverse Proxy
│
│ HTTP / internal network
▼
Apache
│
▼
WordPress physical directory
新的子域名直接映射到 WordPress 的现有物理目录。
这种方式有一个很重要的优势:
域名迁移与文件系统迁移彻底解耦。
二、不要先修改 WordPress
比较安全的迁移顺序并不是:
修改 WordPress URL
→ 再配置服务器
而应该反过来:
准备 DNS
→ 准备 TLS
→ 准备反向代理
→ 准备 Apache VirtualHost
→ 验证新 Host 链路
→ 修改 WordPress
原因很简单。
一旦 WordPress 的:
home
siteurl
被修改,它就可能立即开始:
- 生成新域名链接;
- 将后台重定向到新域名;
- 设置新域名 Cookie;
- 返回新域名 REST URL。
如果此时新域名的 DNS、TLS 或 Web Server 还没有准备好,站点就会进入半迁移状态。
因此应该首先让:
https://blog.example.test/
具备正常到达 WordPress 后端的能力。
三、先确认 WordPress 类型
迁移之前首先确认:
Single Site
还是:
Multisite
这两种 WordPress 的域名结构和数据库处理方式差异很大。
普通 Single Site 的迁移相对直接。
如果启用了 Multisite,则不应该简单照搬本文流程。
四、检查 home 和 siteurl
WordPress 中最核心的站点 URL 是:
home
siteurl
原来可能是:
home = https://www.example.test/articles
siteurl = https://www.example.test/articles
迁移后的目标为:
home = https://blog.example.test
siteurl = https://blog.example.test
同时还应该检查:
WP_HOME
WP_SITEURL
是否在 wp-config.php 中被显式定义。
如果这两个常量已经写死,那么仅修改数据库并不会改变最终结果。
五、检查 Cookie 设置
从:
主域名 + 子目录
迁移到:
独立子域名 + 根路径
以后,WordPress 登录 Cookie 的作用范围也会发生变化。
应当检查是否手工定义过:
COOKIE_DOMAIN
COOKIEPATH
SITECOOKIEPATH
ADMIN_COOKIE_PATH
如果没有人为固定这些值,WordPress 通常可以根据新的站点 URL 自动调整。
迁移完成以后仍然应该实际测试:
登录
后台
登出
重新登录
因为登录页能够显示,并不等于 Cookie 行为一定正常。
六、DNS 与 TLS 应先完成
新子域名应该首先拥有正确的 DNS 记录:
blog.example.test
→ public reverse proxy
然后准备覆盖该 Host 的 TLS 证书。
可以采用:
- 独立证书;
- SAN 证书;
- wildcard 证书。
关键原则是:
WordPress 开始使用新域名以前,新域名的 HTTPS 必须已经能够正常建立连接。
完成这一阶段以后,可以先测试:
TLS handshake
Host routing
reverse proxy
backend connection
而暂时不修改 WordPress 数据库。
七、前置反向代理增加新的 Host
如果架构中使用 Nginx 作为 HTTPS 前端,可以为新的博客域名建立独立 server。
示意配置:
server {
listen 443 ssl;
server_name blog.example.test;
# TLS settings ...
location / {
proxy_pass http://BACKEND_SERVER;
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 https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port 443;
}
}
其中尤其重要的是:
Host
X-Forwarded-Proto
后端必须知道:
用户访问的 Host
以及:
外部实际使用 HTTPS
否则可能出现:
- HTTP/HTTPS 重定向循环;
- WordPress 生成 HTTP URL;
- Secure Cookie 判断异常;
- 后台跳转错误。
修改 Nginx 后应先:
nginx -t
确认无误,再:
reload
而不是直接 restart。
八、Apache 使用独立 VirtualHost
后端 Apache 也应该为博客子域名建立独立 VirtualHost。
例如:
<VirtualHost *:80>
ServerName blog.example.test
DocumentRoot /var/www/site/wordpress-data
<Directory /var/www/site/wordpress-data>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
这样:
blog.example.test/
直接对应 WordPress 根目录。
这比继续通过:
/articles/
这样的 Alias 或子路径模拟 WordPress 根路径更加清晰。
同时,WordPress 的实际目录完全不用移动。
九、反向代理环境中的 HTTPS 识别
如果公网层使用 HTTPS,而后端 Apache 接收到的是 HTTP,那么 PHP 本身可能认为当前连接不是 HTTPS。
因此通常需要根据:
X-Forwarded-Proto: https
恢复 HTTPS 环境。
例如 Apache 可以根据代理 Header 设置相应环境变量。
迁移前应先确认这一机制已经正确工作。
否则 WordPress 可能产生:
HTTPS
↓
HTTP
↓
HTTPS
这样的重定向循环。
十、子目录模式的 .htaccess 需要调整
WordPress 原来运行在:
/articles/
时,.htaccess 可能包含:
RewriteBase /articles/
RewriteRule . /articles/index.php [L]
迁移到独立子域名以后,WordPress 对公网而言位于根路径:
/
因此 Rewrite 通常需要调整为:
RewriteBase /
RewriteRule . /index.php [L]
这里有一个非常重要的边界:
只调整 WordPress 的 URL 根路径,不要顺便修改 permalink 结构。
十一、不要同时重构 Permalink
假设迁移前的 permalink 为:
/index.php/%year%/%monthnum%/%day%/%postname%/
那么域名迁移以后,最安全的结果仍然是:
https://blog.example.test/index.php/...
而不是趁机改成:
https://blog.example.test/...
去掉 index.php 涉及的是另一套问题:
- rewrite 规则;
- permalink structure;
- 历史链接;
- Feed;
- REST endpoint;
- 搜索引擎 URL;
- 额外 301。
一次迁移只解决一个核心问题,可以显著降低排障复杂度。
十二、数据库里的 URL 远比 home/siteurl 多
WordPress 运行多年以后,旧域名通常分布在很多地方,例如:
wp_posts.post_content
wp_postmeta.meta_value
wp_options.option_value
wp_users.user_url
以及:
- Gutenberg 内容;
- 主题设置;
- Widget;
- 菜单;
- 附件;
- 插件配置;
- CDN 设置;
- RSS enclosure。
因此只修改:
home
siteurl
是不够的。
十三、为什么不能直接用 SQL REPLACE
WordPress 数据库中经常存在 PHP serialized data。
例如某些 option 的逻辑结构类似:
a:2:{
...
s:N:"https://old.example.test/...";
}
其中:
N
记录字符串长度。
如果直接通过:
REPLACE()
或者对 SQL dump 使用普通文本:
sed
进行全局替换,字符串长度可能不再正确,从而损坏序列化数据。
因此更适合使用:
WP-CLI search-replace
它能够处理 WordPress 常见 serialized data。
十四、先执行 Dry Run
正式替换以前应该先执行:
--dry-run
示意:
wp search-replace \
'https://www.example.test/articles' \
'https://blog.example.test' \
--all-tables-with-prefix \
--skip-columns=guid \
--precise \
--recurse-objects \
--dry-run
需要检查:
- 哪些表会发生修改;
- 替换数量;
- 是否出现异常插件表;
- 是否和迁移前的数据库审计相符。
只有 dry-run 结果符合预期,才进行正式操作。
十五、不要修改 WordPress GUID
尤其需要注意:
wp_posts.guid
即使 GUID 中仍然保留旧域名,也不应该把它当作普通页面 URL 全部替换。
因此可以明确使用:
--skip-columns=guid
将 GUID 排除在普通 URL 迁移之外。
这是域名迁移中很容易发生误操作的地方。
数据库中仍然存在旧域名字符串,并不自动意味着迁移不完整。
首先要判断:
这个字段究竟是不是应该修改。
十六、正式执行 Search-Replace
确认 dry-run 后,再执行正式替换:
旧:
https://www.example.test/articles
新:
https://blog.example.test
保持与 dry-run 相同的参数,只去掉:
--dry-run
执行后再次 dry-run。
理想结果应该类似:
0 replacements remaining
同时单独确认:
home
siteurl
已经变成新子域名。
十七、清理缓存
数据库已经修改完成,并不代表浏览器马上看到的页面一定完全正确。
WordPress 常见缓存包括:
Page Cache
Object Cache
Redis
CDN
Browser Cache
迁移以后至少应该处理:
Object Cache
Page Cache
然后重新访问新域名,让新的页面缓存重新生成。
否则可能出现:
数据库已经没有旧 URL
但是网页 HTML 仍然偶尔返回旧域名
这通常不是数据库迁移失败,而是缓存。
十八、正式验证新域名
切换完成以后,不要只测试首页。
至少应该验证以下对象。
首页
https://blog.example.test/
登录页
/wp-login.php
后台
/wp-admin/
REST API
/wp-json/
Feed
根据当前 permalink 结构验证实际 Feed 地址。
文章
随机选择多篇不同时期的文章。
媒体
检查:
/wp-content/uploads/...
静态资源
检查:
CSS
JavaScript
favicon
图片
字体
Cookie
实际完成:
登录
后台操作
登出
重新登录
测试。
十九、旧 URL 的两种处理策略
WordPress 新域名稳定以后,旧:
https://www.example.test/articles/
还需要决定是否继续存在。
有两种合理策略。
方案 A:保留 301
适合:
- 有较多外部链接;
- 搜索引擎已经收录;
- 希望历史链接长期可用。
例如:
https://www.example.test/articles/index.php/old-post/?page=2
跳转到:
https://blog.example.test/index.php/old-post/?page=2
必须保留:
path
query string
方案 B:彻底退役旧 namespace
如果旧路径不再需要兼容,可以让前置 Nginx 直接返回:
404
例如:
location = /articles {
return 404;
}
location ^~ /articles/ {
return 404;
}
最终形成:
blog.example.test/*
→ WordPress
www.example.test/articles*
→ 404
这样旧路径甚至不会进入后端 Apache。
二十、为什么直接返回 404 比放任后端处理更干净
如果旧 URL 对应服务器上仍然存在的真实目录,那么即使 WordPress 已迁移,Apache 仍可能执行目录规范化。
例如:
/articles
自动变成:
/articles/
反向代理环境下甚至可能生成不希望出现的协议或 Host。
如果明确决定废弃旧路径,那么在最前面的反向代理层直接:
return 404
通常更加简单、确定。
二十一、主站入口最后再切换
如果主站首页存在一个:
Blog
链接,不建议在迁移第一步就修改。
更安全的顺序是:
DNS/TLS
↓
Reverse Proxy
↓
Apache VirtualHost
↓
WordPress Rewrite
↓
数据库
↓
缓存
↓
新站完整验证
↓
旧 URL 策略
↓
主站 Blog 链接
只有新博客完全正常以后,再把主站入口切换到新的子域名。
这样能够避免用户提前进入一个尚未完成迁移的站点。
二十二、配置文件修改必须有回退点
生产环境配置发生变化以前,应保留修改前版本。
典型对象包括:
Nginx 配置
Apache VirtualHost
.htaccess
主站首页
数据库则应该另外建立完整 dump。
但备份管理中还有一个很容易被忽视的原则:
管理员自己的备份命名规则,不能强加到应用自己管理的文件上。
例如应用本身可能产生:
*.bak
installer
archive
backup
snapshot
cache
这些名称不一定代表“管理员手工备份”。
它们可能是应用运行状态的一部分。
因此批量整理文件以前必须先判断:
文件是谁生成的?
属于哪个应用?
当前是否仍然被应用管理?
不能仅根据:
.bak
这样的扩展名判断性质。
二十三、配置文件可以按应用命名
完成迁移以后,Web Server 配置没有必要全部按照域名命名。
例如 Apache 可以采用:
sites-available/
├── main-site.conf
├── wordpress.conf
└── cloud-app.conf
分别对应:
主站
WordPress
文件云应用
WordPress 配置文件内部再写:
ServerName blog.example.test
这样配置文件表达的是:
这个配置属于哪个应用
而 ServerName 表达:
这个应用当前通过哪个域名访问
两者职责明确。
以后即使域名发生变化,也不一定需要再次重命名配置文件。
二十四、物理目录同样不用追求改名
同理:
公网:
blog.example.test
并不要求:
物理目录:
/var/www/html/blog/
WordPress 完全可以继续位于历史目录:
/var/www/site/wordpress-data/
只要:
Apache DocumentRoot
权限
Rewrite
WordPress URL
全部正确,目录名称本身并没有技术意义。
为了“看起来一致”而迁移整个 WordPress 目录,反而会增加:
- 文件移动;
- 权限变化;
- 路径硬编码;
- 插件依赖;
- 缓存问题;
- 回滚难度。
二十五、一个更清晰的最终架构
经过迁移以后,可以形成类似:
Internet
│
Reverse Proxy
/ \
/ \
▼ ▼
www.example.test blog.example.test
│ │
└──────┬───────┘
▼
Apache
/ \
▼ ▼
Main Site WordPress
不同应用通过不同 Host 暴露。
同时保持后端文件系统相对稳定。
这种架构通常比:
/example-app-1/
/example-app-2/
/example-app-3/
全部堆在一个主域名下面更容易维护。
二十六、完整回退方案
正式迁移前,至少应该准备:
数据库完整备份
Reverse Proxy 配置备份
Apache 配置备份
.htaccess 备份
主站入口页面备份
如果迁移失败,可以按相反方向恢复:
取消新入口或旧 URL 新规则
↓
恢复 Reverse Proxy
↓
恢复 Apache
↓
恢复 .htaccess
↓
恢复数据库
↓
清理缓存
↓
恢复主站入口
↓
验证旧站
尤其需要注意:
数据库和 Web Server 路由必须作为一个整体回退。
如果数据库已经恢复到旧域名,而 Web Server 仍然只提供新 Host,站点仍然无法正常工作。
二十七、迁移结束后不要立即删除备份
完成技术验证以后,最好暂时保留迁移前回退点。
先进行一段人工验收:
- 登录;
- 后台;
- 媒体库;
- 多篇文章;
- 上传资源;
- Feed;
- REST;
- 手机端;
- PC 端;
- 缓存重新生成。
确认不存在隐蔽问题以后,再单独整理此次产生的迁移备份。
不要在迁移成功的同一个动作中自动删除回退资产。
总结
将 WordPress 从:
主域名/子目录/
迁移到:
独立子域名/
本质上不是“修改一个 URL”,而是一次跨多个层次的服务身份迁移。
涉及的主要层次包括:
DNS
TLS
Reverse Proxy
Apache VirtualHost
WordPress Rewrite
home/siteurl
Database URL
Serialized Data
GUID
Cookie
Cache
Permalink
旧 URL 策略
Rollback
比较稳妥的实践原则可以总结为:
- WordPress 物理目录能不动就不动。
- 先建立新域名链路,再切换 WordPress。
- 数据库替换必须支持 serialized data。
- GUID 不作为普通 URL 处理。
- 域名迁移和 permalink 重构分开做。
- 缓存清理属于迁移流程的一部分。
- 旧 URL 要明确选择 301 或彻底退役。
- 生产配置修改前必须建立可验证的回退点。
- 应用自己的备份或恢复文件不能仅根据文件后缀判断性质。
- 最终人工验收完成以前,不急于删除迁移备份。
一个真正完成的迁移,不只是:
新域名能打开
而应该达到:
新域名稳定工作
登录正常
后台正常
数据库 URL 正确
Serialized Data 正常
GUID 保持正确
静态资源正常
Feed/REST 正常
Cookie 正常
缓存已更新
旧地址行为明确
随时能够回退
达到这些条件以后,WordPress 从子目录到独立子域名的迁移才算真正完成。