VPSKnow

数据备份与灾难恢复完全指南

中高级
40分钟

服务器被黑客入侵、机房意外断电、手滑执行了 rm -rf——在运维世界里,数据丢失不是"会不会"的问题,而是"何时"的问题。没有备份的服务器就如同在钢丝上裸奔。本指南将带您建立一套从本地到异地、从手动到全自动的工业级备份防线。

🛡️ 核心:3-2-1 备份黄金原则

在开始动手之前,先理解信息安全界公认的黄金标准。这不是口号,而是确保数据在任何极端情况下都能存活的底线:

3

份数据副本

除生产环境的原始数据外,还需要至少 2 份独立备份。任何单点故障都不会导致数据全部丢失。

2

种不同介质

不要把所有备份放在同一块硬盘或同一台服务器。可以一份存本地磁盘,另一份存云对象存储。

1

份异地备份

至少一份备份存放在物理上远离服务器的地方(如 Cloudflare R2)。防止机房火灾、服务商跑路。

⚠️ "未经恢复测试的备份,等同于没有备份。" 每月至少执行一次恢复演练(见本文最后章节),确认备份文件可以正常还原。很多人的备份在关键时刻才发现已经损坏。

🗄️ 选型:备份存储方案对比

方案 费用 可靠性 适合场景
VPS 本地目录 免费(占用磁盘) 低(同机器故障全丢) 快速临时备份,配合其他方案使用
Cloudflare R2 ⭐ 免费 10GB,出站免费 极高(99.999999999%) 异地备份首选,个人项目完全免费
AWS S3 约 $0.023/GB/月 极高 企业级需求,与 AWS 生态集成
Backblaze B2 约 $0.006/GB/月 大容量备份,价格是 S3 的 1/4
另一台 VPS(rsync) VPS 费用 高(独立机器) 技术控自建,完全掌控数据
阿里云 OSS / 腾讯 COS 约 ¥0.12-0.15/GB/月 国内数据合规要求,国内网络快

💡 推荐组合: 本地目录(临时)+ Cloudflare R2(异地永久存储)。R2 免费额度 10GB 够个人站点半年的备份量,出站流量完全免费,是目前性价比最高的异地备份方案。

📁 基础:文件与目录备份(tar/rsync)

静态文件(网站源码、图片附件、配置文件等)的备份有两种主要方式,各有适用场景:

tar(全量打包)

✅ 单文件易传输和归档,便于按日期版本管理

注意:每次重新压缩全部内容,大目录耗时长

rsync(增量同步)

✅ 只传输变更文件,10GB 目录可能只需传几 MB

注意:目标端是展开目录(非压缩包),不便于版本回退

tar 全量备份 + rsync 增量同步
# ── 打包压缩备份(全量)────────────────────────────────────────────────────
# -c: 创建归档  -z: gzip 压缩  -v: 显示进度  -f: 指定文件名
tar -czvf /backup/website_$(date +%Y%m%d).tar.gz /var/www/html

# 多目录同时备份(一次打包多个路径)
tar -czvf /backup/full_$(date +%Y%m%d).tar.gz   /var/www/html   /etc/nginx   /opt/myapp

# 解压恢复(-C / 表示解压到根目录,还原绝对路径)
tar -xzvf /backup/website_20260316.tar.gz -C /

# 查看压缩包内容(不解压,先检查)
tar -tzvf /backup/website_20260316.tar.gz | head -20
# ── rsync 增量同步(只传输变更的文件,适合大目录)────────────────────────
# -a: 归档模式(保留权限/时间戳/符号链接)
# -v: 显示传输详情
# -z: 传输时压缩(节省带宽)
# --delete: 删除目标端在源端已删除的文件(保持镜像同步)
# --exclude: 排除不需要备份的路径

rsync -avz --delete   --exclude='*.log'   --exclude='cache/'   --exclude='tmp/'   /var/www/html/   /backup/website_sync/

# 远程同步(将本地目录备份到另一台服务器)
rsync -avz -e "ssh -p 22"   /var/www/html/   backup_user@备份服务器IP:/remote/backup/website/

# 查看本次同步传输了哪些文件(dry-run 模式,不实际传输)
rsync -avzn --delete /var/www/html/ /backup/website_sync/

