用 Restic 给 VPS 做异地备份:SFTP 仓库、定时任务与恢复检查

用 Restic 和 SFTP 为 Linux VPS 建立异地备份,讲清仓库密码、数据库导出、systemd 定时任务、快照保留、数据检查与独立恢复的配置和边界。

把应用部署到土耳其|BRNCHOST · 土耳其 VDS
云服务器,积分可续期|雨云 · 国内外节点 · 积分兑换权益
低价年付,大流量 VPS|RackNerd · SSD 存储 · 1Gbps 端口
香港轻量,搭个小站|晚安云 · 香港云服务器
香港 VPS,大带宽可选|野草云 · BGP 直连
大陆访问,精品线路|搬瓦工 · CN2 GIA / CTGNet 套餐
资料归档,交给 AI 整理|WorkBuddy · 本地文件处理
建站起步,先看应用镜像|腾讯云 · 轻量应用服务器
CN2 GIA,中国方向优化|DMIT · Premium 网络
双 ISP 原生住宅 IP|丽萨主机 · 美国 9929 精品线路
高频 CPU,多地部署|Evoxt · 云服务器 · 每周异地备份
京东云轻量云主机:129元/年,新人专享,限购1台

网站每天都能打开,很容易让人觉得数据也一直安全。直到更新失败、目录误删,或者整台 VPS 无法登录,才发现所谓备份只是同一块磁盘上的一个压缩包。数据库有导出,上传图片却没保存;文件还在,恢复所需的密码和配置又找不到。

小网站的备份不必先变成一套复杂平台。把要保留的数据列清楚,放到另一台机器,定时运行,再确认能取回来,已经能解决很多实际问题。Restic 适合承担其中的文件备份部分:它支持加密仓库、快照和多种存储后端,可以通过 SFTP 使用一台已有 SSH 服务的机器。

下面以 Linux VPS 和独立备份主机为例,安排一套可以逐步落地的做法。文中的 site-a、主机地址和路径都需要替换成自己的配置。Restic 的当前稳定版发布页为 0.19.1;通过系统软件源安装时,实际版本可能不同,应先查看版本,再对照相应文档。

先确定要恢复什么

不要从“把整个根目录都备份”开始。先想服务器损坏以后,哪些东西无法简单重新下载。

对于一个 Docker 网站,常见对象包括 Compose 配置、应用环境配置、上传文件、数据库导出,以及重新部署时需要的少量说明。容器镜像通常可以重新取得,运行时缓存往往可以重建,数据库的数据目录则需要按数据库自己的方式处理。

可以先整理成这样的清单:

数据 示例位置 备份方式
应用配置 /srv/apps/site-a/config 文件备份,限制访问
用户上传 /srv/apps/site-a/uploads 文件备份,考虑写入一致性
PostgreSQL 数据 数据库内部 先导出,再交给 Restic
部署说明 配置目录中的文字文件 随配置一起保留
临时缓存 缓存目录 确认可重建后不纳入

这只是清单示例。若图片放在对象存储,数据库里可能只有图片地址,备份数据库并不会带走那些对象。若某些应用把数据放在 Docker 命名卷里,也要查清卷的位置与数据性质,不能因为已经保存了 Compose 文件,就认为数据也包含在内。

同一站点的数据库与文件也可能需要协调时间。有人上传一张图片时,数据库和文件目录分别发生变化,先后备份可能产生短暂差异。数据要求严格的服务,应采用应用支持的导出、短暂维护窗口或相应快照方案,不能把普通文件扫描当作全站事务快照。

准备一台独立的备份主机

业务主机记为 vps-site-a,备份主机在 SSH 配置里使用别名 backupbox。两台机器应有独立存储。把仓库放在业务主机另一目录,无法应对整台主机丢失。

备份主机需要能通过 SSH/SFTP 访问,并为备份账号准备可写目录,例如 /srv/restic/site-a。账号只需拥有相应仓库的访问权,不需要为了传输备份拿到业务主机的全部管理权限。

在业务主机负责运行备份的那个系统用户下,配置 SSH:

Host backupbox
    HostName backup.example.net
    User backup
    Port 22
    IdentityFile /root/.ssh/restic_backup
    IdentitiesOnly yes
    BatchMode yes
    ServerAliveInterval 60
    ServerAliveCountMax 10

