如何用 Restic 将 VPS 数据备份到异地
在 Ubuntu 24.04 上使用 Restic 0.16.4,通过 SFTP 或 S3 加密去重备份 VPS,设置每晚 systemd 定时任务,并用恢复演练验证备份确实可用。
为什么同一台服务器上的备份不算备份
Restic 是一款免费开源的备份工具,可将文件的加密、去重快照发送到其他位置的仓库:第二台 VPS、家中的机器,或兼容 S3 的对象存储。本指南将在 Ubuntu 24.04 上完成配置,内容包括安装、通过 SFTP 创建仓库、执行首次备份、设置每晚运行的 systemd 定时器、配置保留策略,以及执行能够验证整个流程有效的恢复演练。目标位置必须是另一台机器,因为存放在同一台服务器上的副本会随服务器一起损坏。
服务器上的 backup/ 目录只能防止误删文件。它无法应对磁盘故障,因为副本也位于该磁盘上。它无法应对拥有 root 权限的攻击者,因为攻击者会先删除这些副本。它也无法应对误操作删除整个 VPS 的情况。全球效率最低的数据中心调侃过一个名为 backup_final_v2_REAL 的 tarball 与数据位于同一磁盘阵列上的情形。这个笑话之所以成立,是因为很多人确实做过完全相同的事。备份副本必须位于服务器之外,而 restic 是遵循这一原则时最省事的选择。
Restic 的四个核心概念
存储库。 Restic 写入数据的位置。它是采用 restic 自有格式的目录,其中包含大量加密 blob,只有 restic 能读取。不要手动编辑存储库;应通过 restic 命令和 -r 地址访问它。
快照。 备份文件在某个时间点的状态。每次备份都会创建一个快照,每个快照都可以单独恢复,并且每个快照都相当于该时刻数据的一份完整副本。
去重。 Restic 会将文件拆分为基于内容定义的分块,只上传存储库中尚未存在的分块。首次备份会上传全部数据;之后每次运行大致只上传发生变化的数据。假设每天晚上创建一个 20 GB 的快照,但只有 50 MB 发生变化,那么大约只需上传 50 MB。因此,保留数十个快照的成本很低。
默认加密。 Restic 存储库始终使用 AES-256 加密,每条命令都需要存储库密码。备份主机或存储提供商始终只能看到加密 blob。其严重后果是:如果丢失密码,数据将永久丢失,这是系统设计如此。请在不属于此服务器的位置保存一份密码副本。这个问题非常重要,下面还会再提到两次。
在 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(长期支持)版本会固定软件包版本,但这在此处没有影响:0.16.4 已支持本指南中的所有操作。如果您需要最新版本以获得速度提升,请从 restic 项目的 GitHub releases 页面下载官方单文件构建版本,使用 bunzip2 解压,并将其安装到 /usr/local/bin/restic;restic 安装不需要其他步骤。
在另一台服务器上通过 SFTP 创建存储库
您需要一台目标机器:通常使用第二台小型 VPS,任何运行 SSH 服务器且有空闲磁盘空间的主机都可以。Restic 支持 SFTP(通过 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 initinit之后的所有内容对这两种目标都相同。本指南的其余部分使用 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 timer 每晚运行
每次执行命令都输入仓库地址很快会变得繁琐,而手动执行的备份通常不到一个月就会停止。使用一个脚本和一个 timer 可以同时解决这两个问题。脚本设置 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.sh下一节和下下节会解释 forget 和 check 行。现在配置运行计划:使用一个执行脚本的 oneshot 服务,以及一个每天凌晨 03:00 触发该服务的 timer。这里使用 timer 优于 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启用 timer,然后手动运行一次服务并监控其执行情况:
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 会显示下一次运行的时间。您也可以生成这两个 unit 文件,而不是手动输入:
这两个文件背后的完整模式,包括日历语法以及服务可使用的加固指令,请参阅在 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 保留最近 7 天每天的一个快照,--keep-weekly 4 保留最近 4 周每周的一个快照,--keep-monthly 6 保留最近 6 个月每月的一个快照。未被任何规则保护的快照都会被 forget。
单独运行 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 备份中恢复文件?
运行 restic snapshots 查找快照 ID,然后运行 restic restore <id> --target /some/empty/dir 恢复该快照;添加 --include /path 可仅恢复其中一部分。latest 可替代 ID 使用。Restic 会在目标路径下重新创建原始目录结构,因此恢复 /etc/ssh 会生成 /some/empty/dir/etc/ssh。请在真正需要恢复之前先进行演练,因为未经测试的备份无法证明可用。
应多久运行一次 restic 备份?
对于服务器,每晚运行是合理的最低频率,而数据去重会降低成本:每次运行只上传自上次备份以来发生变化的数据块。变化频繁的数据,或丢失一天数据也会造成严重影响的数据,可以每隔几小时备份一次,使用相同的定时器模式。频率只是其中一半;还应定期运行 restic check,并每月进行一次恢复演练,因为没有验证的计划只能带来虚假的安全感。
如果丢失 restic 仓库密码,会发生什么?
备份将无法恢复。Restic 的加密没有后门,也不支持重置,因此密码与备份本身同样重要。请将密码副本保存在密码管理器中,以及其他持久可靠且不在备份服务器上的位置。在您仍能访问仓库时,可以使用 restic key add 为同一仓库注册第二个密码,作为备用密码。