💾 核心:数据库安全导出

数据库文件不能直接用 tar 复制打包(除非先停止服务),否则会导致数据损坏。必须通过 mysqldump / pg_dumpall 等工具导出为 SQL 脚本。

⚠️ Docker 用户必读: 无需进入容器 bash,直接在宿主机用 docker exec 容器名 mysqldump ...,通过管道重定向输出到宿主机文件即可。这是最干净的容器化数据库备份方式。

MySQL / PostgreSQL 备份命令
# ── 原生 MySQL 备份 ──────────────────────────────────────────────────────────
# --single-transaction: InnoDB 热备,不锁表,适合生产环境
# --routines: 包含存储过程  --events: 包含事件
mysqldump   --single-transaction   --routines   --events   -u root -p'your_password'   --all-databases   | gzip > /backup/mysql_all_$(date +%Y%m%d).sql.gz

# 只备份单个数据库
mysqldump -u root -p'your_password' myapp_db   | gzip > /backup/myapp_$(date +%Y%m%d).sql.gz

# ── Docker 容器内的 MySQL 备份(无需进入容器)─────────────────────────────────
# 直接在宿主机执行,通过管道重定向到宿主机文件
docker exec db_mysql mysqldump   -u root -p'your_password'   --single-transaction   --all-databases   | gzip > /backup/mysql_docker_$(date +%Y%m%d).sql.gz

# ── PostgreSQL 备份 ───────────────────────────────────────────────────────────
# 原生
sudo -u postgres pg_dumpall | gzip > /backup/pg_all_$(date +%Y%m%d).sql.gz

# Docker 容器内 PostgreSQL
docker exec db_postgres pg_dumpall -U postgres   | gzip > /backup/pg_docker_$(date +%Y%m%d).sql.gz

🐳 实战:Docker 卷专项备份

如果您用 Docker 部署了 Nextcloud、Mailcow 等服务,数据存储在 Named Volume 中,需要特殊的备份方式。

Docker Named Volume + Mailcow 备份
# ── 方式一:备份 Named Volume(命名卷)─────────────────────────────────────
# 使用临时容器挂载卷并打包导出

# 备份 nextcloud_data 卷
docker run --rm   -v nextcloud_data:/source:ro   -v /backup:/dest   alpine   tar czf /dest/nextcloud_data_$(date +%Y%m%d).tar.gz -C /source .

# 恢复:将备份解压回命名卷
docker run --rm   -v nextcloud_data:/dest   -v /backup:/source   alpine   tar xzf /source/nextcloud_data_20260316.tar.gz -C /dest

# ── 方式二:备份 Bind Mount 目录(绑定挂载)──────────────────────────────────
# Bind mount 的数据直接在宿主机目录,正常用 tar/rsync 备份即可
# 例如 Nextcloud 挂载到 /opt/nextcloud/data:
tar -czvf /backup/nextcloud_files_$(date +%Y%m%d).tar.gz /opt/nextcloud/data

# ── Mailcow 官方备份(推荐)───────────────────────────────────────────────────
cd /opt/mailcow-dockerized
./helper-scripts/backup_and_restore.sh backup all
# 备份文件存储在 /opt/mailcow-dockerized/backup/ 目录下

☁️ 进阶:Rclone 异地云备份

只把备份留在 VPS 本地就如同把保险箱钥匙放在保险箱旁边。一旦 VPS 欠费被删或遭遇勒索病毒,数据照样全军覆没。Rclone 是云存储界的"瑞士军刀",支持 40+ 种存储服务,可以将本地备份自动同步到任何云端。

Rclone 安装、配置与使用
# ── 安装 Rclone ───────────────────────────────────────────────────────────────
curl https://rclone.org/install.sh | sudo bash

# ── 配置 Cloudflare R2(免费 10GB,出站流量免费)────────────────────────────
rclone config
# 选择 n(新建)→ 命名为 r2
# 选择存储类型:S3(Cloudflare R2 兼容 S3 API)
# provider: Cloudflare
# access_key_id: 在 CF R2 后台生成 API Token(R2 Token)
# secret_access_key: 对应的 Secret
# endpoint: https://账户ID.r2.cloudflarestorage.com
# 其余留空,完成配置

