最近,我把网站从 zhimayuandi-old.com 整体迁到了 zhimayuandi.com。
一开始以为这件事很简单:旧服务器写一条 301,新服务器把网站跑起来,结束。
真正做完才发现,301 只是迁移链路里最显眼的一环。一个看似正常的新站,可能同时存在这些问题:
- 首页能跳转,深层文章却丢了路径;
- HTTP 跳 HTTPS,再跳 www,最后才到主域,形成多级跳转;
- 页面已经换域,canonical 和 sitemap 仍指向旧站;
- 新站页面正常,API、反馈和后台路由却被新 Nginx 模板覆盖;
- 百度统计里还能看到流量,但数据一直记在“旧站”名下;
- 自定义 404 页面看起来没问题,HTTP 状态码却是 200。
这篇不是一份“复制一段配置就结束”的教程,而是一次完整迁移后的复盘:怎样把域名、服务器、SEO、统计和验证连成一个闭环。
先定义成功:迁移不是“新域能打开”
我给这次迁移定了六个验收条件:
- 旧域的 HTTP、HTTPS、www、非 www 都能到达新域;
- 每个旧 URL 都跳到内容对应的新 URL,路径和查询参数不丢;
- 整条链只有一次永久重定向;
- 新站的 canonical、hreflang、内链、Feed 和 sitemap 全部指向新域;
- 静态站、API、反馈后台、证书和压缩都正常;
- 搜索引擎和统计平台知道网站已经换了地址。
如果只满足“首页能打开”,最多只能叫“新域上线”,还不能叫“迁移完成”。
第一步:不要先写正则,先做 URL 清单
域名迁移最重要的资产不是 Nginx 文件,而是新旧 URL 的对应关系。
路径完全不变时,事情最简单:
https://old.example.com/blog/article/
→ https://new.example.com/blog/article/
如果路径变了,就必须明确映射:
https://old.example.com/post/123
→ https://new.example.com/blog/article-name/
我会从旧 sitemap、访问日志、搜索平台、外链和站内链接中收集 URL,再整理成表格:
| 旧 URL | 新 URL | 处理方式 | 最终状态 |
|---|---|---|---|
/blog/example/ |
/blog/example/ |
域名级 301 | 200 |
/post/123 |
/blog/example/ |
单独映射 | 200 |
/deleted-page/ |
无替代内容 | 404/410 | 404/410 |
没有对应内容的页面,不要为了“看起来没有死链”而全部跳首页。搜索引擎可能把这种不相关跳转当成 soft 404,用户点进来同样会困惑。
第二步:旧站只做一件事——单跳到最终地址
同路径换域名时,Nginx 的 return 301 足够清晰:
server {
listen 80;
server_name old.example.com www.old.example.com;
return 301 https://new.example.com$request_uri;
}
server {
listen 443 ssl;
server_name old.example.com www.old.example.com;
ssl_certificate /etc/letsencrypt/live/old.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/old.example.com/privkey.pem;
return 301 https://new.example.com$request_uri;
}
这里有三个细节:
第一,HTTP 直接到新站 HTTPS。 不要先跳旧站 HTTPS,再跳新域。
第二,必须保留 $request_uri。 它会带上路径和查询参数:
/tools/example/?from=old
→ https://new.example.com/tools/example/?from=old
第三,旧域证书不能停。 用户和爬虫访问 https://old.example.com 时,必须先完成 TLS 握手,浏览器才看得到后面的 301。证书过期,跳转写得再漂亮也没用。
如果只有部分路径变化,再为那些路径添加精确规则。简单同路径迁移优先 return,需要捕获和改写路径时再使用 rewrite 或 map,不要反过来。
第三步:新站配置必须从“真实服务拓扑”出发
我差点踩的最大坑,是把一个通用 PHP 示例当成新站模板。
实际项目是 Astro 静态站,部署目录是 /var/www/site,同时还有 API 和反馈服务。如果误用了 index.php、PHP-FPM 或 /index.php?$args,首页也许还能被某些默认配置兜住,但静态路由和接口会直接出问题。
简化后的主站结构应该像这样:
server {
listen 443 ssl;
server_name new.example.com;
root /var/www/site;
index index.html;
location /api/ {
proxy_pass http://127.0.0.1:8602;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
}
但这仍然只是骨架。真实站点可能还有:
- 更具体的
/api/feedback、/api/bugs路由; - 上游服务返回跳转时所需的
proxy_redirect; - 修复上游 HTML 根相对链接的
sub_filter; - gzip/Brotli 预压缩;
- 静态资源缓存、安全响应头和附件目录。
所以我的原则是:
迁移时以服务器当前生效配置为事实源,而不是以网上模板为事实源。
先用 nginx -T 看清完整配置,再整理;不要凭记忆重新生成一个“差不多”的版本。
第四步:SEO 信号必须一起换域
canonical
新站每个可索引页面都应该自指向新域:
<link rel="canonical" href="https://new.example.com/current-page/" />
如果页面已经在新域,canonical 仍指向旧域,相当于主动告诉搜索引擎:“旧页面才是正版。”
hreflang
多语言站还要同步修改语言版本关系:
<link rel="alternate" hreflang="zh-CN" href="https://new.example.com/page/" />
<link rel="alternate" hreflang="en" href="https://new.example.com/en/page/" />
<link rel="alternate" hreflang="x-default" href="https://new.example.com/page/" />
每个语言页面都应返回指向其他语言版本的链接,不能只做单向声明。
robots.txt
新站 robots 中的 sitemap 地址也要换:
User-agent: *
Allow: /
Sitemap: https://new.example.com/sitemap-index.xml
robots 规则按协议、主机和端口生效。旧域的 /robots.txt 可以和其他旧 URL 一样 301,但不能理解成“新站 robots 自动接管所有旧域规则”。
sitemap
Astro 的 @astrojs/sitemap 默认生成的是:
sitemap-index.xml
sitemap-0.xml
不是想当然的 /sitemap.xml。这类错误非常隐蔽:文档、robots 和站长平台都填了 /sitemap.xml,实际线上却一直 404。
sitemap 中只放:返回 200、允许索引、canonical 一致的新域 HTTPS URL。Google 会忽略 priority 和 changefreq;lastmod 只有真实准确时才有价值。
第五步:告诉搜索引擎“这是搬家,不是复制站”
Google Search Console
我的处理顺序是:
- 验证新旧域所有权,优先用 Domain property/DNS;
- 确认旧 URL 已经 301、新 URL 可抓取;
- 在旧属性中提交“更改地址”;
- 对实际使用或被收录的 www、非 www 和其他子域变体分别检查;
- 在新属性提交新 sitemap;
- 观察网页索引、抓取统计、HTTPS 和搜索效果。
Google 的地址变更通知持续 180 天,但官方迁站指南建议重定向通常至少保留一年。从用户和外链角度看,只要成本可控,长期保留更稳妥。
百度搜索资源平台
百度侧同样要先做好一一对应的 301,再通过“网站改版”提交域名或 URL 关系,并提交新站 sitemap。平台入口和工具名称可能调整,执行时以当期控制台为准。
Bing 与 IndexNow
Bing Webmaster Tools 中应验证新站并提交 sitemap。IndexNow 可以帮助推送新增和更新 URL,但它不能替代旧域到新域的整站 301。
第六步:百度统计“还有数据”,不等于迁移完成
这次还有一个很有迷惑性的现象:域名已经换了,百度统计的旧站报表仍不断出现新访问。
原因不是 301 在统计流量。服务器返回 301 时不会执行页面里的统计 JavaScript。真正原因是:新站继续加载了旧站的同一个百度统计 Site ID。
于是有两种选择。
新域新建统计站点
这是我更推荐的做法:
- 保留旧站统计项目,不删除历史数据;
- 为新域新增统计站点,获取新的 Site ID;
- 替换网站中的 Site ID,重新构建部署;
- 运行代码安装检查,确认新域开始入数;
- 用迁移日期作为新旧报表的分界线。
优点是归属清楚,代价是趋势图分成两段。
继续沿用旧 Site ID
如果更重视一张报表里的连续趋势,可以保留原 ID,同时更新站点资料、检查域名过滤并记录迁移日期。这样不用改代码,但站点身份可能长期带着旧域痕迹。
不要长期同时安装两个 Site ID,除非明确需要短期并行校验,也清楚两套报表都会记录同一次访问。
第七步:用“验证矩阵”代替“我点开看了”
迁移完成后,我不再只测首页,而是至少覆盖这些类型:
# 旧域四种入口
curl -sSI http://old.example.com/
curl -sSI https://old.example.com/
curl -sSI http://www.old.example.com/
curl -sSI https://www.old.example.com/
# 真实深层页面和查询参数
curl -sSIL 'https://old.example.com/blog/real-article/?from=test'
# 新站基础设施
curl -sSI https://new.example.com/
curl -sSI https://www.new.example.com/
curl -sSI https://new.example.com/robots.txt
curl -sSI https://new.example.com/sitemap-index.xml
curl -sS https://new.example.com/api/health
# 不存在的页面必须返回 404
curl -sSI https://new.example.com/this-page-does-not-exist
验收标准:
- 旧域真实页面一次 301 后最终 200;
- 路径和查询参数不变;
- 新域 HTTP/www 一次跳到 HTTPS 非 www;
- robots、sitemap、API 正常;
- 不存在的页面最终为 404;
- 没有 302、循环、证书错误或额外中转。
还要补一句:不要拿不存在的示例 URL 验证“最终必须 200”。 我最初用 /post/123 测试,最终当然是 404,因为新站根本没有这个页面。验证 URL 本身也必须真实。
对于写接口和后台,HEAD/GET 成功仍然不够。反馈是否落盘、编辑后 303 跳转是否正确、附件能否读取,都要走一遍真实业务流程。
迁移完成后,还要观察什么
我把监控分成四段:
| 时间 | 重点 |
|---|---|
| 0–48 小时 | 301、404/5xx、证书、API、CDN 缓存、服务器负载 |
| 第 1–4 周 | 抓取统计、索引量、搜索流量、关键词和外链访问 |
| 第 2–3 月 | 旧 URL 是否退出索引,新 URL 是否稳定增长 |
| 第 6、12 月 | 旧域剩余流量、外链、证书续期和保留策略 |
最值得报警的不是“排名今天掉了两个位置”,而是这些明确的工程异常:
- 旧域出现非 301;
- 301 目标返回 404/5xx;
- 重定向链超过一次;
- sitemap 混入旧域、noindex 或 404 URL;
- API 错误率上升;
- 旧域证书续期失败。
最后复盘:域名迁移其实是一次“信号一致性工程”
回头看,域名迁移并不难,难的是让每一层都表达同一件事:
- Nginx 说:内容永久搬到了新域;
- 页面 canonical 说:新域是规范版本;
- hreflang 说:各语言版本都在新域;
- sitemap 只提交新域 URL;
- 搜索平台收到明确的地址变更;
- 统计平台按新的站点身份记录数据;
- 旧域证书和 301 继续稳定工作。
其中任何一层说反话,迁移都会变慢,甚至让人误以为“301 没效果”。
所以我现在对迁移的定义是:
不是把网站复制到另一台服务器,而是让用户、浏览器、搜索引擎、统计平台和所有外部链接,对网站的新地址达成一致。
当这五方都指向同一个最终 URL,域名迁移才算真正完成。