网站迁移服务器怎么操作?数据备份、域名切换与验证流程

网站迁移服务器是运维里风险等级最高的一类操作。它不像配置变更那样可以快速回退——DNS 一旦生效,用户就开始访问新的服务器,而如果新环境有问题,你面对的是正在流失的真实流量。更麻烦的是迁移过程中数据还在变化:你在导出数据库的这几分钟里,老站的用户仍在下单、发帖、上传图片,两边的数据就此分叉。

这篇文章给一套可以照着执行的迁移流程:先盘点再动手,用工具而非手动 move 保证文件一致,用合适的导出参数保证数据库一致,用停机窗口或维护页把数据差额收敛到最小,最后用不依赖 DNS 的方式先验证再切换。命令覆盖 RHEL、CentOS、Rocky 系与 Debian、Ubuntu 系的差异。

迁移前先盘点,这一步不能省

很多迁移事故的原因不是技术难点,而是漏了东西。出发前做一张完整的资产清单:

盘点项 具体内容 漏掉的后果
域名与 DNS 所有解析记录,含子域、MX、TXT、CNAME 邮件收不到、第三方验证失效
网站文件 站点目录、上传目录、符号链接 图片丢失、软链失效导致 404
数据库 库名、字符集、用户与权限、存储过程/触发器/事件 程序报错、定时任务不执行
定时任务 用户的 crontab 与系统级定时任务 备份、队列、统计全部停摆
服务依赖 Redis、消息队列、Elasticsearch 等 功能异常或数据丢失
证书 SSL 证书文件与私钥、续期方式 新站没有 HTTPS 或三个月后过期
特殊配置 Nginx/Apache 站点配置、伪静态、PHP 参数、PHP 版本 伪静态失效、上传失败、兼容性报错
外部对接 第三方 API 白名单、支付回调、CDN 回源、邮件服务 回调失败、CDN 拉不到内容
监控与日志 告警规则、日志采集配置 出问题了没有告警也没日志可查

获取这些信息最快的方式是把老服务器上的关键配置打包一份下来对照:

# Nginx / Apache 配置
sudo tar czf /tmp/webconf.tar.gz /etc/nginx /etc/httpd /etc/apache2 2>/dev/null

# 计划任务
sudo crontab -l -u root 2>/dev/null
sudo ls -la /etc/cron.d/ /var/spool/cron/ 2>/dev/null

# PHP 版本与模块、数据库版本
php -v && php -m
mysqld --version || mariadb --version

# 监听端口清单
sudo ss -lntup

版本一致性是绕不开的话题。 理想情况是新老服务器的 PHP、MySQL、Web 服务器主版本保持一致,这样迁移后程序行为不变。如果必须跨版本(比如 PHP 7.4 升到 8.2),要先在测试环境验证兼容性,不能指望"搬过去应该也能跑"。一个稳妥做法是先把新环境搭成和老环境同版本,迁移成功后再单独安排一次版本升级,把两件高风险事分开做。

还有一件必须提前确认的事:老服务器不要迁移完就立刻销毁。保留至少三到七天,期间保持数据可随时切回,这是最后的回滚保障。

第一步:新服务器环境搭建

在新机器上安装与老机一致的运行环境。

Rocky、RHEL、CentOS(较新版用 dnf,旧版 CentOS 用 yum):

sudo dnf upgrade --refresh -y
sudo dnf install -y nginx mariadb-server php php-fpm php-mysqlnd php-gd php-mbstring \
                    php-xml php-curl php-zip php-bcmath php-intl php-opcache rsync

Debian、Ubuntu:

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx mariadb-server php-fpm php-mysql php-gd php-mbstring \
                    php-xml php-curl php-zip php-bcmath php-intl php-opcache rsync

两边都要创建站点目录、部署用户和独立运行用户,权限模型与老站保持一致:

sudo mkdir -p /var/www/example.com/public
sudo useradd -m deploy 2>/dev/null || true

同时把站点配置文件提前写好。这一步很重要——配置文件应该随文件一起迁移或提前在新机写好,而不是等流量切过去之后才手写。可以用 rsync 或 scp 把打包好的配置传过去再逐条核对路径差异。

