搜索全站
建站 18 分钟阅读

WordPress 备份策略落地:按站点重要度设计频率、保留与恢复演练

备份做了但恢复不了等于没做。本文按站点重要度给出备份频率与保留周期设计,附 mysqldump + rsync 完整脚本、rclone 异地同步、自动清理旧备份与恢复演练清单。

U
UFO_PF 2021-07-27 · 阅读 18 分钟
Wo 建站
阅读提示
本文约 6,840 字,预计阅读 18 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:备份文件是空的,是在最需要它的那天发现的

几乎所有站长都”做了备份”,但真正能恢复的没几个。常见的情况有几种:备份脚本写了但从没验证过,某次目录改名后脚本静默失败,三个月没有新备份文件;备份只做了数据库,wp-content/uploads 里的两万张图片从来没备份过;备份文件就放在网站根目录下一个叫 backup 的文件夹里,服务器一崩全没了;最惨的是备份文件是 0 字节,因为 mysqldump 的密码参数写错,脚本直接报错退出而没人看日志。

备份这件事的价值只在”恢复的那一刻”才体现。所以设计备份策略时,思考顺序应该是反过来的:先想清楚”最坏情况我要恢复到什么状态、能接受丢多少数据”,再倒推频率和保留策略。

原理分析:RPO、RTO 与 3-2-1 原则

RPO(Recovery Point Objective,恢复点目标): 能接受丢失多长时间的数据。比如每天凌晨 2 点备份一次,那么最坏情况会丢失从凌晨 2 点到故障时刻的数据,RPO 约等于 24 小时。

RTO(Recovery Time Objective,恢复时间目标): 从故障发生到站点恢复正常需要多久。这决定了你要不要做自动化恢复脚本,以及备份文件要不要放在能快速取用的地方。

3-2-1 原则是业界共识:

3 份数据副本(1 份生产数据 + 2 份备份)
2 种不同存储介质(本地磁盘 + 对象存储)
1 份异地副本(不同机房 / 不同云厂商)

WordPress 站点需要备份的东西只有两样:数据库 + wp-content 目录。 其中:

  • 数据库包含文章、页面、评论、用户、设置、插件配置,体积通常几 MB 到几百 MB;
  • wp-content 包含主题、插件、上传媒体(uploads 通常占 90% 以上体积)。

WordPress 核心文件(wp-admin、wp-includes)和根目录的 wp-*.php 不需要备份——它们是标准文件,出问题时重新下载同版本即可。

按站点重要度分级设计:

级别 站点类型 更新频率 备份频率 保留周期 RPO 目标
S 电商、有交易数据 高 数据库每 6 小时,文件每日 日备 30 天 + 月备 12 个月 < 6 小时
A 内容站、企业官网 中 数据库 + 文件每日 日备 14 天 + 月备 6 个月 < 24 小时
B 个人博客、文档站 低 每日全量 日备 7 天 + 月备 3 个月 < 24 小时
C 演示站、测试站 极低 改版前后各一次 保留 3 份 无所谓

注意:WooCommerce 商城的订单数据存在数据库里,所以 S 级站点的数据库备份频率要显著高于文件备份频率。 而图片站反过来——uploads 体积大且变动频繁,适合用对象存储 + 增量同步而不是每天全量打包。

保姆级实操:一套可验证的备份体系

步骤 1:准备备份目录与数据库凭据。 不要在脚本里明文写密码,用 MySQL 的配置文件方式:

# /etc/mysql/backup.cnf  (权限必须是 600)
[client]
user=wp_user
password=你的数据库密码
host=localhost
sudo chmod 600 /etc/mysql/backup.cnf
sudo mkdir -p /backup/wordpress/{db,files}

步骤 2:编写数据库备份脚本。 关键点:使用 --single-transaction 避免锁表、备份后立刻检查文件非空、记录日志:

#!/usr/bin/env bash
# /opt/scripts/backup_db.sh
set -uo pipefail

CNF=/etc/mysql/backup.cnf
DBNAME=wp_site
DEST=/backup/wordpress/db
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=14
LOG=/var/log/wp_backup.log

