Restic 备份:把 VPS 数据备份到另一台机器
Restic 把 VPS 上的文件加密、去重后,通过每晚的 systemd 定时任务备份到另一台服务器或兼容 S3 的对象存储。本指南从 Ubuntu 24.04 上的安装一路讲到保留策略,并用一次真实的恢复演练证明备份确实能还原。
为什么把备份放在同一台服务器上不算备份
Restic 是一款免费、开源的备份工具,它把您文件的加密、去重后的快照发送到别处的一个仓库:第二台 VPS、家里的一台机器,或兼容 S3 的对象存储。本指南在 Ubuntu 24.04 上把它配置起来,从安装,到通过 SFTP 连接一个仓库、第一次备份、每晚的 systemd 定时任务、保留策略,再到证明整套流程有效的恢复演练。目标必须是另一台机器,因为和服务器放在一起的副本会随服务器一起消失。
在被备份的机器上放一个 backup/ 目录,只能防住一件事:手滑删掉某个文件。它挺不过磁盘损坏,因为它就在那块磁盘上。它挺不过拿到 root 权限的攻击者,因为对方会先删掉这些副本。它也挺不过把 VPS 整个删掉的账号误操作。全世界效率最低的数据中心拿一个名叫 backup_final_v2_REAL、和数据放在同一组磁盘阵列上的 tarball 开玩笑,这个玩笑之所以扎心,是因为我们中太多人真的干过这事。放到机器之外才是规矩,而 restic 是遵守这条规矩最省心的方式。
Restic 的四个核心概念
仓库(repository)。 restic 写入数据的地方。它是一个采用 restic 自有格式的目录,里面全是加密的数据块,只有 restic 能读。您永远不要手动去改它;您通过 restic 命令和 -r 地址来和它打交道。
快照(snapshot)。 您所备份文件在某个时间点的一张画面。每次备份运行都会创建一个快照,每个快照都能单独恢复,每个快照的表现都像那一刻数据的一份完整副本。
去重(deduplication)。 Restic 把文件切成由内容决定边界的数据块,只上传仓库还没见过的块。第一次备份会上传所有内容;之后每一次运行大致只上传变化的部分。对一个 20 GB、其中改动了 50 MB 的目录做每晚快照,成本大约就是 50 MB,这正是保留几十个快照很便宜的原因。
默认加密。 restic 仓库始终是加密的(AES-256),每条命令都需要仓库密码。备份主机或存储提供商看到的永远只是加密的数据块。这带来一个硬性后果:丢了密码,数据就永久没了,这是设计使然。请把密码的副本保存在这台服务器以外的地方。这一点重要到下文还会再提两次。
在 Ubuntu 24.04 上安装 restic
sudo apt update && sudo apt install -y restic
restic version在 Ubuntu 24.04 上,这会安装 restic 0.16.4,而当前的上游发布版本是 0.19.1。差距的存在是因为 LTS(long term support,长期支持)版本会冻结它的软件包版本,而这在这里无关紧要:0.16.4 能完成本指南里的所有操作。如果您想要最新版本带来的速度提升,可以从 restic 项目的 GitHub releases 页面下载官方的单一二进制构建,用 bunzip2 解压,再把它安装到 /usr/local/bin/restic;restic 的安装除此之外别无他物。
通过 SFTP 在另一台服务器上创建仓库
您需要一台目标机器:常见的答案是第二台小型 VPS,任何带有 SSH 服务和多余磁盘的机器都行。Restic 使用 SFTP(file transfer over SSH,基于 SSH 的文件传输)通信,所以备份主机上完全不需要装任何东西。在本指南中,备份主机是 10.0.0.12,上面有一个名为 restic 的用户。不要把那个用户命名为 backup:Ubuntu 和 Debian 在每次安装时都会自带一个名为 backup 的保留系统账号(uid 34,没有登录 shell),所以 adduser backup 会失败,而 ssh backup@... 会落进 nologin。
每晚的任务会在被备份的服务器上以 root 身份运行,所以 root 需要能用密钥登录到备份主机。创建一个不带密码短语的专用密钥,因为凌晨 3 点没有人在场去输入它,然后把它复制过去:
sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N "" -C "web1-restic"
sudo ssh-copy-id -i /root/.ssh/id_ed25519.pub restic@10.0.0.12
sudo ssh restic@10.0.0.12 true && echo key login works如果密钥对您来说还很陌生,SSH 密钥管理基础讲解了它的原理、权限,以及之后如何吊销一个密钥。
接下来是仓库密码。生成一个足够强的密码,写入一个仅 root 可读的文件:
openssl rand -base64 32 | sudo tee /root/.restic-password
sudo chmod 600 /root/.restic-password现在,在继续任何后续步骤之前,先把那个密码复制到您的密码管理器里。如果这台 VPS 挂了,仓库加上这个密码能把一切找回来;只有仓库、没有密码则什么也找不回来。
初始化仓库:
sudo restic -r sftp:restic@10.0.0.12:/srv/restic/web1 --password-file /root/.restic-password initcreated restic repository 9f3c2a1b0d at sftp:restic@10.0.0.12:/srv/restic/web1另一种目标是兼容 S3 的对象存储,当您不想再运行第二台机器时,这是正确的选择。任何兼容 S3 的存储桶用法都一样;只有地址和两个凭据变量会变:
export AWS_ACCESS_KEY_ID=your-key-id
export AWS_SECRET_ACCESS_KEY=your-secret-key
sudo -E restic -r s3:https://s3.example.com/web1-backups --password-file /root/.restic-password init在 init 之后,两种目标的所有操作都完全相同。本指南余下部分展示的是 SFTP 地址;请替换成您自己的。
第一次备份,带上排除项
要备份的是您无法重新安装的数据,而不是整个文件系统。操作系统重装一下就回来了;您的配置和数据不会。对一台典型的 VPS 来说,这意味着 /etc、/home,以及您的应用保存状态的任何地方,比如 /srv 或 /var/www。把缓存排除掉,因为它们体积大、每天都在变动,而且会自己重建:
sudo restic -r sftp:restic@10.0.0.12:/srv/restic/web1 --password-file /root/.restic-password backup /etc /home /srv --exclude '/home/*/.cache'Files: 4181 new, 0 changed, 0 unmodified
Added to the repository: 731.204 MiB (312.418 MiB stored)
snapshot 5b8a3f2c saved第一次运行会上传所有内容,所以要花点时间。再运行同一条命令,它几秒钟就能完成,报告只有几个文件改动、增加了几 MiB,因为去重只上传新的数据块。列出您已有的快照:
sudo restic -r sftp:restic@10.0.0.12:/srv/restic/web1 --password-file /root/.restic-password snapshots每个快照都会显示一个 ID、一个时间,以及它包含的路径。恢复时用的就是这些 ID。
用 systemd 定时任务每晚运行
每条命令都手打仓库地址会让人厌烦,而靠手动运行的备份,不出一个月就会停下来。这两个问题用一个脚本加一个定时任务就能解决。脚本会设置 restic 读取的两个环境变量 RESTIC_REPOSITORY 和 RESTIC_PASSWORD_FILE,这样脚本里的每条命令都能保持简短:
sudo nano /usr/local/bin/restic-backup.sh#!/usr/bin/env bash
set -euo pipefail
export RESTIC_REPOSITORY='sftp:restic@10.0.0.12:/srv/restic/web1'
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic backup /etc /home /srv --exclude '/home/*/.cache'
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic checksudo chmod 700 /usr/local/bin/restic-backup.shforget 和 check 这两行会在接下来的两节里讲解。现在来看调度:一个运行该脚本的 oneshot 服务,以及一个每晚 03:00 触发它的定时任务。在这里定时任务比 cron 行更好,因为运行会记录到 journal,而且 Persistent=true 会在服务器停机后一恢复就立刻补跑错过的备份。
# /etc/systemd/system/restic-backup.service
[Unit]
Description=Nightly restic backup
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run the nightly restic backup
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=15m
Persistent=true
[Install]
WantedBy=timers.target启用定时任务,然后手动运行一次该服务,看着它工作:
sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service -fsystemctl list-timers 会显示下一次运行何时触发。您也可以直接生成这两个单元文件,而不必手动敲:
这两个文件背后的完整模式,包括日历语法和一个服务可以携带的加固指令,都在在 VPS 上把程序作为 systemd 服务运行里。
备份在您恢复它之前只是传闻
请把这句话当成一条戒律。一个每晚都跑成功的备份任务,只能证明有个任务跑过了;它并不能证明您的数据能回来。两项检查能补上这个缺口。
第一项是 restic check,脚本已经每晚在跑。它会校验仓库结构和索引,这样备份主机上悄无声息的损坏会在第二天晚上被发现,而不是等到恢复那天。每月运行一次更深入的版本,它会下载并用密码学方式校验随机的十分之一的实际数据:
sudo -i
export RESTIC_REPOSITORY='sftp:restic@10.0.0.12:/srv/restic/web1'
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic check --read-data-subset=10%因为每次抽取的子集都是随机的,每月运行就能逐步遍历整个仓库,而不必付出一次完整下载的代价。
第二项是恢复演练。仍然在上面那个 root shell 里,把最新快照中的一个真实目录恢复到一个临时位置,再和线上文件做对比:
restic restore latest --target /srv/restore-drill --include /etc/ssh
diff -r /etc/ssh /srv/restore-drill/etc/sshdiff 什么都不打印,意味着每个字节都原样回来了,这是唯一算数的证据。之后把 /srv/restore-drill 删掉。这个演练每月做一次,每年再做一两次完整版本:把整个最新快照恢复到一台临时 VPS 上,检查您的应用真的能从中启动起来。当您需要它在压力之下顶用的那一天,您会希望它是一件您早已做惯的例行公事。
保留策略:forget 加 prune
没有策略,快照就会永远堆积,仓库只会越来越大。脚本的 forget 行每晚都会应用一条策略:--keep-daily 7 为最近七天每天保留一个快照,--keep-weekly 4 为四周每周保留一个,--keep-monthly 6 为六个月每月保留一个。没有被任何规则保护的都会被遗忘。
forget 本身只删除快照记录;数据块会留在仓库里,直到有什么东西去删它们。这正是 --prune 做的事:它找出没有任何剩余快照引用的数据块并删掉,这时磁盘空间才真正回来。Prune 会对仓库做实打实的工作,所以在大仓库上,有些人每晚跑 forget、每周跑一次 --prune;在典型的 VPS 规模下,每晚跑就行。
数据库:先导出,再备份导出的文件
Restic 是边读边复制文件的,而数据库会持续不断地写它的文件。一个在写入中途被捕获的活动数据库文件,恢复出来会是一个损坏的数据库,因为这份副本混合了某次写入之前和之后的页。修法很标准:让数据库引擎生成一个一致的导出文件,再让 restic 去备份那个文件。
对于 PostgreSQL,在 restic-backup.sh 顶部、restic backup 命令之前加一行导出命令,并把导出目录纳入备份路径:
mkdir -p /var/backups/db
sudo -u postgres pg_dump myapp | gzip > /var/backups/db/myapp.sql.gz对于 MariaDB 和 MySQL,mysqldump 承担同样的角色。想看整套模式的实战示例,Nextcloud 备份那一节会开启维护模式、导出 Postgres,并把文件作为一个一致的集合复制出来,这正是 restic 每晚应该带离机器的那份集合。SQLite 是同样的思路,只是用的锤子更小:Vaultwarden 指南会把容器停几秒,对 db.sqlite3 做一次冷复制,而那个归档就是 restic 送出服务器的东西。
FAQ
Restic 备份是加密的吗?
是的,始终加密。每个 restic 仓库都用 AES-256 加密,没有不加密的模式,每条命令都需要仓库密码。存放仓库的机器或提供商手里始终只有加密的数据块,所以备份主机即使被攻破也不会暴露您的文件。代价是绝对的:没有密码,任何人都无法恢复数据,所以请把密码的副本保存在服务器以外的地方。
Restic 做的是增量备份吗?
每个 restic 快照的表现都像一次完整备份,占用的却是增量的存储。Restic 把文件切成数据块,只上传仓库尚未存过的块,所以每晚运行大致只传输当天变化的部分。和传统的增量方案不同,这里没有需要回放的链条:任何快照都能直接恢复,删掉一个旧快照也绝不会破坏更新的快照。
如何从 restic 备份中恢复文件?
运行 restic snapshots 找到快照 ID,然后用 restic restore <id> --target /some/empty/dir 恢复它,加上 --include /path 可以只恢复其中一部分。latest 可以代替具体的 ID。Restic 会在目标目录下重建原始的目录结构,所以恢复 /etc/ssh 会落到 /some/empty/dir/etc/ssh。请在需要之前就练熟它,因为未经测试的备份只是传闻。
restic backup 应该多久运行一次?
对一台服务器来说,每晚一次是合理的下限,而去重让它很便宜:每次运行只上传自上次以来变化的数据块。变化很快、或者哪怕丢一天都很心疼的数据,可以用同样的定时任务模式每几个小时跑一次。频率是容易的那一半;也要定期运行 restic check,并每月做一次恢复演练,因为只有调度、没有验证是虚假的安心。
丢了 restic 仓库密码会怎样?
备份就再也无法恢复了。Restic 的加密没有后门,也没有重置,所以密码和备份本身一样重要。请在您的密码管理器里、以及被备份服务器以外任何持久的地方各留一份副本。趁您还能访问,restic key add 可以为同一个仓库注册第二个密码,给您留一个备用的。