站长救命必备:用 Cron + Restic + S3 把 VPS 数据库与网页每日静默增量备份

网站最宝贵的是数据!本文讲清为什么不用 tar、为什么选 Restic/BorgBackup(加密/去重/快照/增量),并实战编写脚本:每日凌晨 dump MySQL,加密增量推送到 Cloudflare R2 / AWS S3,自动清理 30 天前旧快照。

先讲三个真实到让人后背发凉的场景。第一,你在国外 VPS 上跑了两年博客,某天手滑敲了 rm -rf /var/www/*,回车后才发现路径多了个空格,连家目录一起删了。第二,服务器中了勒索病毒,数据库文件全被加密,黑客留了句"打 0.05 个比特币就给你解密"。第三,某天清晨机器起不来了,工单提上去,商家说"磁盘阵列挂了,数据恢复不了,但你可以免费换一台新的"。这三种翻车,每一种的后果都只有一句话:服务器可以再买,数据丢了就彻底完了

很多新手对备份有一种迷之自信:要么从不备份,要么用最原始的办法。这篇文章就是来纠正这个认知的——我们用一套现代化、自动化、加密的灾备方案,把"每天凌晨自动备份、加密增量推送、30 天滚动清理、随时能恢复"这整套动作,变成一行也不用你半夜爬起来手动跑的流水线。

延伸阅读

更多相关攻略推荐:Ollama AI系列(2):VPS上的AI推理与API应用Ollama AI系列(2):VPS上的AI推理与API应用【CPU选型 01】搭载 AMD Ryzen 9950X 的 VPSARM / Ampere 席卷 VPS:性价比真香还是兼容陷阱?Ollama AI系列(2):VPS上的AI推理与API应用

一、先说结论:为什么别再用 tar 压缩备份了

我见过太多人把备份写成 tar -czf /backup/site.tar.gz /var/www,然后 scp 到另一台机器。这套做法不是完全没用,但坑实在太多,列出来你看看中了几条:

  • 占空间、吃 CPU:全量压缩一份几 GB 的站点,磁盘和 CPU 都要被狠狠薅一顿,大站点甚至能把机器拖卡。
  • 每次都是全量传输:无论你只改了一个 CSS 文件,还是新增了 500 张图片,tar 方案都整包重新传,带宽和时间是纯浪费。
  • 没有去重:两个快照之间 99% 的内容是相同的,但 tar 不管这些,照样存两份,仓库很快被撑爆。
  • 多版本管理是噩梦:你想保留"最近 30 天每天一份",就得自己写脚本轮转文件名、算日期、删旧的,稍微写错就丢版本或堆满磁盘。
  • 明文,不安全:tar 出来的包是明文,传到对象存储上,等于把数据库密码、用户隐私、配置文件直接摊在第三方面前。被拖库就是裸奔。

说白了,tar 是"打包工具",不是"备份系统"。它解决不了"增量、去重、加密、版本管理、可恢复性验证"这些备份真正该操心的事。

二、现代备份工具:Restic 与 BorgBackup

这两位是目前开源圈最稳的两条路,核心能力都覆盖了传统 tar 方案的盲区:

  • 端到端加密:数据在本地就用 AES-256 加密(Restic 是 AES-256-CTR + Poly1305-AES 认证;Borg 是 AES-256-CTR + HMAC-SHA256),上传到对象存储的已经是密文。就算存储商被拖库,你的数据也只是一堆无法解密的乱码。
  • 内容去重(deduplication):备份时把文件切成小块,相同内容的块只存一次。第二次备份只传变化的部分,存储和带宽都大幅缩水,典型能省下 60%-80% 的空间。
  • 快照(snapshot)管理:每次备份都会生成一个可读的快照,相当于那一时刻的完整状态。你可以恢复到任意一天的任意快照。
  • 真正的增量备份:首次是全量,之后每次只传新块,速度飞快。

既然都这么强,怎么选?一句话对比:

  • Restic:用 Go 写的单文件静态二进制,跨平台(Linux/macOS/Windows 通吃),原生支持 S3、B2、Azure、SFTP 等一堆对象存储后端,命令行简洁,上手快。对非 S3 的云存储友好度最高。
  • BorgBackup:偏 Linux/本地与 SSH,去重率略高、压缩选项更丰富(LZ4/zlib/LZMA/zstd),有 FUSE 挂载能像目录一样浏览快照;但没有原生 Windows 客户端,云存储要靠 rclone/borgmatic 中转。

对我们这群"买国外 VPS、要把备份推到 Cloudflare R2 / AWS S3 的中国折腾党"来说,Restic 几乎是默认答案:S3 兼容后端原生支持,一条命令就能把快照塞进对象存储,不用任何中转。

三、先搞懂 Restic 的几个核心概念

动手前把名词理顺,后面才不晕:

  • Repository(仓库):存放所有备份的远端目标,比如一个 S3 桶里的某个目录。一个仓库可以用同一个密码管理。
  • Snapshot(快照):一次备份产生的、某一时刻的完整状态,有唯一 ID 和时间戳。恢复时就是指定某个快照。
  • forget:根据保留策略"标记"哪些快照过期。光 forget 并不立刻释放空间。
  • prune:真正去仓库里删除那些被 forget 标记、且不再被任何快照引用的数据块,回收空间。

所以完整的清理动作是 restic forget ... --prune,forget 决定"哪些不要了",prune 负责"物理删除"。

四、实战一:安装与初始化仓库

在 Debian/Ubuntu 上直接装(仓库版本可能略旧,想要最新可用 restic self-update):

sudo apt update
sudo apt install -y restic

# 验证
restic version

接着把连接信息和密码放进一个只有 root 能读的 env 文件,避免密码写进 shell 历史。以 Cloudflare R2 为例(S3 兼容):

cat > /root/.restic-env <<'EOF'
export AWS_ACCESS_KEY_ID="你的R2_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="你的R2_SECRET_KEY"
export RESTIC_REPOSITORY="s3:https://你的账户ID.r2.cloudflarestorage.com/vps-backup"
export RESTIC_PASSWORD="一段非常强的仓库密码,务必另外存一份"
EOF
chmod 600 /root/.restic-env

注意:RESTIC_PASSWORD 一定要另外找地方记一份(比如密码管理器)。如果 VPS 没了、密码也跟着没了,备份就永远解不开,等于白备。然后初始化仓库:

source /root/.restic-env
restic init

看到 "created restic repository" 就成功了。

五、实战二:编写每日静默备份脚本

核心动作是:① 先 dump 数据库(不能直接备份正在写的库文件);② 把 dump + 网页目录 + Nginx 配置一起 restic backup;③ restic forget --keep-within 30d --prune 清理 30 天前的旧快照。完整脚本如下,存为 /usr/local/bin/vps-backup.sh

#!/bin/bash
set -euo pipefail

# 载入仓库环境变量
source /root/.restic-env

LOG=/var/log/vps-backup.log
echo "[$(date '+%F %T')] 开始备份" >> "$LOG"

# 1) 导出 MySQL/MariaDB(--single-transaction 保证 InnoDB 一致性,不锁表)
mkdir -p /var/backups/mysql
mysqldump --single-transaction --routines --events   --all-databases > /var/backups/mysql/all-$(date +%F).sql 2>> "$LOG"

# 2) 加密增量备份:网页目录 + Nginx 配置 + 刚导出的 dump
restic backup /var/www /etc/nginx /var/backups/mysql   --tag daily   --exclude /var/www/*/cache   --exclude-caches >> "$LOG" 2>&1