第二步:迁移网站文件

传输文件的两种主流方案的对比:

方案 适用场景 优点 缺点
rsync 直传 常规迁移、可中断续传、需要增量同步 支持断点续传、可用 --exclude 排除、可反复执行追赶增量 首次传输耗时取决于数据量
tar 打包后传 海量小文件、网络质量差、需要压缩 压缩后体积小,小文件场景比逐个传输快很多 中断需重来,需要额外磁盘空间
scp 临时传几个文件 简单 无断点续传,不适合大目录

推荐用 rsync,并允许它反复执行。 迁移通常需要跑不止一次:第一次全量拷贝耗时较长,正式切换前再跑一次追赶这段时间的变化,最后一次在停维护后立即执行,把差额收敛到极小。rsync 的增量特性让这个过程非常自然。

从老服务器推送到新服务器:

sudo rsync -aHAX --numeric-ids --info=progress2 -e ssh \
      --exclude='cache/' --exclude='*.log' \
      /var/www/example.com/ root@新服务器IP:/var/www/example.com/

参数含义要理解,不要只复制命令:

参数 作用
-a 归档模式,保留权限、属主、时间戳、符号链接
-H 保留硬链接
-A 保留 ACL
-X 保留扩展属性
--numeric-ids 不按用户名映射 UID/GID,跨机器时避免属主错乱
--info=progress2 显示整体进度
--exclude 排除缓存与日志这类可再生的临时数据

特别注意 -a 中已包含 -l(保留符号链接),不要把 rsync 写成带斜杠的模式去追加目录名而复制了整个目录进去。 源路径末尾的 / 表示同步目录内的内容,不带 / 表示连目录本身一起同步,两者结果完全不同,这是 rsync 最经典的错误。

不建议加 --delete:如果目的目录已有内容,删除可能导致意外损失,尤其是当你已经先把配置放到新机器上时。只有在确认目标目录可以被完全替换时才用它,且先用 --dry-run 模拟:

sudo rsync -aHAX --numeric-ids --delete --dry-run -e ssh \
      /var/www/example.com/ root@新服务器IP:/var/www/example.com/

海量小文件场景(比如几十万张图片)rsync 可能很慢,改用打包加解压:

# 老服务器打包
sudo tar czf - -C /var/www example.com | ssh root@新服务器IP 'cat > /tmp/site.tar.gz'

# 新服务器解压
sudo tar xzf /tmp/site.tar.gz -C /var/www

校验一致性:

# 统计文件数与总大小,两边对比应一致
sudo find /var/www/example.com -type f | wc -l
sudo du -sh /var/www/example.com

第三步:迁移数据库

数据库迁移最容易出的问题是参数不完整,导致存储过程、触发器、事件、视图丢失,或者字符集错乱造成中文乱码。

常规规模用 mysqldump

在老服务器上执行导出:

mysqldump -u root -p \
  --single-transaction \
  --routines \
  --triggers \
  --events \
  --default-character-set=utf8mb4 \
  --hex-blob \
  --databases website > /tmp/website.sql

参数逐条说明:

参数 作用 不加的后果
--single-transaction 在一个事务内做一致性快照导出(InnoDB 适用) 导出过程中业务仍在写入,数据可能是撕裂的
--routines 导出存储过程和函数 依赖存储过程的业务报错
--triggers 导出触发器 触发器丢失,数据完整性逻辑失效
--events 导出事件调度器 定时数据统计不执行
--default-character-set=utf8mb4 指定连接字符集 中文乱码、表情符号损坏
--hex-blob 二进制字段以十六进制输出 二进制数据被破坏
--databases 导出时包含建库语句 导入时需要先手工建库

传输并导入:

scp /tmp/website.sql root@新服务器IP:/tmp/

在新服务器上:

mysql -u root -p < /tmp/website.sql

导入后必须验证:

SHOW DATABASES;
USE website;
SHOW TABLES;
-- 表数量应与老库一致
SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='website';
-- 关键表行数对比
SELECT COUNT(*) FROM 某张核心表;

再检查字符集和存储过程是否齐全:

SELECT table_name, table_collation FROM information_schema.tables WHERE table_schema='website';
SHOW PROCEDURE STATUS WHERE Db='website';
SHOW TRIGGERS;

注意:MySQL 的用户账号不会跟着 --databases 导出。 需要在新服务器上单独创建应用账号:

CREATE USER 'website_user'@'localhost' IDENTIFIED BY '新的强密码';
GRANT ALL PRIVILEGES ON website.* TO 'website_user'@'localhost';
FLUSH PRIVILEGES;

想导出用户定义可以用 --system=user 参数单独处理,但通常更推荐在新机上重建并顺便更新密码。

大库用多线程工具

mysqldump 是单线程的,几十 GB 以上的库导出加导入可能耗时数小时甚至更久,超出可接受的业务停机窗口。这种情况的思路是换用多线程逻辑备份工具,社区常见的选择是 mydumper 和 myloader:

# 导出:多线程,按表或按块切分(包名以发行版仓库实际提供为准)
mydumper -u root -p '密码' -B website -o /backup/website -t 8 -F 100 -c

# 导入
myloader -u root -p '密码' -B website -d /backup/website -t 8

其中 -t 是线程数,-F 是单个文件切分大小(MB),-c 表示压缩输出。具体参数名和可用性请查相应发行版的文档与包版本。

还有一种方案是物理备份工具级别的迁移(针对 MySQL 的数据目录热备),速度快且与 server 版本强相关,适合同版本、大数据量的场景,代价是对版本一致性要求高、跨版本不可用。

无论哪种方式,大库迁移的正确思路都是:全量加增量。先做一次全量复制让两边数据接近,正式切换时再用增量(如 binlog 追平或短暂只读后的最后一次 rsync 式导出)把差额补上,把停机时间压到分钟级。

第四步:保证数据一致性

只要业务在写入,纯靠"导出加导入"就无法做到完全一致。处理这个差额有三种策略,按业务容忍度选择。

策略一:设置维护窗口,老站进入只读或维护页。 这是最稳妥、也是绝大多数中小站点应该采用的方案。操作顺序:

-- 老库上执行,阻止写入
FLUSH TABLES WITH READ LOCK;
SET GLOBAL read_only = ON;

然后在老站放置一个维护页:

# 老服务器 Nginx:所有请求返回维护页,返回 503 表示暂时不可用
location / {
    return 503;
}
error_page 503 /maintenance.html;

为什么用 503 而不是简单的 200 页面? 因为对搜索引擎来说,503 明确表示"临时不可用稍后再来",不会被当作内容变更而重新索引;返回 200 的维护页则可能被搜索引擎收录,导致排名损失。

维护期间完成最后一次增量同步:

# 最后一次导出(此时数据已静止)
mysqldump -u root -p --single-transaction --routines --triggers --events website > /tmp/final.sql

# 最后一次文件同步
sudo rsync -aHAX --numeric-ids -e ssh /var/www/example.com/ root@新服务器IP:/var/www/example.com/

完成后务必确认应用层的数据库连接字符串已指向新库地址(同机部署的话仍是本机地址,不受迁移影响)。

策略二:应用级双写或只读锁定。 对订单、支付这类不能丢数据的表,可以让应用在短时间内既写老库又写新库(或写消息队列),这在有开发能力的团队可行,代价是需要改造代码。

策略三:容忍少量差额。 适用于内容型站点:接受切换时间点前后几分钟的数据差异,比如最后再同步一次增量(按时间过滤新发布的文章或新上传的图片)。这个方案必须在事前向业务方明确说明风险并获得确认,不能由运维单方面决定。

选择建议:内容站、企业官网、博客用策略一,挑一个低峰时段,停机十分钟到半小时通常足够。电商、论坛、交易类系统应尽量走策略二或策略一加更精细的双写方案,并延长验证时间。

第五步:切换 DNS 与灰度回滚

提前降低 TTL

这是最容易忘记、也最影响切换体验的一步。TTL 决定了各地递归 DNS 缓存旧记录的时长。

操作要点:提前至少一个旧 TTL 周期修改。 例如原来 TTL 是 3600 秒,要在切换前一天就把 TTL 改成 60 或 300。查询当前 TTL:

dig example.com A

输出里的数字就是剩余缓存秒数。确认新 TTL 已经生效(各地查询结果都显示小数值)之后再执行切换。

迁移前先验证新服务器

不要改完 DNS 才第一次访问新服务器。用以下方式绕过 DNS 直接测试。

Linux 或 macOS 修改本机 hosts:

sudo tee -a /etc/hosts <<'EOF'
新服务器IP  example.com www.example.com
EOF

Windows 修改 C:\Windows\System32\drivers\etc\hosts,内容相同,需要管理员权限。测试完记得删除这些行,否则以后访问会一直走旧指向。

更轻量的方式是用 curl 的 --resolve 参数,不修改任何文件:

curl --resolve example.com:443:新服务器IP -o /dev/null -sS -w '%{http_code} %{time_total}s\n' https://example.com/
curl --resolve example.com:80:新服务器IP  -o /dev/null -sS -w '%{http_code} %{time_total}s\n' http://example.com/

镜像地核对:首页、若干二级页面、后台登录、图片资源、表单提交、支付或回调路径都要过一遍。

正式切换

在 DNS 控制台把 A 记录(以及 AAAA 记录)改成新服务器 IP。如果使用了 CNAME(比如指向 CDN 或厂商负载均衡),改的是对应的后端地址。

切换后持续观察解析在全球的传播情况:

dig example.com A +short
dig @1.1.1.1 example.com A +short
dig @8.8.8.8 example.com A +short
dig @223.5.5.5 example.com A +short

同时观察老服务器和新服务器的访问日志。如果切完半小时后老服务器仍有大量访问,说明部分地区缓存未过期,属于正常现象,可以用老服务器做反向代理把剩余流量转发到新服务器,避免这部分用户看到旧数据:

# 老服务器:把过来的请求代理到新服务器,平滑过渡
server {
    listen 80;
    server_name example.com www.example.com;
    location / {
        proxy_pass http://新服务器IP;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这是一个非常实用的过渡技巧,能让 DNS 未刷新的用户也能拿到新数据,避免"一部分人看新站一部分人看旧站"造成的数据不一致。

回滚方案

回滚很简单:把 DNS 记录改回老服务器 IP。但因为 TTL 的存在,回滚生效也需要时间。所以回滚窗口内必须保证老服务器仍处于可服务状态,这也是前面强调不要立刻销毁老机器的原因。

回滚决策要有明确标准,比如:核心功能(下单、登录、支付)失败率超过阈值、数据库连接异常无法快速修复、数据一致性出现不可逆问题。不要因为小的样式问题就回滚,来回切换会让 DNS 状态更加混乱。

第六步:迁移后的验证清单

切换完成不等于迁移完成。按下面清单逐条核对:

验证项 方法 通过标准
DNS 解析 多个公共 DNS 查询 全部返回新 IP
HTTP 状态码 首页、栏目页、详情页逐个请求 200,无 404/500
HTTPS 证书 浏览器或 openssl 检查 域名匹配、证书链完整、未过期
HTTP 跳转 访问 http:// 版本 正确 301 到 https
伪静态 访问带参数的二级页面 正常显示而非 404
图片与上传 抽查历史图片、新上传一张 均可正常访问
后台登录 登录后操作一次 Cookie 与会话正常
数据库写入 发布测试内容后查库 数据落库正确
邮件发送 触发一封注册或通知邮件 能收到且不在垃圾箱
计划任务 检查 crontab 是否执行 日志有正常输出
CDN 访问 CDN 域名并检查回源 回源指向新 IP 且正常
搜索引擎 站点地图提交、robots 检查 URL 结构未变,无大量 404
监控告警 确认新机监控已接入 CPU、内存、磁盘、服务存活均有告警
第三方回调 支付、短信、接口回调测试 回调成功到达

观察日志是发现问题最快的手段:

sudo tail -f /var/log/nginx/example.com.access.log
sudo tail -f /var/log/nginx/example.com.error.log
sudo journalctl -f -u php-fpm

重点关注 4xx 和 5xx 的比例,以及是否有大量请求某些不存在的路径——这通常意味着某个资源目录没迁移完整。

CDN 用户要额外做一件事:更新 CDN 的回源配置为新服务器 IP。如果 CDN 还在回源到已下线的老服务器,用户会看到 502 或旧内容,而 DNS 层面看起来一切正常,这类故障排查起来很绕。

搜索引擎方面,如果 URL 结构没有变化,一般不会有明显影响。如果有改动,需要设置 301 重定向并在站长平台更新站点地图。切过程中的 503 维护页不会被当作内容变更,这点前面已经说明。

常见误区

误区一:直接在生产服务器上操作,没有回滚准备。 至少要有:完整数据备份、老服务器保持可用、DNS 记录可速改回这三样。

误区二:只迁移了数据库,忘了权限。 用户表迁移后新库的账号没建,站点直接 500。

误区三:忽略 crontab。 迁移完成后一切正常,直到第二天发现备份没跑、队列没消费。 定时任务不会跟着 rsync 走,必须手工核对并迁移。

误区四:迁移后长期遗忘证书续期。 新机上如果没有配好自动续期,九十天后站点会因证书过期全面不可用。

误区五:不检查隐藏文件和符号链接。 用 scp 或不当的打包参数可能丢失点文件和软链,导致 .env 缺失或某些目录路径失效。

误区六:迁移完立刻销毁原服务器。 一旦出现深层次数据问题(比如某个触发器迁移时丢失,几周后才暴露),没有老机器就没有退路。

常见问题

迁移过程中 SEO 会受影响吗?

短暂维护用 503 状态码,URL 结构保持不变,一般影响很小甚至没有。如果更换了 URL 结构,必须配 301 重定向并提交新的站点地图,短期内排名可能有波动,通常数周恢复。

不停机能迁移吗?

技术上可以,通过主从复制或双写方案实现,但复杂度和风险都高。绝大多数中小站点选择一个低峰期的短暂维护窗口,性价比更高。

数据量很大,停机时间能压到多久?

关键策略是"全量加增量":先在后台把绝大部分数据同步过去,正式停机时只同步最后的差额。配合这个方法,几十 GB 的站点也能把停机控制在几分钟到几十分钟。

DNS 改完之后多久全部生效?

取决于修改前的 TTL。如果提前降到 60 秒,多数地区几分钟内生效;如果没提前降 TTL 且原值是 3600,可能需要几小时甚至更久才能全网收敛。期间用老服务器做反向代理可以平滑过渡。

怎么确认数据没有丢失?

对比两边的表数量、核心表行数、文件总数和目录总大小,再抽查实际内容。数据库可以对比关键表的最大 ID 与记录数,文件系统用 find | wc -l 和 du -sh 对比。

迁移后发现邮件发不出去怎么办?

大概率是 MX、SPF、DKIM、DMARC 这些记录仍在指向旧 IP,或者 PTR 记录未更新。此外新 IP 可能在部分反垃圾组织的记录里信誉不佳,需要一段时间积累。SPF 记录里要把新服务器的 IP 加进去。

总结

  • 迁移前必须完整盘点:域名记录、网站文件、数据库、crontab、第三方依赖、证书与配置,任何一项遗漏都会表现为上线后的功能性故障。
  • 文件迁移优先用 rsync 并支持重复执行追赶增量,注意源路径末尾斜杠的含义差异,-aHAX --numeric-ids 是跨机迁移的常用参数组合。
  • 数据库导出至少带 --single-transaction --routines --triggers --events --default-character-set=utf8mb4;超大库改用多线程逻辑备份工具并采用"全量加增量"把停机窗口压到分钟级。
  • 一致性靠短暂的只读加维护页解决,维护页必须返回 503 而非 200,避免被搜索引擎当作内容收录。
  • 提前一天降 DNS TTL,切换前用 --resolve 或 hosts 在新机上验证,切换后让老服务器反向代理到新服务器吸收剩余流量,并保留老机三到七天做回滚保障。
  • 迁移后的验证必须覆盖 HTTPS、伪静态、上传、后台登录、计划任务、CDN 回源、邮件与第三方回调,而不是只看首页能不能打开。