# ── 常用 Rclone 命令 ──────────────────────────────────────────────────────────
# 单向同步(本地 → 云端,删除云端多余文件)
rclone sync /backup/ r2:my-bucket/vps-backup/   --transfers=4   --checkers=8   --progress

# 只复制(不删除云端文件,安全)
rclone copy /backup/ r2:my-bucket/vps-backup/ --progress

# 列出云端文件
rclone ls r2:my-bucket/vps-backup/ | tail -20

# 从云端恢复(将云端备份下载到本地)
rclone copy r2:my-bucket/vps-backup/ /restore/ --progress

# ── 测试配置是否正常 ──────────────────────────────────────────────────────────
rclone lsd r2:my-bucket   # 列出存储桶目录
rclone about r2:          # 查看配额使用情况

🔐 进阶:Restic 加密去重备份

Restic 是新一代备份工具,相比 tar 有三大核心优势:① 自动加密(AES-256,备份文件在云端也是密文);② 数据去重(相同数据块只存一份,节省大量空间);③ 快照管理(可以恢复到任意历史时间点)。适合对安全性有要求的场景。

Restic 安装、初始化与使用
# ── 安装 Restic ───────────────────────────────────────────────────────────────
# Ubuntu / Debian
apt install restic -y

# 或从 GitHub 下载最新版(功能更新更快)
wget https://github.com/restic/restic/releases/latest/download/restic_linux_amd64.bz2
bunzip2 restic_linux_amd64.bz2 && chmod +x restic_linux_amd64
mv restic_linux_amd64 /usr/local/bin/restic

# ── 初始化备份仓库(首次使用)────────────────────────────────────────────────
# 本地仓库
restic init --repo /backup/restic-repo

# Cloudflare R2 仓库(通过 S3 兼容 API)
export AWS_ACCESS_KEY_ID=你的R2_AccessKey
export AWS_SECRET_ACCESS_KEY=你的R2_SecretKey
restic -r s3:https://账户ID.r2.cloudflarestorage.com/bucket-name init

# ── 执行备份 ──────────────────────────────────────────────────────────────────
restic -r /backup/restic-repo backup /var/www/html /etc/nginx
# 首次备份创建快照,后续只存储变更的数据块(去重!)

# ── 查看所有快照 ──────────────────────────────────────────────────────────────
restic -r /backup/restic-repo snapshots

# ── 恢复指定快照 ──────────────────────────────────────────────────────────────
restic -r /backup/restic-repo restore latest --target /restore/

# ── 自动清理旧快照(保留策略)─────────────────────────────────────────────────
# 保留最近 7 天每日快照、4 周每周快照、12 月每月快照
restic -r /backup/restic-repo forget   --keep-daily 7   --keep-weekly 4   --keep-monthly 12   --prune

tar + Rclone 方案

  • 配置简单,命令直观
  • 备份文件是标准格式,任何工具都能恢复
  • 适合中小型站点和新手

推荐大多数用户

Restic 方案

  • 自动加密,上传到云端也是密文
  • 增量去重,相同数据只存一次
  • 完整的快照版本管理

推荐有加密需求或大量重复数据

🤖 实战:全自动化备份脚本

将文件打包、数据库导出、清理旧备份、云端同步、完成通知五个步骤合并成一个脚本,配合 Cron 实现每天无人值守自动执行。

备份脚本(含 Telegram 告警通知)

/root/auto_backup.sh
#!/bin/bash
# ════════════════════════════════════════════════════════════════════
#  VPS 全自动化备份脚本 v2.0
#  功能:文件备份 + 数据库备份 + Docker 卷备份 + 云端同步 + 告警通知
# ════════════════════════════════════════════════════════════════════
set -euo pipefail   # 任何命令失败立即退出

# ── 配置区(根据实际情况修改)────────────────────────────────────────────────
BACKUP_DIR="/root/backups"
WEB_DIR="/var/www/html"
DOCKER_DB_CONTAINER="db_mysql"
DB_USER="root"
DB_PASS="your_db_password"
DB_NAME="myapp_db"
RCLONE_REMOTE="r2:my-bucket/vps-backup"
RETAIN_DAYS=14
TELEGRAM_TOKEN=""         # 可选:Telegram Bot Token(填写后发送告警)
TELEGRAM_CHAT_ID=""       # 可选:Telegram Chat ID
LOG_FILE="/var/log/backup.log"
DATE=$(date +"%Y-%m-%d_%H-%M-%S")