# 3) 保留最近 30 天内的快照,其余 prune 掉
restic forget --keep-within 30d --prune >> "$LOG" 2>&1

# 4) 顺手清理本地临时 dump,省空间
rm -f /var/backups/mysql/all-$(date +%F).sql

echo "[$(date '+%F %T')] 备份完成" >> "$LOG"

给脚本加执行权限,再用 crontab 挂在每天凌晨 3 点跑:

chmod 700 /usr/local/bin/vps-backup.sh

# 编辑当前用户(建议 root)的定时任务
crontab -e
# 加入这一行:每天 03:00 执行,输出追加到日志
0 3 * * * /usr/local/bin/vps-backup.sh

如果你用的是 PostgreSQL,把第 1 步换成 pg_dumpall -U postgres > /var/backups/postgres/all.sql 即可。想顺便做个存活监控,可在脚本末尾加一行 curl -fsS https://hc-ping.com/你的UUID > /dev/null(用 Uptime Kuma / Healthchecks 这类心跳服务),备份没跑就报警。

六、实战三:Cloudflare R2 与 AWS S3 后端怎么配

两者都是 S3 兼容协议,Restic 配法几乎一致,区别只在 RESTIC_REPOSITORY 的地址和免费额度:

  • Cloudflare R2:S3 兼容、出流量(egress)永久免费,这对"备份要常恢复测试、会频繁下载"的场景简直是天选。免费额度是每月 10GB 存储、100 万次 A 类操作、1000 万次 B 类操作,新手基本用不完。仓库地址形如 s3:https://账户ID.r2.cloudflarestorage.com/桶名。它最大的卖点就是"恢复时不收流量费",所以强烈推荐做备份目的地。
  • AWS S3:生态最全,但免费额度只有 首年 12 个月:每月 5GB 标准存储、2 万次 GET、2000 次 PUT、100GB 出流量。超过就按 $0.023/GB/月 存储 + $0.09/GB 出流量计费,恢复大备份时流量费可能比存储费还贵。仓库地址形如 s3:s3.amazonaws.com/桶名/路径。适合已经深度用 AWS 的人。

命令里那一串密钥和仓库地址,最稳妥的就是放在 /root/.restic-envsource 进来(也就是题面说的 from-env 思路),别硬编码进脚本正文。另外提醒一句:对象存储桶本身也要开好"版本控制"或"防误删",否则极端情况下有人拿到密钥还能把你的备份也删了。

七、进阶:把备份做成真正可靠的防线

能跑通上面那套,你已经超过了 90% 的站长。但要做到"灾难真来了能救命",再加几层:

  • 一致性:数据库 dump 用 --single-transaction 已经能在不锁表的情况下拿到一致快照;更严格的场景(如 MyISAM 或要求绝对静止)可以在备份前停服或锁表,备份完再恢复。
  • 3-2-1 原则:至少 3 份数据副本、2 种不同介质、1 份异地。Restic 本身就能同时往本地磁盘和 R2/S3 两个仓库备份,一份本地(恢复快)、一份异地(防机房级灾难)。
  • 定期完整性校验:定期跑 restic check(建议每周一次,可用 --read-data-subset=5% 抽查部分数据,省时间),确认仓库没悄悄损坏。
  • 恢复演练:这是最常被跳过、却最关键的一步。"从没恢复过的备份,不算备份,只是个美好的假设。" 定期做 fire drill:restic snapshots 看列表,restic restore latest --target /tmp/restore-test 拉一份出来,校验数据库能导入、网页能打开。季度一次,雷打不动。

#VPS备份 #Restic #BorgBackup #增量备份 #加密备份 #CloudflareR2 #AWSS3 #mysqldump #灾备