这里假设任务以 root 运行,因此私钥位于 root 的目录。使用其他用户时,应同步调整路径、文件权限和后续任务用户。首次建立连接时,应核对备份主机的主机密钥并记录到对应用户的 known_hosts,不要用关闭主机密钥检查来掩盖连接问题。

Restic 的 SFTP 后端可以利用这个别名。自动任务还要求 SSH 连接不依赖交互输入密码,否则定时启动后可能一直等待。必要的 SSH 测试应使用与定时任务相同的用户;你自己的登录账号能连上,不说明 root 的任务也能连上。

Restic 官方文档中的 SFTP 仓库配置

官方 SFTP 文档展示了仓库地址写法,以及使用 SSH 配置别名的方法。

安装后先确认版本与路径

Debian、Ubuntu 可以从软件源安装:

sudo apt-get update
sudo apt-get install restic
restic version
command -v restic

软件源的版本可能落后于上游。若需要指定版本,可以按官方安装说明选择对应操作系统与 CPU 架构的发布文件,并核对校验信息。不要把另一台机器上的二进制文件直接复制过来,却忽略架构不同。

记录实际可执行文件路径。手动安装常见于 /usr/local/bin,系统包可能在 /usr/bin。交互终端的 PATH 与定时任务环境未必一致,所以“终端里能运行”只是第一步。

Restic 本身不常驻运行,也没有内置的定时调度器。后面的定时安排由 systemd 或 cron 负责,Restic 在每次被调用时执行一次任务。

仓库密码与 SSH 密钥是两回事

SSH 密钥用于连接备份主机,Restic 仓库密码用于解密仓库。连得上备份机器,不代表能读出备份内容;仓库密码丢失,文件仍在也无法恢复。

创建只允许任务用户读取的配置目录:

sudo install -d -m 700 /etc/restic

在 /etc/restic/site-a.password 中保存一行独立的强密码,将文件权限设为 600。密码应另有受控副本,例如保存在自己的密码管理器中。不要让它的唯一副本留在要保护的那台 VPS 上。

创建 /etc/restic/site-a.env:

RESTIC_REPOSITORY=sftp:backupbox:/srv/restic/site-a
RESTIC_PASSWORD_FILE=/etc/restic/site-a.password

这个配置文件也设为 600。仓库地址不是公开下载链接,仓库目录也不应由 Web 服务对外提供。

手动运行前,把这两项配置加载到当前 shell。下面示例在 root 的 Bash 中执行:

set -a
. /etc/restic/site-a.env
set +a
restic init

init 用于第一次创建仓库,不应该塞进每天重复执行的脚本。以后查看或写入都使用同一个仓库地址与密码。仓库准备文档说明了 SFTP 地址和密码文件的用法。

配置里要保留清楚的主机、站点与路径关系。多个站点各用独立仓库,更容易限制权限和安排保留策略;共用仓库也可以,但需要更谨慎地设置标签与筛选条件。

先把 PostgreSQL 导出成可检查的文件

运行中的数据库目录不能像普通上传目录一样随手复制。PostgreSQL 提供逻辑导出工具 pg_dump,可以在数据库继续运行时得到一致的单库导出。它不包含整个集群的全部角色和全局对象,这些内容需要另外处理。

对于数据量较小、能接受按次导出的站点,可以先把数据库导出到专用目录,再由 Restic 上传。数据量大、需要很短恢复窗口或时间点恢复的生产数据库,则应考虑数据库物理备份与 WAL 等方案,不能只靠每日导出解决所有需求。

下面假设数据库客户端已经安装,连接信息通过 PostgreSQL 支持的环境变量或受控密码文件配置,示例数据库名为 site_a:

set -euo pipefail
install -d -m 700 /srv/backup-input/site-a
umask 077
pg_dump -Fc -f /srv/backup-input/site-a/database.dump.tmp site_a
pg_restore -l /srv/backup-input/site-a/database.dump.tmp >/dev/null
mv /srv/backup-input/site-a/database.dump.tmp \
   /srv/backup-input/site-a/database.dump

先写临时文件,导出成功并能读取归档目录后,再替换正式导出文件,可以避免失败的半份文件覆盖上一份。实际放进脚本时,要在错误发生后停止执行。