# ── 工具函数 ──────────────────────────────────────────────────────────────────
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }

notify() {
  if [[ -n "$TELEGRAM_TOKEN" && -n "$TELEGRAM_CHAT_ID" ]]; then
    curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage"       -d "chat_id=${TELEGRAM_CHAT_ID}"       -d "text=$1" > /dev/null 2>&1 || true
  fi
}

# 捕获错误并发送告警
trap 'notify "❌ VPS 备份失败!服务器:$(hostname) 时间:$(date)" ; log "ERROR: 备份任务异常退出"' ERR

# ── 开始执行 ──────────────────────────────────────────────────────────────────
log "=== 开始备份任务 DATE=$DATE ==="
mkdir -p "$BACKUP_DIR"

# 1. 网站文件备份
log "① 打包网站文件..."
tar -czf "${BACKUP_DIR}/web_${DATE}.tar.gz" "$WEB_DIR" 2>>$LOG_FILE
log "   完成:$(du -sh ${BACKUP_DIR}/web_${DATE}.tar.gz | cut -f1)"

# 2. Docker MySQL 数据库备份
log "② 导出 MySQL 数据库..."
docker exec "$DOCKER_DB_CONTAINER" mysqldump   -u "$DB_USER" -p"$DB_PASS"   --single-transaction "$DB_NAME"   | gzip > "${BACKUP_DIR}/db_${DATE}.sql.gz"
log "   完成:$(du -sh ${BACKUP_DIR}/db_${DATE}.sql.gz | cut -f1)"

# 3. 清理本地过期备份
log "③ 清理 ${RETAIN_DAYS} 天前的旧备份..."
find "$BACKUP_DIR" -type f ( -name "*.tar.gz" -o -name "*.sql.gz" )   -mtime "+${RETAIN_DAYS}" -delete
log "   清理完成"

# 4. Rclone 同步到云端
log "④ 同步到云存储 $RCLONE_REMOTE..."
rclone sync "$BACKUP_DIR/" "$RCLONE_REMOTE/"   --transfers=4   --log-file="$LOG_FILE"   --log-level=INFO
log "   同步完成"

# 5. 验证云端文件数量
CLOUD_COUNT=$(rclone ls "$RCLONE_REMOTE/" 2>/dev/null | wc -l)
log "   云端共 $CLOUD_COUNT 个备份文件"

log "=== 备份任务成功完成 ==="
notify "✅ VPS 备份成功!服务器:$(hostname) 云端文件数:$CLOUD_COUNT"

⚙️ 部署与激活

赋予执行权限:

chmod +x /root/auto_backup.sh

先手动测试一次(确认无报错):

/root/auto_backup.sh && echo "✅ 测试成功"

添加到 Cron(每天凌晨 4 点执行):

# crontab -e 添加以下行
0 4 * * * /root/auto_backup.sh >>  /var/log/backup.log 2>&1

备份文件完整性验证脚本(每周执行)

#!/bin/bash
# 每周执行一次备份完整性验证(防止备份文件损坏但浑然不知)
BACKUP_DIR="/root/backups"
ERRORS=0

echo "=== 备份文件完整性验证 ==="

