密码短语只存在于那台已经报废的机器上
仓库完整、已加密,且永远无法被读取。在第一次快照之前,把密码短语——以及 Borg 导出的密钥——存进密码管理器,并留一份纸质记录。

VPS 加密备份指南:六个步骤,带你从一台毫无防护的服务器,走到第二法域内另一台机器上通过验证的加密仓库——对比 restic 与 Borg,可干净恢复的数据库转储,一个 systemd 定时任务,以及一个即使服务器被攻破也无法抹除的仅追加仓库。第二台服务器每月只需 $3.90。已在 Debian 12 上测试。
盘点
无法重建的部分
安装
restic 还是 Borg
目标
第二座堡垒
初始化
仓库 + 首次运行
自动化
转储、定时任务、保留策略
演练
恢复,并计时
几乎每一场关于备份的争论,都在第一分钟就跑偏了,因为对话双方脑子里想的是不同的失效场景。一个人想的是硬盘报废,另一个人想的是某个周二早上账户突然没了。这两种情况需要不同的答案,而一个只扛得住第一种的方案,会一直感觉很安全,直到被第二种情况真正考验为止。
有四种值得专门设计应对方案的失效模式,它们并非同一主题的变体——每一种都会击溃一种不同的防御手段。
硬件。 NVMe 故障、宿主节点宕机、阵列同时损失两个成员。这是人人都会为之做准备的失效场景,也是现代虚拟化基础设施上最罕见的一种。冗余存储能应对它。除此之外冗余存储什么也应对不了,这正是 RAID 从来都不是备份的原因:它会忠实地复制每一次写入,包括那条写着「删除一切」的写入。
人为。 在生产环境上跑错的迁移脚本。在错误的终端里执行的 DROP TABLE。参数写反的 rsync。这是目前为止造成真实数据丢失最常见的原因,而唯一的防御手段是历史记录——一份保存在错误发生之前、且错误没有波及到的副本。关键的成分是时间:如果唯一的副本只是一个落后三十秒的镜像,它早已包含了这个错误。
恶意。 有人拿到 root 权限,或者勒索软件通过一个未打补丁的应用侵入。现代勒索软件做的第一件事就是寻找备份配置——凭据、挂载的共享、云端密钥——并将找到的一切摧毁,原因很简单:一次有效的恢复能把一场灾难变成一个下午就能解决的小事。一个能删除数据的备份凭据,就是攻击者继承来的凭据。本指南中关于仅追加的章节,正是为应对这种失效而存在的。
托管方。 没有任何东西损坏,也没有遭到任何攻击;你只是失去了访问权限。一次由自动欺诈信号触发的封号、一次出差途中银行卡扣款失败、一场纠纷、一次合规审查、一份送达服务商的法律命令。机器完好无损,却无法访问,而该账户里的每一份快照也随之一起无法访问。这种失效,无论在同一家服务商内部堆砌多少冗余都无法应对——这正是一份严肃的备份必须放在另一个屋檐下的原因。
| 防御 | 硬件 | 人为失误 | 勒索软件 | 账户丢失 |
|---|---|---|---|---|
| RAID / 冗余存储 | 是 | 无 | 无 | 无 |
| 服务商快照,同一账户 | 是 | 部分 | 无 | 无 |
| 异地仓库,可写密钥 | 是 | 是 | 无 | 是 |
| 异地仓库,仅追加,另一个法域 | 是 | 是 | 是 | 是 |
最后一行正是本指南接下来要搭建的目标。它并不比上一行更贵——仅追加设置是免费的,第二个法域的费用和第一个一样,同样是 $3.90。
3-2-1 法则,针对单台 VPS 重新表述。 三份数据副本,存放在两种不同的存储介质上,其中一份放在异地。这个经典表述来自磁带和办公室服务器的时代,其精神比字面表述更经得起翻译。对于一台自托管的机器,它的意思是:VPS 上的在线数据,另一台机器上的一个仓库,再加上——对于真正无可替代的东西——一份你能亲手掌握的第三份副本,放在家里的硬盘上,或者抽屉里。真正重要的数字并不是「三」,而是需要多少个相互独立的环节同时出错,数据才会真正丢失。「二」是值得拥有的最低限度。
你需要的这一类工具叫做去重加密快照工具。它把每个文件切分成按内容划定的分块,在拥有数据的机器上对每个分块加密,无论多少个快照引用它,都只存储一次。这样一来你同时获得三个特性:几乎只需一份数据的空间就能拥有大量恢复点、明文永不离开源端、传输只搬运发生变化的部分。
这个领域由两款程序主导,两者都很出色。restic 是一个用 Go 编写的单一静态二进制文件,支持广泛的存储后端。BorgBackup 是一个 Python 程序,通过 SSH 与远端另一份自身副本通信。下面是它们之间真实存在的差异。
| 属性 | restic | BorgBackup | rsync / rclone |
|---|---|---|---|
| 客户端加密 | 始终启用,无法关闭 | 始终启用,无法关闭 | 仅通过 rclone crypt 远程 |
| 去重 | 基于内容的分块 | 基于内容的分块 | 无,或使用硬链接树 |
| 快照历史 | 可以,配合保留策略 | 可以,配合保留策略 | 此刻的镜像 |
| 目标端要求 | 不需要——SFTP、S3、REST、rclone 均可 | 两端都要安装 Borg | 一个 SSH 账户,或一个 API |
| 强制仅追加 | 通过 rest-server 或对象锁定 | 内置支持,基于普通 SSH | 无 |
| 适用场景 | 几乎所有人,多数存储后端 | 一台你自己拥有的 SSH 主机,仅追加 | 镜像,不是备份 |
rsync 之所以出现在这张表里,是因为大多数人第一个想到的就是它,也因为它确实是错误的工具:镜像会忠实地复制一次删除操作,而且一块静态加密磁盘的镜像本身在任何地方都不是加密的。它在备份体系里应有的位置,是负责搬运一个已经完成的归档,而不是充当归档本身。
凡是命令有差异的地方,本指南都会同时展示 restic 和 Borg 的写法,你选哪一个都能照着做。但凡需要二选一的地方——完整示例、定时任务、脚本——一律采用 restic,因为这样目标端只需要一个 SSH 账户,能让第二台机器保持简单、少折腾。
备份整个文件系统是一个说得过去的选择,但也是一种浪费。服务器的大部分内容,只需一条包管理器命令就能重新生成,而你多背负的每一 GB,都会让仓库清理变慢、存储成本变高、在你急需时搜索也更慢。真正有用的做法正好相反:列出一份全新安装无法帮你找回的东西清单。
对于一台典型的自托管 VPS,这份清单并不长,而且总是包含以下这五样东西:
把这份清单在源服务器上变成两个文件。包含列表让意图一目了然;排除列表则把噪音挡在外面。两者都会被备份命令读取,而且两者本身也都应该被纳入备份。
/etc/backup/include.txt
/etc
/opt
/root
/var/backups
/var/lib/docker/volumes
/home
/etc/backup/exclude.txt
# Caches and rebuildable artefacts
**/node_modules
**/.cache
**/*.tmp
/var/lib/docker/volumes/*/_data/cache
# Log noise — keep the config, drop the volume
/var/log/journal
# Never back up the mount point you restore into
/mnt/restore
关于那一行 Docker 命令,有两点需要说明,因为很容易让人栽跟头。命名卷位于 /var/lib/docker/volumes 之下,这正是你需要的;绑定挂载则位于你自己指定的位置,通常就在 Compose 文件旁边,被 /opt 覆盖到。容器镜像不是数据——它们只需一条 pull 命令就能找回,把它们也纳入备份,只是在白白搬运用不上的若干 GB。
最后,在围绕这个数字搭建任何东西之前,先把它测出来。这个数字会告诉你该订哪一档仓库,也会告诉你第一次运行是要四分钟还是四十分钟:
du -sh --exclude=/var/lib/docker/overlay2 /etc /opt /root /home /var/lib/docker/volumes
在拥有数据的那台机器上安装工具。Debian 和 Ubuntu 都对两者提供了软件包,而 restic 的发行版软件包往往会落后一两个版本——这一点很重要,因为仓库特性和性能改进都会体现在小版本更新里。先安装软件包,再让 restic 原地自我更新:
apt update && apt install -y restic
restic self-update
restic version
对于 Borg,用发行版自带的软件包是正确的选择,因为源端和目标端的版本必须能够互相理解,而来自同一个发行版版本的匹配组合,是达成这一点最省事的方式:
apt install -y borgbackup
borg --version
在把多年历史托付给一个仓库之前,先说一句关于大版本的话:Borg 的仓库格式在大版本之间发生过变化,把仓库迁移跨越那道分界线是一次转换,而不是一次升级。安装前先读一遍对应版本的发行说明,并让目标端和源端保持在同一个大版本上。restic 在各个版本间一直保持向前兼容,新特性也是通过明确的仓库版本号来启用的,所以这方面的顾虑要小得多。
这两款工具都不需要在源端跑守护进程、代理,也不需要开放端口。这一点值得明说:你安装的这套备份系统,不会给它所保护的机器增加任何监听服务,也不会带来任何新的攻击面。它做的一切都是按计划、经 SSH 向外发起的。
仓库主机只有一项任务,几乎没有别的要求。它不需要核心数,因为它从不读取你的数据——它保存的是自己也打不开的加密数据块。它不需要内存,因为去重工作是在源端完成的。它需要的是磁盘、一个稳定的地址,以及一个源机器的麻烦不会波及到的地方。
在控制面板中:Order → VPS → Sentinel,镜像选 Debian 12,以及——这才是重点——选一个与源机器所在堡垒不同的堡垒。Helsinki 负责运行,Reykjavík 负责保存。开户不需要邮箱地址,账单可用 Monero、Bitcoin、Lightning 或其他支持的资产结算,这意味着第二台机器不会重新暴露第一台机器一直小心保护的身份信息。
| 仓库目标 | 按月 | 存储 | 所容纳的工作集 | 恢复成本 |
|---|---|---|---|---|
| Sentinel | $3.90 | 120 GB NVMe | ~30 GB | 不需要——不限流量 |
| Garrison | $7.90 | 240 GB NVMe | ~70 GB | 不需要——不限流量 |
| Ravelin | $16.90 | 480 GB NVMe | ~150 GB | 不需要——不限流量 |
| Bulwark | $32.90 | 960 GB NVMe | ~300 GB | 不需要——不限流量 |
| S3 级对象存储 | ~$0.023/GB | 弹性 | 任意 | 出站流量计费 |
「工作集」这一列假设的是正常变化频率下、保留一年的每日快照;如果仓库主要存放变化缓慢的文件,实际能撑得更久,如果里面全是被反复重写的大型二进制文件,则撑不了那么久。最后一行是为了体现规模,也是为了提醒一个人们总要到倒霉那天才想起的细节:对象存储的下载同样要收费,而下载正是你在紧急情况下要做的事。
在目标端——一个无特权用户,加一个目录。
adduser --disabled-password --gecos "" backup
install -d -m 0700 -o backup -g backup /srv/restic/web01
备份用户没有密码、没有 sudo 权限,也没有任何可登录的入口。它存在的唯一目的就是拥有一个目录。如果你要备份多台源机器,就给每台机器分配各自独立的目录——每个源对应一个仓库,能确保一台机器被攻破不会波及另一台的历史记录。
在源端——一把不做他用的密钥。
ssh-keygen -t ed25519 -f /root/.ssh/id_backup -N "" -C "backup:web01"
cat /root/.ssh/id_backup.pub
不要复用你的登录密钥。这把密钥会以未加密的形式留在磁盘上,因为凌晨三点要由一个无人值守的定时任务使用它——这正是你需要把用途限定在单一目标、单一用途上的那种密钥。
回到目标端——限制这把密钥能做的事情。 将公钥粘贴到 /home/backup/.ssh/authorized_keys 中,并在其前面加上一个前缀。对于通过 SFTP 使用的 restic:
restrict,from="198.51.100.7" ssh-ed25519 AAAAC3NzaC1lZDI1... backup:web01
restrict 选项一次性关闭端口转发、代理转发、X11 和 PTY 分配,只留下文件传输功能,别无其他。from= 子句将密钥锁定在源地址上,因此即使这把密钥从服务器上被窃取,在其他地方也毫无用处。对于 Borg,可以更进一步,强制指定命令,仅追加模式正是由此实现的:
command="borg serve --append-only --restrict-to-path /srv/borg/web01",restrict ssh-ed25519 AAAAC3NzaC1lZDI1... borg:web01
这一行就是整套勒索软件防御的核心:无论源端发来什么,目标端始终只会运行 borg serve,只在那个路径内,也只以追加模式运行。源端就算拿到 root shell,也无法用这把密钥抹掉上个月的数据。
在源端把目标地址命名一次就够了。 将其写入 /root/.ssh/config,这样之后的每条命令都会很短,地址也只存在于一个文件中:
Host rkv-repo
HostName 198.51.100.42
User backup
IdentityFile /root/.ssh/id_backup
IdentitiesOnly yes
然后手动连接一次——ssh rkv-repo——以接受主机密钥。无人值守的定时任务不会回答指纹确认提示,第一次备份悄无声息地卡住一整周是个经典案例。趁你还在目标端,就把它当作你拥有的任何其他机器一样对待:在它上面也运行一遍首小时加固清单。它保存着一切的副本,值得花上完整的一小时。
生成一个你永远不需要手动输入的密码短语。它会由脚本从文件中读取,所以长度不花一分钱代价,也没有理由让它好记:
openssl rand -base64 32 > /root/.restic-pass
chmod 600 /root/.restic-pass
cat /root/.restic-pass
现在先停下来,把这串字符抄到这台服务器以外的地方。 密码管理器、抽屉里的一张纸条、第二台机器——任何一个在源机器丢失后依然存在的地方。这正是摧毁自托管密码库和自动化引擎的同一种失效:解开一切的密钥就存放在一切旁边,两者会一起丢失。一个密码短语只存在于它所保护的那台机器上的仓库,算不上备份;它只是一堆整整齐齐的噪音。
写好这份环境文件,之后的每条命令都会读取它:
/etc/backup/restic.env
RESTIC_REPOSITORY=sftp:rkv-repo:/srv/restic/web01
RESTIC_PASSWORD_FILE=/root/.restic-pass
chmod 600 /etc/backup/restic.env
set -a; . /etc/backup/restic.env; set +a
restic init
Borg 对应的一对命令——repokey 模式将加密密钥存储在仓库内部,由密码短语保护,这样很方便,但也正因如此你才需要额外导出一份副本:
export BORG_REPO=ssh://borg@rkv-repo/srv/borg/web01
export BORG_PASSCOMMAND="cat /root/.restic-pass"
borg init --encryption=repokey-blake2
borg key export :: /root/borg-key.txt # copy this off the server too
第一次快照。 手动运行它,全程观察,等它跑完再谈自动化。这是最慢的一次——因为每个分块都是全新的——也正是这一次运行会告诉你包含列表是否正确:
restic backup \
--files-from /etc/backup/include.txt \
--exclude-file /etc/backup/exclude.txt \
--tag nightly --one-file-system --verbose
如果源端正在对外提供服务,而上传占满了链路,就给它限速。这个参数以 KiB/s 为单位,所以 20000 大约相当于 20 MB/s:
restic backup --limit-upload 20000 --files-from /etc/backup/include.txt
运行结束后,看看你实际得到了什么。以下这三条命令,现在就该运行一遍,六个月后——等你早就不再惦记这件事的时候——再运行一遍:
restic snapshots # the list, with dates and tags
restic stats latest # what the newest snapshot contains
restic ls latest /etc/backup # confirm a path you expected is in there
第三条命令值得停下来看一眼。半数出问题的备份其实根本没坏——它们是完整、健康的仓库,只是备份错了目录。在第一次快照时就检查一下你预期会看到的路径,能在包含列表还记忆犹新时及时发现这个问题。
数据库规则,只讲一次。 一个正在运行的数据库引擎把状态保存在内存中,并按自己的节奏写入磁盘。在它这样做的同时读取它的文件,你得到的会是来自好几个不同时刻的一组页面——这样的文件可能可以恢复,可能恢复后是损坏的,也可能恢复出一个能正常打开、却悄悄缺了最后一小时数据的东西。从外部根本无法分辨会是哪一种。所以备份从不触碰在线文件:先把转储写入磁盘,备份再去取这份转储。
所有内容都写进一个脚本。把它放在 /usr/local/sbin/nb-backup.sh,赋予可执行权限,并且把这个脚本本身也纳入备份:
#!/bin/sh
set -eu
# ---- 1. Consistent dumps, written where the include list will find them.
install -d -m 0700 /var/backups/db
# PostgreSQL in a container:
docker exec -t app-db pg_dumpall -U postgres | gzip -9 > /var/backups/db/postgres.sql.gz
# MariaDB / MySQL, InnoDB tables:
# docker exec -t mail-db mariadb-dump --single-transaction --all-databases \
# | gzip -9 > /var/backups/db/mariadb.sql.gz
# SQLite — never a plain file copy:
# sqlite3 /opt/app/data/app.db ".backup '/var/backups/db/app.db'"
# ---- 2. The snapshot.
restic backup \
--files-from /etc/backup/include.txt \
--exclude-file /etc/backup/exclude.txt \
--tag nightly --one-file-system
# ---- 3. Retention, then reclaim the space it freed.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
# ---- 4. Structural check every night; read 5% of the data on Sundays.
restic check
[ "$(date +%u)" = "7" ] && restic check --read-data-subset=5%
echo "backup ok: $(date -u +%FT%TZ)"
那段脚本里有三个细节,起的作用比看上去要大。开头的 set -eu 意味着一次失败的转储会直接中止运行,而不是悄无声息地在接下来六个月里一直快照昨天那份旧转储。forget 和 prune 合并成一条命令,因为一个从不释放空间的保留策略,最终只会是一块被填满的磁盘。而周日的检查读取的是实际数据的一部分,而不只是索引——仓库损坏很少见,而且是无声的,唯一能发现它的办法就是去读取数据。
定时任务。 两个小小的 unit 文件。这里应该用 systemd 而不是 cron 作调度器,因为定时器能给你 Persistent 选项——机器关机期间错过的一次运行,会在下次开机时补上,而不是悄悄被跳过——而且 journalctl 会保留每一次运行的历史记录。
/etc/systemd/system/nb-backup.service
[Unit]
Description=Nightly encrypted offsite backup
Wants=network-online.target
After=network-online.target docker.service
[Service]
Type=oneshot
EnvironmentFile=/etc/backup/restic.env
ExecStart=/usr/local/sbin/nb-backup.sh
Nice=10
IOSchedulingClass=idle
/etc/systemd/system/nb-backup.timer
[Unit]
Description=Nightly encrypted offsite backup
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=45m
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now nb-backup.timer
systemctl list-timers nb-backup.timer
systemctl start nb-backup.service && journalctl -u nb-backup.service -n 40 --no-pager
随机延迟不是摆设。互联网上几乎每台自托管服务器都恰好在 03:00 执行维护任务,如果备份和日志轮转、证书续期、容器更新在同一秒启动,整晚就会为争夺 I/O 而互相拖累。把启动时间分散在四十五分钟内不花一分钱,却能消除一整类莫名其妙的慢夜。
然后,让失败不再无声无息。 这是人人都会跳过的一步,却决定了前面做的一切是否有意义。一个停止工作的备份不会发出任何提示:定时任务照常触发,服务照常运行失败,最后一个可用快照渐渐成为过去,而仪表盘依然显示一片绿色。给这个服务加上一个 OnFailure= 单元,让它真正把消息发到你能看见的地方,并且每月扫一眼快照列表的最上面几条。一行配置,一个习惯。
restic snapshots --latest 3 # is the newest one from last night?
systemctl list-timers nb-backup.timer # is the timer still armed?
有两种演练,回答的是不同的问题。小演练问的是「数据是不是真的在里面、能不能读出来?」,只需要九十秒。大演练问的是「要让服务真正恢复到底需要多久?」,每年花一个下午做一次。小演练每月做一次,大演练至少做一次,因为答案从来都不是人们猜的那样。
小演练。 将一个目录和一份转储拉取到临时路径,然后与线上版本进行比对:
install -d -m 0700 /mnt/restore
restic restore latest --target /mnt/restore --include /etc/backup
restic restore latest --target /mnt/restore --include /var/backups/db
diff -r /etc/backup /mnt/restore/etc/backup && echo "config identical"
gzip -t /mnt/restore/var/backups/db/postgres.sql.gz && echo "dump readable"
如果工具支持,还有更好的办法:把仓库以只读方式挂载,像浏览文件系统一样浏览它。每个快照都会显示为一个带日期的目录,你可以直接用 ls 和 cat 浏览历史,完全不用真正恢复任何东西。执行挂载的机器上需要有 FUSE:
restic mount /mnt/snapshots # then: ls /mnt/snapshots/snapshots/latest/
borg mount :: /mnt/snapshots # the Borg equivalent
大演练。 按小时租用一台临时 VPS,仅凭仓库本身在其上重建服务——不靠记忆中的笔记,不从在线主机复制任何文件。按照书面操作手册进行,并在过程中修正手册,因为你发现的缺口正是这场演练的意义所在。按出现频率排列的常见发现:密码短语只存在于已报废的机器上;DNS 记录从未被记录下来;某个绑定挂载的目录落在所有包含路径之外;恢复需要一个已不再是默认版本的软件包;没有人知道哪个容器必须先启动。
给它计时。把这个数字连同日期写在操作手册的最上面。这个数字——而不是仓库的大小,也不是快照的数量——才是衡量这个服务到底受到多少保护的唯一诚实标准。
偶尔也要读一读数据本身。 每夜的检查只验证结构:索引本身是否一致、有没有缺失的数据块。它不会读取数据块内容。每月一次,或者在周日那次运行时,读取其中一部分——轮流抽查百分之五,几个月下来就能覆盖整个仓库,而且不会占用整晚的时间:
restic check --read-data-subset=5%
borg check --verify-data # the Borg equivalent, slower and thorough
到目前为止的一切,防的都是意外。这一章要防的是对手,而这个设计问题一旦看清楚就会让人不太舒服:源机器每晚都要连上仓库,这意味着源机器上保存着一份能用的仓库凭据。谁拿下了源机器,谁就拿到了这份凭据。如果这份凭据能删除数据,攻击者就会删除——现代勒索软件正是故意这么做的,而且是在加密任何东西之前先做,因为一个能恢复的受害者是不会付钱的。
解决办法不是用更强的密码,而是一种非对称权限:源端可以新增数据,但不能删除任何数据。有三种实现方式,按上手难度从易到难排列。
第一种——通过 SSH 强制仅追加写入。 这是 Borg 的主场,也是在你自己拥有的机器上能得到的最干净的答案。第 03 步中 authorized_keys 里的那一项强制执行 borg serve --append-only,因此无论源端提出什么请求,目标端都会拒绝删除。清理仍然需要进行,但要从目标端发起,按源端无法影响的计划执行,由源端触及不到的账户运行。攻击者即便在源端拿到了 root 权限,也只能往仓库里塞垃圾数据;他们无法删除攻击发生前一晚的归档。
第二种——仅追加的 REST 端点。 restic 配套的 rest-server 也有一个 --append-only 模式,性质相同:接受新的数据块,但无法删除已有的。把它运行在目标端并置于 TLS 之后,将 RESTIC_REPOSITORY 指向 https:// 地址而不是 sftp: 地址,防御的形态就完全一样了。代价是目标端多跑一个服务,换来的是相同的保障。
第三种——对象锁定。 在 S3 兼容存储上,版本控制加上对象锁定保留期,能在保留期到期之前让删除操作彻底无法执行,这是由存储层强制保证的,而不是靠某个程序。这是三种方式里最强、也最贵的一种,而且它会引入一个服务商账户——这又带回了本指南通篇都在设法分散的托管方风险。作为第三份副本,它很合理;作为唯一的一份,就显得别扭了。
如果以上都不适用——只有普通 SFTP,一把能写入、因此也能删除的密钥——就不要自欺欺人;另外加一份源端完全碰不到的独立副本。用拉取代替推送:让目标端登录到源端,读取所需内容并保存下来。凭据这时就保存在没有暴露的那台机器上,攻击者即便拿下了源端,也找不到任何指向备份的线索。搭建起来会多花一点功夫,但恰好把风险扭转到了正确的方向。
无论你选择哪一种,都要用验证任何其他安全控制手段的方式去验证它——尝试把它攻破。从源端,用备份密钥,尝试执行一次删除。正确的结果应该是被拒绝。
borg delete ::name-of-an-old-archive
# → Remote: Repository is in append-only mode. Refusing to delete.
每个服务都有一小块既不是数据、也不是配置的状态,丢失它不会损坏服务本身——而是会把它换成另一个恰好同名的服务。旧链接失效,联邦网络中的对端拒绝新身份,客户端要重新生成密钥。以下是本站已有指南涉及的各个技术栈中,那些不可或缺的部分。
数据目录、config/config.php,以及一份数据库转储。做转储前打开维护模式,做完后再关闭——否则 Nextcloud 会同时向两者写入数据。
整个数据目录:db.sqlite3 要用 .backup 命令而不是直接复制来获取,此外还有 rsa_key 文件、附件和发送记录。
签名密钥、homeserver.yaml、媒体存储,以及一份 PostgreSQL 转储。丢失签名密钥,联邦身份就永久性地没了。
邮件存储本身、虚拟用户表,以及 DKIM 私钥——换一把新的 DKIM 密钥,意味着在 DNS 更新跟上之前,所有邮件都将处于未签名状态。
先要 N8N_ENCRYPTION_KEY,再要 PostgreSQL 转储。没有这把密钥的数据库,只是一份每条凭据都无法读取的工作流列表。
隐藏服务目录。这几个文件就是 .onion 地址——没有它们,服务恢复后地址就会变成另一个,所有指向它的链接都会失效。
完整的 /etc/wireguard。体积很小,也很枯燥,却是五分钟重建和给你所有设备重新生成密钥之间的区别。
种子和通道状态,按节点自身的方式处理。恢复过时的通道数据可能让你损失资金——请遵循具体实现的备份流程,而不是简单地复制文件。
问大多数人为什么备份要放在别处,得到的答案是火灾、水灾,或者数据中心断电。这些都对,也都越来越罕见,而且只需要 100 公里的物理距离就能应对。真正在 2026 年拖垮企业的失效,是行政性的,仅靠物理距离对此无能为力。
一个账户,就是一个单点故障。 一次自动欺诈信号触发的封号,一次发生在你坐飞机时的拒付,一场合规审查,一份寄给公司而非某台机器的下架通知——这些情况无一例外会在同一时刻波及该账户下的每一台服务器,包括保存着快照的那一台。技术上的冗余再完美,也与此无关。两家服务商,或者至少两个分属不同法律主体的账户,才是唯一能应对这种情况的结构。
一个法域,就是一套法律框架。 北欧四大堡垒并非可以互换——瑞典、芬兰、挪威和冰岛各自保护的重点略有不同,而挪威和冰岛完全不在欧盟范围内。把工作机器和它的仓库分置在其中两地,意味着副本不只是换了个地方,而是处于另一套独立的规则之下。北欧法域参考逐条梳理了相关法规。
而目标端对此一无所知。 正因如此,这种拆分是划算的,而不是一种取舍。restic 和 Borg 都会在数据离开源端之前完成加密,所以仓库主机存储的是它没有密钥可以打开的数据块。它不知道自己保存了哪些文件、有多少个文件,也不知道文件叫什么名字。你不是在把信任交给第二方,而是在租用一块连自己都读不了的磁盘。这也是「我该不该用一个我并不完全信任的服务商作为备份目标?」这个问题最诚实的答案——对一个加密仓库来说,信任几乎无关紧要。
延迟并不是问题。 Helsinki 到 Reykjavík 骨干网延迟是 30 ms,Helsinki 到 Stockholm 是 8 ms,Stockholm 到 Oslo 是 11 ms。每晚一次的备份根本不在乎这些数字,受限于磁盘吞吐量的恢复过程也一样不在乎。选这对组合要看法律距离,而不是网络距离——在北欧范围内,网络距离几乎可以忽略不计。
还有一件事值得直说,因为这正是本指南会出现在这个网站上、而不是出现在一个普通系统管理博客上的原因:第二台机器会把第一台机器已经解决过的每一个问题重新打开一遍。如果工作用的 VPS 是无需身份证件、用 Monero 支付订购的,而它的备份机却是用公司信用卡加护照扫描件订购的,那么这一对机器的可识别程度,就等同于其中较弱的那一半。匿名 VPS 托管主题指南详细讲解了三个层面——注册、付款、网络——它们对备份目标的适用程度,和对一台 Web 服务器完全一样。
仓库完整、已加密,且永远无法被读取。在第一次快照之前,把密码短语——以及 Borg 导出的密钥——存进密码管理器,并留一份纸质记录。
复制的是在线数据文件,而不是转储。要在备份前的步骤里先写出一份转储,再备份这份转储;永远不要让包含列表直接指向一个正在运行的引擎的数据目录。
一台被攻破的机器上的可写凭据,就是攻击者手里的可写凭据。要么在目标端强制仅追加,要么用拉取而非推送的方式获取第二份副本。
只执行 forget 而不执行 prune,只是给快照打上标记,并不会释放任何空间。把两者一起运行,在目标端保留可用空间,并且永远不要备份容器镜像或 node_modules。
定时任务在一次升级后失败了,却没有任何提示。添加一个 OnFailure= 单元,并且每月检查一次快照列表的最上面几条——只需要十秒钟。
一个位于 /opt 之外的绑定挂载,一次迁移中被移动的卷,一条匹配范围超出预期的排除规则。在每次新快照中列出一个已知路径,并在每次改动技术栈后重新读一遍包含列表文件。
搭建过程中会遇到的十个问题——以及只会在事后才想到一次的另外两个。
不算——它是一种回滚手段,而且很有用,但恰恰在备份真正派上用场的那些时刻会失效。快照和它所复制的服务器存放在同一个服务商账户里。如果账户被封,如果付款出现纠纷,如果客服人员手滑删错了东西,或者攻击者拿到了你的面板凭据,快照会和机器一起没了。它也做不到任何精细恢复:你无法从昨天单独取出一个被删除的文件,只能把整块磁盘回滚,丢掉这之后的一切。留着快照——它是撤销一次糟糕升级最快的办法——但不要把它和一份存放在原问题波及不到的地方的副本混为一谈。经验法则是:如果一份被攻破的凭据能同时摧毁两份副本,那你其实只有一份副本。
两者都在客户端加密,都按分块去重,都足够成熟,都能胜任这项工作。如果你想要一个静态二进制文件、一个可以放在 SFTP、S3 兼容对象存储、REST 服务器或任何 rclone 能连接到的地方的仓库,并且目标端完全不用安装任何东西,就选 restic。如果目标端是一台你自己掌控的 SSH 主机,你想要在不使用对象锁定存储的前提下获得最强的仅追加保障,并且喜欢它略高一些的压缩率,就选 Borg。真正决定取舍的实际差异是:Borg 需要两端都安装 borg,且只支持 SSH 或本地路径;restic 需要执行一次清理,会短暂对仓库加独占锁。如果实在拿不定主意,就用 restic——目标端零件越少,比任何跑分都更值钱。
先从你实际要备份的数据大小算起——而不是它所在磁盘的大小。一套典型的自托管方案通常只有几 GB 的数据库和配置,外加用户上传的内容。去重和压缩接下来会发挥作用:一个 20 GB 工作集、保留十二个月的每日快照,仓库体积通常落在 40 到 80 GB 之间,因为只有发生变化的分块才会被重复存储。一台 Sentinel($3.90/月,120 GB NVMe)足以从容应对。当多台服务器备份到同一个仓库主机时,升级到 Garrison(240 GB,$7.90);当工作集本身达到数百 GB 时,升级到 Bulwark(960 GB,$32.90)。按仓库所需空间估算,然后再翻一倍——一个没有可用空间的仓库无法执行清理,而一个无法清理的仓库只会越长越大。
你可以把它们复制下来,但未必能把它们恢复回去。一个数据库在你读取的同时还在往磁盘写入,给你的会是一组来自多个不同时刻的文件,这正是「撕裂式备份」的定义——恢复出来的要么是一个损坏的数据库,要么更糟,是一个能正常打开、却悄悄缺了若干行数据的数据库。解决办法是做转储:PostgreSQL 用 pg_dump 或 pg_dumpall,MariaDB 和使用 InnoDB 表的 MySQL 用 mariadb-dump --single-transaction,SQLite 用 sqlite3 db .backup out.db 或 VACUUM INTO。把转储写入一个文件,然后再备份这个文件。如果数据库大到无法每晚转储,另外的办法是在存储引擎短暂静默期间做一次文件系统快照,或者使用引擎自带的物理备份工具——但对单台 VPS 运行的任何东西来说,转储都是正确且简单的选择。
备份就没了。restic 和 Borg 都在客户端加密,两者都没有恢复途径、没有主密钥,也没有哪张工单能帮你解锁仓库。这正是重点所在:目标主机——即便不是你自己的主机——永远看不到你的明文数据。这也意味着密码短语现在是一份价值等同于它所保护的一切的数据,绝不能只存在被备份的那台机器上。把它存进你的密码管理器,再留一份实体副本。对于 Borg,还要用 borg key export 导出仓库密钥并妥善保存;在 repokey 模式下,密钥就存放在仓库内部,因此一旦仓库无法访问,密钥也会随之一起丢失。
你要给它一个只能新增、不能删除的凭据。这是整个方案中最重要的一个设计决策,因为现代勒索软件首先寻找的正是备份配置,并会顺藤摸瓜找上门来。实现方式有三种。通过 SSH 仅追加:在目标端的 authorized_keys 中强制指定命令 borg serve --append-only --restrict-to-path /srv/borg——源端可以写入新的归档,但无法删除旧的。通过 REST 仅追加:用 --append-only 运行 restic 的 rest-server,并让仓库通过 HTTPS 指向它。对象锁定:一个启用了版本控制和保留锁定的 S3 兼容存储桶。普通的 SFTP 完全做不到这些——一把能写入仓库的密钥同样能抹掉仓库——所以如果你用 SFTP 作为传输方式,就应该再搭配一份从目标端发起的拉取式第二副本,让攻击者从源端窃取的凭据无法触及它。
反过来问会更清楚:你愿意重新做多少工作?这个数字就是你的恢复点目标,它决定了备份间隔。每晚一次,几乎适合所有自托管服务——邮件服务器、Nextcloud、Matrix homeserver、工作流引擎。当数据是事务性的、且无法重新录入时,每小时一次才值得。至于保留策略,真正经得起现实考验的模式是 --keep-daily 7 --keep-weekly 4 --keep-monthly 12:一周内的精细撤销、一个月的每周检查点、一年的每月检查点,总共约 23 份快照。这条长尾比人们想的更重要,因为它捕捉到的失效不是硬盘报废,而是一次持续六周都没人发现的损坏或删除。
不同建筑物是技术上的最低要求;不同法域才是大多数人会跳过、之后又追悔莫及的部分。火灾、水灾或机架级故障,靠物理距离就能应对。物理距离应对不了的是法律和商业层面的问题:某家服务商封锁账户、一次付款纠纷、一份波及同一家公司名下所有机器的下架通知、一份送达某家公司的法律命令。如果两份副本都放在同一家服务商那里,一封信就能同时波及两者。把这对副本分散到两座北欧堡垒——工作机放在 Helsinki,仓库放在 Reykjavík,骨干网上相距 30 ms——在延迟上不花一分钱代价,却换来第二套独立的法律框架。无论如何,数据在离开源端之前就已经加密,所以目标端保存它也不会因此了解到任何内容。
加密不是瓶颈——现代 CPU 运行 AES 的速度,比千兆链路能承载的速度还快,而且两款工具在硬件支持的情况下都会使用硬件加速。真正的瓶颈是第一次上传,因为那时一切都是全新的:在 1 Gbps 的上行链路上,一个 20 GB 的工作集按线速传输只需几分钟,如果目标端被限速,或者文件数量多且体积小,耗时会更长。之后每一次运行只会传输发生变化的分块,对典型的技术栈来说通常只有几十 MB,一分钟以内就能完成。如果第一次运行会与生产负载抢资源,就给它限速——restic 用 --limit-upload(单位 KiB/s),Borg 用 --upload-ratelimit——并且用 nice 和 ionice 运行它。
是的,原因不是过度谨慎,而是常见的失效都是无声的。一次软件包升级后就停止触发的定时任务。悄悄吞掉上传目录的排除规则。写到了备份从未覆盖的路径上的数据库转储。已经连续数周未通过完整性检查、却没人去看那份日志的仓库。这些问题都不会主动告诉你;但只要恢复一个文件并检查一下,九十秒内就能全部发现。每月做一次小型恢复——把一份数据库转储和一个数据目录拉到临时路径并做差异对比——每年在一台全新的 VPS 上做一次完整重建,并计时。那次重建跑出来的数字,才是你真正的恢复时间,而且几乎从来不是你猜的那个数字。
Sentinel——每月 $3.90,120 GB NVMe,位于 Helsinki、Stockholm、Oslo 或 Reykjavík。带宽不计费,恢复不花一分钱。注册无需邮箱,无需身份证件,目标端也读不到它所存内容的任何一个字节。
最后审核 · 2026-08-24 · 来源 · restic 与 BorgBackup 官方文档、PostgreSQL 与 MariaDB 备份手册、sshd authorized_keys(5)、systemd.timer(5) · 周期 · 每年
This guide is one spoke of a larger series. The pillar walks the three privacy layers end to end — the sibling spokes below dive into the specifics.
Three independent layers — signup, payment, network — explained, legal context included, common mistakes flagged.
SSH keys, ufw, fail2ban, kernel knobs, unattended-upgrades.
Deploy your own MCP server on a no-KYC VPS — TLS, streamable HTTP, OAuth.
Host an MCP server with no ID — the privacy stack, crypto-paid.
把代理从笔记本电脑搬走 — 规格选择、systemd、密钥、支出上限。