mkdir -p "$DEST"
OUTFILE="${DEST}/${DBNAME}_${DATE}.sql.gz"

log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }

log "开始备份数据库 ${DBNAME}"

# --single-transaction 对 InnoDB 不锁表,--routines 带上存储过程
if mysqldump --defaults-extra-file="$CNF" \
      --single-transaction --quick --routines --triggers \
      --default-character-set=utf8mb4 \
      "$DBNAME" | gzip > "$OUTFILE"; then
    :
else
    log "错误: mysqldump 执行失败"
    rm -f "$OUTFILE"
    exit 1
fi

# 校验备份文件是否有效(gzip 完整性 + 文件大小)
SIZE=$(stat -c%s "$OUTFILE")
if [ "$SIZE" -lt 1024 ]; then
    log "错误: 备份文件过小(${SIZE}B),判定为失败"
    exit 1
fi

if ! gzip -t "$OUTFILE"; then
    log "错误: 备份文件 gzip 校验失败"
    exit 1
fi

log "备份完成: ${OUTFILE} (${SIZE} bytes)"

# 清理过期备份
find "$DEST" -name "${DBNAME}_*.sql.gz" -type f -mtime +${KEEP_DAYS} -delete
log "已清理 ${KEEP_DAYS} 天前的备份"

步骤 3:编写文件备份脚本,用硬链接实现”增量”效果。 rsync 的 --link-dest 可以让每次备份看起来是全量,实际只占增量空间:

#!/usr/bin/env bash
# /opt/scripts/backup_files.sh
set -uo pipefail

SRC=/var/www/html/wp-content
DEST=/backup/wordpress/files
DATE=$(date +%Y%m%d_%H%M%S)
KEEP_DAYS=7
LOG=/var/log/wp_backup.log

mkdir -p "$DEST"
TODAY="${DEST}/${DATE}"
# 找到上一次成功的备份作为硬链接基准
LAST=$(ls -1d "${DEST}"/[0-9]* 2>/dev/null | tail -1)

log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }

if [ -n "$LAST" ] && [ -d "$LAST" ]; then
    # 未变动的文件直接硬链接到上次备份,几乎不占额外空间
    rsync -a --delete --link-dest="$LAST" "$SRC/" "$TODAY/"
else
    rsync -a --delete "$SRC/" "$TODAY/"
fi

COUNT=$(find "$TODAY" -type f | wc -l)
NEWSIZE=$(du -sh "$TODAY" | awk '{print $1}')
log "文件备份完成: ${TODAY} (${COUNT} 个文件, 占用 ${NEWSIZE})"

# 清理旧备份,只保留最近 KEEP_DAYS 份
ls -1d "${DEST}"/[0-9]* 2>/dev/null | head -n -${KEEP_DAYS} | xargs -r rm -rf

步骤 4:配置 rclone 同步到对象存储(异地副本)。 安装后按提示配置 S3 兼容存储(阿里云 OSS、腾讯云 COS、MinIO 均可):

# 安装 rclone
curl https://rclone.org/install.sh | sudo bash

# 交互式配置,创建一个名为 oss 的远端
rclone config

# 测试连通性
rclone lsd oss:your-bucket
#!/usr/bin/env bash
# /opt/scripts/sync_remote.sh
set -uo pipefail
LOG=/var/log/wp_backup.log

log() { echo "[$(date '+%F %T')] $1" | tee -a "$LOG"; }

# --transfers 控制并发,--bwlimit 限速避免影响网站带宽
rclone sync /backup/wordpress oss:your-bucket/wordpress \
  --transfers 4 \
  --bwlimit 10M \
  --s3-chunk-size 64M \
  --delete-excluded \
  --log-file="$LOG" \
  --log-level INFO

if [ $? -eq 0 ]; then
  log "异地同步完成"
else
  log "错误: 异地同步失败"
  exit 1
fi

# 在远端保留一份"最新"标记,用于监控检查是否按时同步
date +%s | rclone rcat oss:your-bucket/wordpress/_last_sync.txt

步骤 5:配置 crontab 定时执行。 注意 cron 的 PATH 问题,脚本里用绝对路径:

sudo tee /etc/cron.d/wp-backup > /dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# 每天凌晨 2:10 备份数据库,2:40 备份文件,3:30 同步到对象存储
10 2 * * * root /opt/scripts/backup_db.sh
40 2 * * * root /opt/scripts/backup_files.sh
30 3 * * * root /opt/scripts/sync_remote.sh

# S 级站点:每 6 小时追加一次数据库备份
# 0 */6 * * * root /opt/scripts/backup_db.sh
EOF

sudo chmod 644 /etc/cron.d/wp-backup

步骤 6:定期做恢复演练。 这一步决定了你的备份是否真的有效。每月一次,在一台临时机器上执行:

# 1. 准备一个空的测试库
mysql -e "CREATE DATABASE wp_restore_test DEFAULT CHARACTER SET utf8mb4;"

# 2. 导入最新的数据库备份
zcat /backup/wordpress/db/wp_site_20261010_021001.sql.gz \
  | mysql --defaults-extra-file=/etc/mysql/backup.cnf wp_restore_test

# 3. 检查数据完整性(看文章数、用户数是否正常)
mysql --defaults-extra-file=/etc/mysql/backup.cnf wp_restore_test -e "
SELECT
  (SELECT COUNT(*) FROM wp_posts WHERE post_status='publish') AS 已发布文章,
  (SELECT COUNT(*) FROM wp_users) AS 用户数,
  (SELECT COUNT(*) FROM wp_options) AS 配置项;"

# 4. 恢复文件(rsync 到测试目录,检查图片数量)
rsync -a /backup/wordpress/files/20261010_024001/ /tmp/restore_test/wp-content/
find /tmp/restore_test/wp-content/uploads -type f | wc -l

# 5. 演练结束后清理
mysql -e "DROP DATABASE wp_restore_test;"
rm -rf /tmp/restore_test

步骤 7:把备份状态纳入监控。 用上一节的思路,检查最新备份文件的修改时间与远端 _last_sync.txt 时间戳,超过 26 小时就告警。

常见踩坑

  1. 备份文件放在 Web 目录下。 /var/www/html/backup/db.sql 这种路径可以被任何人直接下载,数据库账号密码和全站数据一起泄露。
  2. 只备份数据库不备份 uploads。 恢复后所有图片变成裂图,而图片恰恰是最难重新生成的部分。
  3. mysqldump 没加 --single-transaction。 备份期间锁表,网站在备份窗口内无法写入,表现为”每天凌晨 2 点有几秒钟打不开”。
  4. 字符集不对导致中文乱码。 必须加 --default-character-set=utf8mb4,否则 emoji 和部分生僻字恢复后变成问号。
  5. 从没做过恢复演练。 这是最致命的一条,备份的价值只在恢复时被验证。
  6. 磁盘满了导致备份失败。 备份目录所在的分区必须监控,脚本里也要检查剩余空间,不足时先清理再备份。
  7. 案例:一次改版后的”成功”备份。 客户的备份脚本一直稳定运行,某天站点被挂马,需要回滚到三天前。恢复后发现数据恢复成功但页面样式全乱——原因是他们的备份只包含数据库和 uploads,没包含自定义开发的子主题;而子主题文件在改版时被误删,备份里也没有。最后只能用 Wayback Machine 的快照重做页面结构。教训:自定义代码(子主题、mu-plugins、自定义插件)必须纳入备份范围,或者更好——把它们放进 Git 仓库。备份解决数据问题,版本控制解决代码问题,两者不能互相替代。

总结

备份体系的判断标准是”能在 RTO 内恢复到任意一个保留点”。落地清单:按站点分级确定频率(S 级数据库 6 小时、A/B 级每日)→ 用 mysqldump --single-transaction + rsync --link-dest 做数据备份 → rclone 限速同步到对象存储做异地副本 → cron 定时 + 日志留痕 → 每月一次恢复演练 → 备份状态纳入 网站监控与告警搭建 的告警项。记住:备份文件本身也要保护,参照 网站上线安全基线配置 里的权限要求,备份目录权限设为 700 且绝不能放在 Web 可访问路径下。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。