# 检查 tar.gz 文件
for f in "$BACKUP_DIR"/*.tar.gz; do
  if gzip -t "$f" 2>/dev/null; then
    echo "✅ $f"
  else
    echo "❌ $f 损坏!"
    ERRORS=$((ERRORS + 1))
  fi
done

# 检查 sql.gz 文件(验证能否解压读取前100行)
for f in "$BACKUP_DIR"/*.sql.gz; do
  if gunzip -c "$f" 2>/dev/null | head -100 > /dev/null; then
    echo "✅ $f"
  else
    echo "❌ $f 损坏!"
    ERRORS=$((ERRORS + 1))
  fi
done

echo "=== 验证完成,发现 $ERRORS 个问题 ==="
exit $ERRORS

🚑 演练:灾难数据恢复

灾难发生时,保持冷静,按以下步骤有条不紊地恢复。请在风险未发生前就演练一遍,确认整个流程可以走通。

完整恢复流程(4步)
# ── Step 1:从云端拉回备份文件 ─────────────────────────────────────────────
# 新服务器上先安装 Rclone 并配置,然后下载最新备份
rclone copy r2:my-bucket/vps-backup/ /root/backups/ --progress
ls -lh /root/backups/   # 确认文件已下载

# ── Step 2:恢复网站文件 ──────────────────────────────────────────────────────
# -C / 确保按绝对路径还原到原始位置(/var/www/html)
tar -xzvf /root/backups/web_2026-03-16_04-00-00.tar.gz -C /
chown -R www-data:www-data /var/www/html   # 恢复文件权限

# ── Step 3:恢复 MySQL 数据库 ─────────────────────────────────────────────────
# 先确保数据库容器已启动
docker compose up -d db

# 等待 MySQL 就绪(约 10-20 秒)
sleep 15

# 导入备份(解压后通过管道导入)
gunzip -c /root/backups/db_2026-03-16_04-00-00.sql.gz   | docker exec -i db_mysql mysql -u root -p'your_password' myapp_db

# ── Step 4:验证恢复结果 ──────────────────────────────────────────────────────
# 检查网站文件数量
find /var/www/html -type f | wc -l

# 检查数据库表
docker exec db_mysql mysql -u root -p'your_password' myapp_db   -e "SHOW TABLES; SELECT COUNT(*) FROM users;"

# 启动所有服务
docker compose up -d

常见问题解答

备份文件应该保留多少份?保留多久合适?

取决于数据变化频率和云存储成本。通用建议:本地保留 7-14 天(磁盘空间允许的范围内);云端保留 30-90 天(Cloudflare R2 出站免费,只有存储费用,10GB 内完全免费)。更精细的方案:每日备份保留 7 份、每周备份保留 4 份、每月备份保留 12 份(这正是 Restic 的 --keep-daily 7 --keep-weekly 4 --keep-monthly 12 策略)。对于不常变化的小博客,每周一次备份、保留 4 周完全够用,不需要过度备份占用空间。

Rclone sync 和 Rclone copy 有什么区别?用哪个更安全?

关键区别:rclone sync 会删除目标端在源端已不存在的文件(保持完全镜像);rclone copy 只会新增和更新文件,不删除目标端多余的文件。对于备份场景:① 如果您希望云端始终与本地一致(本地删了旧备份,云端也跟着清理),用 sync;② 如果您希望云端保留所有历史备份文件(即使本地已删除),用 copy,配合 Cloudflare R2 的生命周期规则来自动清理过期文件。推荐用 copy——更安全,不会因为本地配置错误导致意外删除云端的唯一备份。

备份文件本身占用了大量磁盘空间,如何解决?

多管齐下:① 控制本地保留天数:脚本中 RETAIN_DAYS=7,只保留 7 天本地备份;② 排除不必要的内容:网站的 node_modules/vendor/、缓存目录不需要备份,在 tar/rsync 命令中用 --exclude 排除;③ 改用 Restic 去重:Restic 对相同数据块只存一份,50GB 网站每天增量可能只占几十 MB;④ 直接备份到云端跳过本地:配合管道直接 mysqldump | gzip | rclone rcat r2:bucket/db.sql.gz,不在本地存中间文件;⑤ 定期检查磁盘使用du -sh /root/backups/* 找出占用最大的文件。

MySQL 备份时提示密码在命令行中不安全,如何解决?

MySQL 会警告 "Using a password on the command line interface can be insecure",这不是错误,只是提醒。安全做法:创建 ~/.my.cnf 文件存储密码:
[client]
password=your_password

然后 chmod 600 ~/.my.cnf(只有 root 可读)。之后 mysqldump 命令中去掉 -p'密码' 参数,MySQL 会自动从配置文件读取。对于 Docker 容器内的 MySQL,可以在 Compose 的 environment 中使用 MYSQL_PASSWORD 变量,然后在 docker exec 命令中引用同名变量。

如何验证备份是否完整可用(而不是等到出事才发现备份坏了)?

三层验证体系:① 文件完整性gzip -t backup.tar.gz 验证压缩包未损坏,0 表示完好;② SQL 可读性gunzip -c backup.sql.gz | head -50 确认 SQL 文件头部有正常的 MySQL 注释和建表语句;③ 真实恢复测试(最重要):每月选一天,在新建的测试数据库中导入备份文件,检查表数量和关键数据记录是否完整。可以用本文提供的 verifyBackup 脚本自动化前两步,第三步需要手动执行。另外,Restic 提供了 restic check 命令内置验证仓库完整性,比 tar 方案更可靠。

Cloudflare R2 怎么创建 API Token?和 AWS S3 的配置有什么区别?

R2 Token 创建路径:Cloudflare 控制台 → R2 → 管理 R2 API 令牌 → 创建令牌(选择"对象读和写"权限)。创建后会显示 Access Key ID 和 Secret Access Key,只显示一次,立即保存。与 AWS S3 的差异:① Endpoint 不同:R2 是 https://账户ID.r2.cloudflarestorage.com,需要在 Rclone 配置中指定;② R2 不收出站流量费(S3 收),适合频繁读取恢复的场景;③ R2 的存储桶默认是私有的,不需要额外配置;④ Rclone 配置时 provider 选 "Cloudflare",会自动处理一些 R2 特有的 API 差异。两者的 API 格式完全兼容,迁移很容易。

rsync 备份和 git 版本控制有什么区别?应该同时用吗?

两者解决不同问题,互补而非替代:rsync/tar 备份负责保护服务器上的运行时数据(数据库、用户上传的文件、配置文件),这些数据不适合放入 Git(体积大、敏感信息、频繁变更);Git 版本控制负责管理代码逻辑(源代码、Dockerfile、Nginx 配置模板),适合追踪修改历史和多人协作。最佳实践:代码推送到 GitHub/Gitea(Git),用 rsync+Rclone 备份数据库和用户数据。灾难恢复时:先从 Git 拉取代码重建环境,再从备份恢复数据——这是最快的恢复路径。

备份脚本的 Cron 任务一直没有执行,如何排查?

按顺序排查:① 检查 Cron 服务systemctl status cron(或 crond)是否在运行;② 验证 Crontab 语法:用 crontab.guru 粘贴 Cron 表达式验证格式;③ 检查日志grep CRON /var/log/syslog | tail -20 查看 Cron 执行记录;④ 环境变量问题:Cron 的 PATH 环境变量比交互式 Shell 少,命令找不到是最常见的原因。在脚本开头加 export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin;⑤ 权限问题:确认脚本有执行权限(chmod +x);⑥ 手动测试:直接运行脚本 /root/auto_backup.sh,观察是否有报错输出。

学完备份后,下一步应该怎么进阶?

按本站 30 篇路径,第 19 篇(本篇)→ 第 20 篇(server-monitoring)→ 第 21 篇(low-code-website-build)是最自然的延伸。关联逻辑:备份脚本设置好之后,您需要知道它是否在正常运行——第 20 篇的 Uptime Kuma 可以监控备份任务的执行状态,当 Cron 任务长时间未执行时推送告警;第 21 篇的 WordPress/Halo 建站则是本篇备份能力的最佳实践场景——一个完整的博客站点,数据库 + 文件的 3-2-1 备份让您再也不怕数据丢失。三篇构成"保护数据 → 监控运行 → 放心建站"的完整闭环。

服务器上有敏感的数据库密码和 API Key,备份到云端安全吗?

这是一个合理的担忧。分层防护方案:① tar + gzip 方案:备份文件本身未加密,但 Cloudflare R2 的存储桶默认私有(需要 API Token 才能访问),云服务商本身不会主动查看;如果您备份的是配置文件(含密码),建议用 openssl enc -aes-256-cbc 对 tar.gz 再加密一层;② Restic 方案:所有数据在上传前就用 AES-256 加密,即使有人获得了 R2 的 API Token,下载的备份文件也是密文,解不开 Restic 密码就无法读取内容——这是对敏感数据最安全的方案;③ 通用建议:备份时可以排除不必要的敏感文件(如 .env),只保留重建所需的最小数据集,密码单独用密码管理器(Vaultwarden)存储和备份。