换机最怕两件事:数据没搬全和出问题回不去。所以顺序永远是「先备份、再迁移、留退路」,而不是先动 DNS。

下面以 Ubuntu、旧机 old.example.com、新机 new.example.com 为例。

一、先盘点旧机在跑什么

systemctl list-units --type=service --state=running
ss -tulpn
crontab -l
ls /etc/cron.d /etc/cron.daily

把这几类东西列成清单:

  • 运行中的服务(nginx、php-fpm、数据库、应用进程);
  • 监听端口与对应程序;
  • 定时任务(crontab、systemd timer);
  • 站点目录(如 /var/www)、应用目录(如 /opt、/srv);
  • 配置文件(/etc/nginx、/etc/letsencrypt、环境变量文件);
  • 数据库名、用户与授权。

清单不全,迁移完一定会漏东西。

二、先做一份可验证的备份

数据库

mariadb-dump --all-databases --single-transaction --routines --events \
  | gzip > /root/all-databases-$(date +%F).sql.gz

三个参数的意义(来自 MariaDB 官方文档):

  • --single-transaction:导出前发 START TRANSACTION,对 InnoDB 表取一致快照,不阻塞应用;官方也提醒,MyISAM、MEMORY 这类非事务表不保证一致。
  • --all-databases:导出全部数据库。
  • --routines、--events:把存储过程、函数和事件调度器一并带上,不加就会丢。

文件

tar czf /root/site-files-$(date +%F).tar.gz /var/www /etc/nginx /etc/letsencrypt /opt

验证备份(这步不能省)

  • tar tzf 能看到文件清单;
  • 把 .sql.gz 导入新机上的临时库,确认表数量与关键表行数对得上;
  • 记下备份文件的校验值:sha256sum。

没有验证过的备份,只能算「有一份文件」,不能算「有备份」。

三、新机准备

  1. 装同版本或更新的系统,装同名软件包(nginx、php、数据库版本尽量对齐);
  2. 建好相同的系统用户,或迁移时用 --numeric-ids 保留数字 UID/GID;
  3. 确认新机能 SSH 登录,防火墙放行 22/80/443。

四、第一轮 rsync(业务仍在旧机跑)

先在旧机上执行空跑,看清单:

rsync -aHAXz --numeric-ids --dry-run \
  --exclude={/proc,/sys,/dev,/run,/tmp} \
  /var/www /etc/nginx /etc/letsencrypt /opt \
  [email protected]:/

确认没有意外的大量删除或权限变更后,去掉 --dry-run 实际执行。

参数含义(rsync 官方手册):

  • -a:归档模式,递归并保留权限、时间戳、属主等;
  • -H:保留硬链接;
  • -A、-X:保留 ACL 与扩展属性;
  • -z:传输时压缩,跨地域省带宽;
  • --numeric-ids:按数字 UID/GID 传输,避免两台机器用户名不一致导致权限错乱。

如果站点很大,第一轮耗时长是正常的;这一轮的作用是把绝大部分文件先搬过去。

五、切换窗口:停机、最后一轮同步

选低峰时段,按这个顺序:

  1. 旧机停写入(停应用或切维护页):
systemctl stop nginx
  1. 导出最终数据库(同上一条 mariadb-dump);
  2. 再跑一次 rsync,这次加 --delete,让目标端与源端完全一致:
rsync -aHAXz --numeric-ids --delete \
  --exclude={/proc,/sys,/dev,/run,/tmp} \
  /var/www /etc/nginx /etc/letsencrypt /opt \
  [email protected]:/

--delete 会删除新机上多出来的文件,用之前务必确认目标路径没写错。

  1. 把最终数据库导入新机:
gunzip -c all-databases-2026-10-27.sql.gz | mariadb
  1. 新机启动服务并自检:
nginx -t
systemctl start nginx
systemctl status nginx --no-pager

到这一步,用 IP 或临时 hosts 绑定访问新机,确认页面正常。

六、证书处理

/etc/letsencrypt 整目录已同步,路径一致就能直接用。注意私钥权限:

chmod 600 /etc/letsencrypt/live/*/privkey.pem

DNS 切到新机后,验证续期:

certbot renew --dry-run

DNS 没切之前不要在新机跑续期验证,HTTP-01 验证会打到旧机。

七、切 DNS

如果域名在 Cloudflare:

  • 官方说明里,代理记录(橙色云)的 TTL 固定为 Auto = 300 秒,不能改;
  • 仅 DNS 记录(灰色云)可以自定义 TTL,最低 60 秒(非 Enterprise);
  • 官方也提醒:TTL 越长,改记录生效越慢。

建议在切换前 24 小时把 TTL 调短,切换时只改 A 记录指向新机 IP,然后观察:

dig +short a.example.com
curl -I https://a.example.com

同时看旧机访问日志是否在下降、新机日志是否正常。

八、切换后的验证清单

  1. 首页、文章页、后台都能打开;
  2. HTTPS 证书域名正确,无混合内容告警;
  3. 数据库能读能写(发一条测试内容再删掉);
  4. 上传功能正常(目录属主是 Web 用户);
  5. 定时任务在跑(看日志时间戳);
  6. 邮件、队列、第三方回调没有报错;
  7. certbot renew --dry-run 通过;
  8. 监控与备份任务已经指向新机。

九、留退路

  • 旧机至少保留 72 小时,期间不要释放、不要删数据;
  • 出问题时把 DNS 切回旧机 IP,并重新启动旧机服务;
  • 确认新机稳定后再退订旧机,退订前再导出一份最终备份。

十、日常备份:迁移之外的另一件事

  • 每日:数据库导出(--single-transaction);
  • 每周:站点文件全量打包;
  • 异地:至少一份不在本机(另一台 VPS 或对象存储);
  • 演练:每季度真正恢复一次,确认备份能用;
  • 保留:至少留 7 天日备 + 4 周周备。

一句话总结

先盘点、再备份并验证,rsync 两轮(第二轮配短停机),证书整目录带走,DNS 最后切,旧机留三天。 迁移的成功标准不是「新机能打开」,而是「出问题能在十分钟内切回去」。