pg_restore -l 只是检查归档目录能否读取,不等于已经在新数据库里恢复成功。恢复演练仍然需要独立数据库,并核查结构与关键数据。

Docker 或 1Panel 中的数据库,还要按实际容器、网络和账号调用导出。不要照着别人的容器名运行。数据库客户端也应与服务端版本兼容,尤其不能用过旧的 pg_dump 去导出更新的服务端。具体限制见 PostgreSQL 的 pg_dump 文档。

第一次备份,把路径写清楚

在已加载仓库环境的 shell 中运行:

restic backup \
  /srv/apps/site-a/config \
  /srv/apps/site-a/uploads \
  /srv/backup-input/site-a \
  --host vps-site-a \
  --tag site-a

这里没有直接扫描整个 /srv,因为仓库、缓存、日志与临时文件可能混在其中。明确列出源路径,后续比较快照也更容易。

第一次备份之后查看记录:

restic snapshots --host vps-site-a --tag site-a

确认路径、主机名和标签与计划一致,再把输出里的快照 ID 用于后面的恢复检查。快照时间存在,并不证明全部文件都成功读取,仍然要看备份命令的退出状态。

Restic 的备份文档说明:退出码 0 表示成功;1 表示致命错误;3 表示部分源文件无法读取,可能已经创建不完整快照。自动化中不能只检查“有新快照”就报成功。

也不要以为第二次备份一定传输整个目录。Restic 会利用内容去重,但实际占用和传输量取决于数据变化。数据库导出文件、压缩包和大量改动文件都可能影响结果,不适合先承诺固定的节省比例。

把导出和上传放进同一个脚本

脚本还需要 Bash、flock、pg_dump 与 pg_restore;应先确认这些命令在任务环境中可用。保存为 /usr/local/sbin/site-a-backup,并将权限设为 700:

#!/usr/bin/env bash
set -euo pipefail
umask 077

exec 9>/run/lock/site-a-backup.lock
if ! flock -n 9; then
  echo 'Another site-a backup job is running' >&2
  exit 1
fi

install -d -m 700 /srv/backup-input/site-a
archive=/srv/backup-input/site-a/database.dump
pg_dump -Fc -f "${archive}.tmp" site_a
pg_restore -l "${archive}.tmp" >/dev/null
mv "${archive}.tmp" "$archive"

restic backup \
  /srv/apps/site-a/config \
  /srv/apps/site-a/uploads \
  /srv/backup-input/site-a \
  --host vps-site-a \
  --tag site-a

这个脚本假设 pg_dump 的连接与认证已为任务用户配置好,数据库也确实叫 site_a。它没有自动发现数据库,也没有创建备份账号。正式使用前,要把这些前提与目录逐一对上。

脚本用本机文件锁避免两次同类任务同时导出,也依赖错误退出让数据库导出失败时不继续上传。Restic 自身的仓库锁仍然保留,不要为了省事加上跳过锁的参数。

若想直接把数据库输出传给 Restic,可研究 --stdin-from-command。它能根据子命令失败退出处理任务;简单的管道加 --stdin 则可能收下一份不完整输出。对第一次建立备份流程的站长,先落盘、检查、再上传更容易排查。

用 systemd 安排每天执行

创建 /etc/systemd/system/site-a-backup.service:

[Unit]
Description=Back up site-a with restic
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=root
Environment=PATH=/usr/local/bin:/usr/bin:/bin
EnvironmentFile=/etc/restic/site-a.env
ExecStart=/usr/local/sbin/site-a-backup

再创建同名 timer:

[Unit]
Description=Daily site-a backup

[Timer]
OnCalendar=*-*-* 03:20:00
RandomizedDelaySec=10m
Persistent=true

[Install]
WantedBy=timers.target

这里的时间按服务器当前时区理解,应先确认时区是否符合自己的安排。随机延迟用于错开任务,不保证刚好 03:20 开始。Persistent=true 可以在机器恢复运行后补触发错过的日历任务,但不会把停机期间的每一天分别补成独立数据快照。相关行为见 systemd timer 文档。

加载并先手动运行一次,再启用 timer:

sudo systemctl daemon-reload
sudo systemctl start site-a-backup.service
sudo systemctl status site-a-backup.service
sudo journalctl -u site-a-backup.service -n 100 --no-pager
sudo systemctl enable --now site-a-backup.timer
sudo systemctl list-timers site-a-backup.timer

以上命令是部署步骤。实际使用时,手动执行失败就先处理原因,不要继续启用定时器。Timer 存在只说明安排已经建立,日志和快照才说明任务是否运行。

失败通知可以通过自己的监控工具或 systemd 的失败处理机制接入,通知中记录任务名与错误摘要即可,不要把密码、私钥或整份环境文件发出去。

保留策略先预览,再删除

备份越积越多,需要保留策略,但没有必要第一天就把清理自动化。先积累可恢复的快照,再预览哪些会被删除:

restic forget \
  --host vps-site-a \
  --tag site-a \
  --group-by host,paths \
  --keep-daily 7 \
  --keep-weekly 4 \
  --keep-monthly 6 \
  --dry-run

这是一份示例策略,实际数量要根据数据变化、容量和恢复需求决定。按天、周、月保留的是相应分组中符合策略的快照,不是承诺存在每一个自然日的备份。某一天任务失败,保留策略无法替你生成那天的数据。

检查预览的主机、路径与删除列表,确认范围正确后,再执行去掉 --dry-run 的命令。forget 删除快照引用,prune 才回收不再被引用的数据;首次清理可以分开进行。prune 还可能需要重写数据与额外空间,因此不要等磁盘完全填满才处理。

Restic 官方文档中的快照保留策略

官方文档列出了保留选项,并说明 --dry-run 可以只查看计划,不删除快照。

完整规则与容量限制可以看快照清理文档。若后来采用只追加仓库,保留策略与删除权限还需要重新设计,不能直接套用普通 SFTP 仓库的定时清理任务。

仓库检查与恢复演练分别做

先做结构检查:

restic check

默认检查不会把全部数据包重新读一遍。需要检查实际数据时,可以安排:

restic check --read-data-subset=10%

它随机抽取一部分数据包,反复抽查也不保证覆盖全部。需要完整数据检查时使用 restic check --read-data,但要考虑时间与网络流量。具体区别见仓库检查说明。

即便检查全部通过,也还需要知道怎么恢复。先选定快照,而不是在共享仓库中直接使用含义不清的 latest:

restic snapshots --host vps-site-a --tag site-a
restic ls SNAPSHOT_ID
restic restore SNAPSHOT_ID --target /srv/restore-check/site-a

SNAPSHOT_ID 要替换为实际记录。目标应是独立目录,预留足够容量。绝对路径通常会在恢复目标下重建相应目录层级,应查看恢复出来的实际结构,不要直接覆盖正在使用的应用目录。

恢复后检查配置与上传文件,再把数据库导出恢复到独立的测试数据库。打开应用并核查关键数据,才能发现缺少对象存储文件、角色配置或外部依赖的问题。数据库归档能列目录与网站真正恢复,是两种不同的结果。

更有价值的一次演练,是换到另一台机器,仅凭记录的仓库地址、密码副本和 SSH 配置恢复。它能暴露“所有恢复资料都放在原机上”这种平时看不出来的问题。

SFTP 备份的边界要记住

加密能保护仓库内容,但不能阻止持有仓库写权限的人删除文件。普通 SFTP 账号如果能修改整个仓库,业务主机被攻破后,备份也可能被影响。需要更强保护时,应研究独立删除权限、只追加后端或另一份隔离副本,不能把加密称为不可删除。

备份主机也会出现空间不足、网络故障和账号失效。除了看任务是否成功,还要看最近一次完整快照的时间、容量趋势与恢复资料是否仍然有效。

每天备份也意味着可能丢失最后一次成功备份之后的变更。如果站点不能接受这样的数据损失,就需要提高频率,或采用数据库的持续归档方案。恢复时间同样受数据量、网络和部署复杂度影响,不宜先给出固定分钟数。

对一个小网站,第一版可以很清楚:配置与上传目录备份,数据库先导出,仓库放在独立主机,每天运行并记录失败,每月做一次独立恢复检查。先让这套流程持续可靠,再增加新的后端和更复杂的策略,比同时上线很多从没恢复过的备份任务更踏实。