为什么备份策略比备份本身更重要

备份这件事,随手 tar 打个包、mysqldump 导一次库,人人都能做。真正拉开差距的是三个问题:出事时你能不能半小时内恢复、恢复出来的数据是否完整、服务器整台宕掉时备份是否还活着。中小网站的勒索软件攻击和误删事件里,相当一部分站长的"备份"只存在于同一台服务器的磁盘上——服务器一挂,备份一起陪葬。这也是为什么业内把 3-2-1 原则当作底线,而不是最高标准。

3-2-1 备份原则

  • 3:保留 3 份备份副本
  • 2:使用 2 种不同存储介质(本地磁盘 + 远程对象存储)
  • 1:至少有 1 份异地备份(最好跨可用区或跨云)

进阶版是 3-2-1-1-0:多出的"1"指一份离线或不可变备份,专门应对勒索软件把在线备份一并加密;"0"指恢复演练零失败。

备份类型怎么选

类型 原理 占用空间 恢复速度 适用场景
全量备份 每次复制全部数据 数据库、中小站点
增量备份 只备份上次备份后的变更 慢(需依次叠加) 大文件站
差异备份 备份上次全量以来的所有变更 折中方案

对绝大多数个人网站和中小企业站点,每日全量备份 + 保留 30 天是最省心的组合。数据库通常几 MB 到几百 MB,全量导出再 gzip 压缩,成本和复杂度都完全可控,没必要为了省那点空间引入增量逻辑。

自动备份脚本

下面这个脚本覆盖"文件 + 数据库 + 清理 + 异地同步 + 校验"五个环节,可以直接放进 cron 里每天跑:

#!/bin/bash
# backup.sh — 网站文件和数据库自动备份
set -euo pipefail

# === 配置 ===
SITE_DIR="/var/www/example.com"
DB_NAME="wordpress"
DB_USER="root"
DB_PASS="your_password"
BACKUP_DIR="/var/backups"
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)

# 远程存储配置(可选)
RCLONE_REMOTE="s3:bucket-name/backups"

# === 1. 创建备份目录 ===
mkdir -p "$BACKUP_DIR/files" "$BACKUP_DIR/database"

# === 2. 备份文件(排除缓存目录) ===
echo ">>> Backing up files..."
tar -czf "$BACKUP_DIR/files/files_$DATE.tar.gz" \
  --exclude="wp-content/cache" \
  --exclude="wp-content/uploads/backupwp" \
  --exclude="node_modules" \
  --exclude=".git" \
  -C "$SITE_DIR" .

# === 3. 备份数据库 ===
echo ">>> Backing up database..."
mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" \
  --single-transaction --quick --lock-tables=false \
  | gzip > "$BACKUP_DIR/database/db_$DATE.sql.gz"

# === 4. 删除超过保留期的本地备份 ===
echo ">>> Cleaning old backups..."
find "$BACKUP_DIR/files" -name "*.tar.gz" -mtime +$RETENTION_DAYS -delete
find "$BACKUP_DIR/database" -name "*.sql.gz" -mtime +$RETENTION_DAYS -delete

# === 5. 同步到远程存储(可选) ===
if command -v rclone &>/dev/null; then
    echo ">>> Syncing to remote storage..."
    rclone sync "$BACKUP_DIR" "$RCLONE_REMOTE" --progress
fi

# === 6. 验证备份 ===
echo ">>> Verifying backup integrity..."
tar -tzf "$BACKUP_DIR/files/files_$DATE.tar.gz" > /dev/null && echo "Files backup OK"
zcat "$BACKUP_DIR/database/db_$DATE.sql.gz" | head -1 > /dev/null && echo "Database backup OK"

echo "=== Backup complete: $DATE ==="

几个值得留意的细节:

  • mysqldump--single-transaction 在 InnoDB 下能拿到一致性快照,--lock-tables=false 避免锁表影响在线业务,适合 MySQL 5.6+;
  • find -mtime +30 清理而不是自己维护文件列表,脚本重启或中断后也不会漏删;
  • set -euo pipefail 让任一环节失败就立即中止,避免"脚本一直在报错但没人发现"。

参考:mysqldump 官方文档 https://dev.mysql.com/doc/refman/8.0/en/mysqldump.html;rclone 对象存储同步 https://rclone.org/s3/

Crontab 配置

# 每天凌晨 3 点执行备份,日志追加到文件
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

数据库量大的站点建议拆成两次:凌晨 3 点备份文件、3 点 30 分备份数据库,避免同一时刻磁盘 IO 打满拖慢线上服务。

恢复步骤

备份的价值在恢复那一刻兑现。文件恢复:

tar -xzf /var/backups/files/files_20260713_030001.tar.gz -C /var/www/example.com/

数据库恢复:

gunzip < /var/backups/database/db_20260713_030001.sql.gz | mysql -u root -p"$PASSWORD" db_name

恢复完立刻验证:前台能打开、后台能登录、最新一条数据还在。没有经过恢复验证的备份不是备份,只是占用磁盘的文件。

举个真实场景:一家做外贸站的小团队,某天早上发现整个站点被植入挖矿脚本,同时数据库被勒索软件加密。因为他们的备份脚本会把每日备份自动同步到另一家云服务商的对象存储,站点在 40 分钟内恢复到了前一天的完整状态,最多损失 24 小时内的几条订单记录。而另一个只把备份存在本机的团队,只能看着被加密的文件和一起被加密的备份干瞪眼。异地副本的意义,就体现在这种时候。

每月做一次恢复演练

给日历固定一个每月提醒,把下面三步走一遍:

  1. 在一台干净的临时服务器上恢复最近一次备份;
  2. 检查文件权限、数据库连接、缓存目录是否正常;
  3. 记录从开始恢复到网站可用的总时长,目标控制在 30 分钟以内。

备份策略建议

数据类型 备份频率 保留时间 备份方式
网站文件 每日 30 天 全量 + 可选增量
数据库 每日 30 天 mysqldump + gzip
配置文件 每次变更 90 天 Git 仓库(含历史)
日志文件 每周 180 天 logrotate
完整快照 每月 12 个月 服务商快照(灾备用)

配置文件和数据库分开管理:配置文件放进 Git 仓库,改一次提交一次,90 天内任意一次变更都能回退;数据库用快照恢复更稳妥。

安全注意事项与常见误区

  1. 备份文件包含数据库明文和潜在密钥,文件权限务必 600、目录 700;
  2. 远程存储要加密:用服务商自带加密,或先 GPG 对称加密再上传;
  3. 脚本和密钥分开存放——密钥泄露等于备份公开;
  4. 三个常见误区:只备份文件不备份数据库、备份放在同一台服务器、从未测试过恢复。这三条占了"数据找不回"案例的大头。

16IDC 观察

对个人站长来说,"每日自动备份 + 快照同步到对象存储 + 每月恢复演练"这套流程成本几乎为零,却足以覆盖九成的数据丢失场景。真正的分水岭不是备份工具多高级,而是你上一次成功的恢复测试是在什么时候。