我们的文档里只用一句话讲这件事:备份由你自己负责。对任何给你完整 root 权限的主机商来说,这都是老实话,也是大多数人读到这里就不再往下读的地方。真正有意思的,是这句话没说完的部分,因为给服务器做备份,其实不是该跑哪条命令的问题,而是第二份副本放在哪里、谁能销毁它,以及你每晚兢兢业业产出的那个东西,到底能不能真的还原成一台能用的机器。
大多数备份方案都会在这三关中的某一关栽跟头,而且几乎总是栽在同样的三处:副本和原件放在同一家服务商、同一个账户下;能写入备份的凭据同样能删除它;以及从来没有人用它做过恢复。这份指南把这三处都补上。它面向的是一台跑着真实业务的 Linux VPS — 一个网站、一个数据库、一套 Nextcloud、一个节点 — 由 restic 或 BorgBackup 来干活,加密在你自己的机器上、在任何数据离开之前完成,而另一端,是一台同样归你所有、位于另一个国家的廉价服务器。
为什么备份往往是保护最差的那份副本
人们往往把难的部分做完,却把容易的部分晾在一边。服务器做过一遍加固,用密钥代替密码登录,防火墙默认拒绝,也许还上了全盘加密,关机之后磁盘上的内容毫无意义。然后每天晚上,这块磁盘上所有内容的一份完整明文副本,被推送到一个几年前用银行卡开的存储账户里,放在一个没人认真想过的国家,由一个同时也存在浏览器里的密码守着。
副本值得和原件同样的重视,因为它就是原件,只不过没在运行而已。值得把你究竟在防什么说具体一些,因为几种情况的答案各不相同,而人们脑子里想到的,通常只有其中一种:
- 机器挂了。硬件、hypervisor,或者一个重启后变成只读的文件系统。少见,平淡,也是所有备份都能应对的情况。
- 你自己把它搞坏了。一次跑了一半的迁移、一条通配符写得过于豪迈的
rm、一处直到重启前都好好的配置改动。这是人们做恢复最常见的原因,没有之一。 - 别人拿到了 root。这是决定你整个设计的那种情况。想要筹码的攻击者会先删备份,再加密服务器;如果你的服务器上存着一个能抹掉备份仓库的凭据,那你搭建的就是一套帮他们干活的系统。
- 账户没了。一笔没到账的付款、一场争议、一次封停。如果原件和副本都在同一个登录之后,它们就共享同一个单点故障,而且这个故障跟技术无关。
- 有权力的人找上门来。硬件可能被收走,账户可能被冻结,靠的是程序而不是攻击。这时唯一管用的,是一份放在另一个司法辖区的副本,而且前提是你还能访问到它。
留意一下:由你自己的服务商、在你自己的账户下、在同一个数据中心里拍的快照,对上面这些情况里的多少种根本无能为力。
3-2-1 备份原则,以及后来加上的两位数字
这条原则来自摄影,而不是系统管理 — Peter Krogh 当初是为手里握着无法替代的底片的人写下它的 — 它能流传下来,是因为够短:数据保留三份,放在两种不同的系统上,其中一份在异地。每一位数字防的都是上面清单里不同的一项,这让它不只是一句口号。
三份应对的是普通的数据丢失:线上数据加两份备份,这样发现某份备份坏了,只是件烦心事,而不是世界末日。两种不同的系统应对的是关联故障 — 同一台主机、同一个阵列或同一个账户里的两份副本,不过是一份副本换了身伪装。而异地那份,应对的是一切发生在某个地点、而非某块磁盘上的事:火灾、查扣、封停,还有一个再也联系不上的服务商。
它的现代扩展版是 3-2-1-1-0,而在 2026 年真正要紧的,恰恰是多出来的这两位。多出来的那个 1,是一份不可篡改或离线的副本 — 一个拿着你凭据的攻击者也删不掉的东西,而这正是仅追加模式给你的。0 指的是校验零错误:一份你检查过的备份,而不只是一份你做过的备份。
放到一台小型 VPS 上,这条原则落地起来比听上去朴素得多。线上服务器是第一份。另一个国家第二台机器上的仓库是第二份:异地、不同系统,而且因为是仅追加,它同时也是那份不可篡改的副本。每月一次恢复到临时服务器上的完整恢复,就是那个 0。整条原则就这么满足了,代价只是多一台小服务器,而这份指南余下的部分,讲的就是具体怎么做。
restic 还是 Borg:真正起决定作用的差别
这两个工具做的是同样的四件事,而且都做得很好:按内容给数据分块,所以移动或重命名过的文件不会被重新上传;跨所有快照去重,所以一年的每日备份,占用的空间远比一年的完整拷贝少;压缩;以及 — 这里最要紧的一点 — 在客户端完成加密和认证,赶在任何一个字节进入网络之前。两种情况下,存储端保存的都是它读不懂、也无法在不被察觉的前提下篡改的密文。正因为有这个特性,这份指南对“谁来运营你的存储”没有立场。
真正的差别,都在另一端。
- restic 是一个静态编译的 Go 单文件程序,对目标端没有任何要求。它能写入 SFTP、S3 兼容的对象存储、它自己的 REST 服务器,或者任何
rclone能访问到的地方。加密用的是 AES-256 配合 Poly1305 认证器;仓库格式 2 加入了压缩。如果你的目标是一个存储桶,或者一台你没法装软件的机器,答案就是 restic,没什么好争的。 - Borg 要求两端都装上
borg,通过 SSH 和自己对话。作为交换,它提供了 restic 需要借助辅助程序才能做到的东西:一个真正由服务器强制执行的仅追加模式,通过远端账户authorized_keys里的command=选项锁定。客户端 — 包括已经被攻破的客户端 — 可以添加归档,却无法删除。对上一节的威胁模型而言,这是两个项目中最有价值的一项功能。
所以决定起来很简单。如果另一端归你控制,就用开启仅追加模式的 Borg。如果不归你控制,就用 restic,再通过在目标端运行 rest-server --append-only,或者使用开启了对象锁定的存储桶,把同样的保障找回来。两条路下面都会讲到。
在选定仓库之前还有一点要提醒:Borg 的命令接口在不同大版本之间并不稳定 — 下文用到的 repo::archive 简写属于 1.x 系列,到了 2.x 就变了 — 所以要拿 borg --version 去对照你正在读的文档,而不是反过来。restic 在这方面要稳定一些,代价是它自己也经历过一次仓库格式迁移。
有一件事不要做:以“两份备份总比一份安全”为由,两个都跑。两套维护得半吊子的系统,只会给你两个没验证过的仓库,外加一套在压力之下想不起来的恢复流程。选那个适合你另一端的工具,把省下的精力花在测试它上。
仓库放在哪里,以及谁有权删除它
上面这些都是标准建议。下面才是通常被略过的部分。
备份服务器的常规做法,是给它一个凭据,让它往外推送。而这个凭据几乎总是也能删除 — 因为清理旧快照需要删除权限,而发一把什么都能干的密钥更省事。后果就是:拿下你服务器的攻击者,也一并拿下了你的备份,而在你最需要备份的那个场景里,它恰恰一文不值。出路只有三条:
- 在目标端启用仅追加。不管客户端提出什么请求,另一端都只接收新数据、拒绝删除。
borg serve --append-only和rest-server --append-only都能做到,并且由服务器端强制执行,删除则变成你在备份主机上有意为之的操作。 - 对象锁定。S3 兼容存储可以让对象在保留期内不可更改。有效,只不过它把信任转移到了一家服务商和一段付费关系上。
- 用拉取代替推送。由备份主机主动连进来读取数据。生产服务器上的任何东西都碰不到仓库 — 但现在备份主机手里握着一把进入生产环境的钥匙,所以你只是把信任关系反转了,而不是消除了它。如果备份主机确实被锁得更严,这样做是合理的;否则并不见得更好。
仅追加这条路最便宜,前提条件也最少,所以下面搭建的就是它。
然后是放在哪里的问题。备份是你服务器的一份完整副本,它受所在地的法律管辖,挂在某个人名下的账户里。精挑细选了服务器的司法辖区,然后每晚把副本推送到另一个完全不相干地方的存储桶里,这是一种常见而又不声不响的错误 — 副本在哪个司法辖区,数据就在哪个司法辖区。客户端加密能在很大程度上缓解这一点:向你的存储服务商发出的调取要求,拿到的只是密文。它保护不了的是可用性,也掩盖不了仓库存在这一事实、它有多大,以及它最后一次变动是在什么时候。
正因如此,一次解决最多问题的方案,是第二台同样归你所有的小服务器,放在和第一台不同的国家,用和第一台相同的方式租下:不绑定任何身份,免实名,用 Monero 支付。两端都是你的,两端都便宜,传输从不经过第三方,而且无论原服务器所在的司法辖区或账户出了什么事,都波及不到这份副本。
给它选配置,比给一台真正干活的服务器选配置容易,因为备份目标说白了就是一块带着 CPU 的磁盘。它真正需要的,是足够的内存,好让清理(prune)和完整性检查跑起来不至于疯狂换页,这是唯一真正吃资源的环节 — 所以 1 GB 偏紧,2 GB 就宽裕了。对照套餐列表:
- Cub — 1 vCPU / 2 GB / 40 GB NVMe,$5.00/月。默认之选。一台普通的 Web 或应用服务器,线上数据 10–15 GB,保留一年的每日备份,放在这里绰绰有余,因为大部分工作都由去重和压缩完成了。
- Scout — 2 vCPU / 4 GB / 70 GB,$9.00/月。适合把几台服务器备份到同一个仓库,或者一套存着照片的 Nextcloud。
- Runner 和 Hunter — 100 GB 售 $14.00,140 GB 售 $19.00。邮件存储、媒体库或好几年的历史记录,最后都会落到这一档。
- Pup — 1 vCPU / 1 GB / 25 GB,$3.50/月。只备份配置和数据库转储的话够用。别把一棵庞大的文件树丢给它,还指望维护操作能一直顺顺当当。
磁盘容量有一条好用的经验法则:按单次完整备份大小的两到三倍来规划,你就能留住一年的历史,还有余地容你估错。把它放在一个不是原服务器所在地的地方 — 阿姆斯特丹、巴黎、布加勒斯特和索非亚都是基础价,雷克雅未克乘以 1.20,苏黎世乘以 1.35,也就是说一台放在瑞士的 Cub 每月 $6.75。首次上传是你唯一一次大流量传输,而且 1 Gbps 端口流量不限,所以初次填充仓库不会在月底给你带来一张账单。
分步操作
- 分清哪些东西不可替代,其余的列成清单
在碰任何工具之前,先花十分钟把服务器上的东西分成三堆。光这一步,就能省掉大部分成本,以及几乎所有的困惑。
不可替代 — 在别处都不存在的东西:数据库、上传的文件、应用状态、无法重新签发的 TLS 材料、钱包文件、SSH 主机密钥,以及整个
/etc,因为/etc是一份书面记录,记下了你做过、然后又忘掉的每一个决定。费点功夫就能重建 — 已安装的软件包、容器镜像,以及任何可以用脚本重建的东西,前提是脚本本身在第一堆里。
可以免费重新获取 — 软件包缓存、区块链数据、发行版安装介质、构建产物。这些永远不要备份。在一个节点上,这就是 3 GB 的仓库和 300 GB 的仓库之间的区别。
现在就把第三堆写成一个排除文件,这样从第一次运行起它就能生效:
cat > /etc/backup-excludes <<'EOF' /dev /proc /sys /run /tmp /var/tmp /var/cache /var/lib/apt/lists /var/lib/docker/overlay2 /swapfile /var/lib/bitcoind/blocks /var/lib/bitcoind/chainstate **/node_modules **/.cache EOFDocker 值得单独说一句。备份
/var/lib/docker是一种错误的直觉:那些镜像层既能重建,又体积庞大。要备份的是你的 compose 文件和命名卷 —/var/lib/docker/volumes— 镜像则让它们从镜像仓库重新拉回来。 - 搭建备份目标机,只让它干一件事
部署第二台服务器 — 默认选 Cub,Debian 13,放在和被备份服务器不同的国家。和其他任何机器一样,给它做一遍十分钟加固:只用密钥登录 SSH、禁用 root 登录、默认拒绝的防火墙。这台机器上存着另一台机器的完整副本,所以绝不是可以掉以轻心的地方。
然后创建一个用户,它存在的全部意义就是接收备份,没有交互式登录权限,也做不了其他任何事:
adduser --disabled-password --gecos '' --home /srv/backup --shell /bin/bash backup install -d -m 700 -o backup -g backup /srv/backup/.ssh apt install -y borgbackup # or: apt install -y restic这里的 shell 是特意设成真实 shell 的。SSH 强制命令是由该账户的登录 shell 来执行的,所以在这里设成
nologin— 这是人的本能反应 — 非但不会加强、反而会破坏你在第七步里要加上的限制。真正起约束作用的是那行command=,而不是 shell 字段。在生产服务器上生成一把专用于此、别无他用的密钥,这样以后撤销备份访问权限时,永远不必碰到你自己的登录方式:
ssh-keygen -t ed25519 -f /root/.ssh/id_backup -C 'backup@prod' -N ''把公钥复制到目标机的
/srv/backup/.ssh/authorized_keys里,属主设为backup,权限设为600。暂时让它保持为一条普通条目 — 等你确认整条链路都能跑通之后,第七步才会给它加上限制。 - 让数据库可以被安全地拷贝
有没有这一步,决定了你得到的是一份备份,还是一个看起来像备份的文件。数据库引擎把状态保存在内存里,并按它自己的顺序写入磁盘;从外部拷贝那个目录,抓到的是引擎从未打算让人看到的某个瞬间,恢复出来的东西要么启动不了 — 要么更糟,能启动,却藏着细微的错误。
先转储,并且安排在备份之前运行。对于 MariaDB 或 MySQL,
--single-transaction能在不把应用锁在门外的情况下,为 InnoDB 表生成一份一致的快照:install -d -m 700 /var/backups/db mariadb-dump --single-transaction --quick --all-databases \ | zstd -T0 > /var/backups/db/all-$(date +%F).sql.zst对于 PostgreSQL,以
postgres用户身份运行;pg_dumpall还会把角色一并导出,而很多人都是到了恢复那天才发现自己需要它们:su - postgres -c 'pg_dumpall' | zstd -T0 > /var/backups/db/pg-$(date +%F).sql.zstSQLite 有它自己的陷阱:在预写日志(WAL)仍处于活动状态时直接拷贝文件,可能抓到一个撕裂的中间状态。改为让 SQLite 库自己来做:
sqlite3 /var/lib/app/app.db ".backup '/var/backups/db/app.db'"磁盘上只保留最近几天的转储 — 保存历史是仓库的事 — 并且记住,
/var/backups/db现在包含了你数据库里的一切,所以权限700可不是摆设。如果你根本不想让明文转储落到磁盘上,两个工具都可以从管道读取:borg create ::db-{now} -配合--stdin-name,或者restic backup --stdin --stdin-filename。 - 初始化仓库,把口令存到服务器弄丢不了的地方
生成一个具有足够熵的口令,因为在仓库和任何拿到仓库的人之间,挡着的只有它:
openssl rand -base64 32在使用之前,先把它存进你的密码管理器,并抄在纸上。这份指南里的其他一切,犯了错都还能补救;唯独这一步不行。一个口令只存在于它所保护的那台服务器上的仓库,是你能做出来的最完整、也最无用的备份。
然后初始化。用 Borg 的话,通过 SSH 连接第二步创建的那个用户,选用带认证、密钥存放在仓库中的模式 — 并立刻把这把密钥导出到别处:
export BORG_REPO='ssh://[email protected]/srv/backup/prod' borg init --encryption=repokey-blake2 borg key export :: /root/borg-key.txt # then move it OFF this machine用 restic 的话,等价的做法是走 SFTP:
export RESTIC_REPOSITORY='sftp:[email protected]:/srv/backup/prod' export RESTIC_PASSWORD_FILE=/root/.restic-pass install -m 600 /dev/null /root/.restic-pass && nano /root/.restic-pass restic init没错,口令文件就放在生产服务器上 — 要让无人值守的任务跑起来,只能这样。也正因为如此,真正要紧的是密码管理器里和纸上的那两份,而接下来的几步,也正是为了让这台服务器无法销毁任何东西。
- 运行第一次备份,读懂它告诉你的东西
第一次运行是最慢的:一切都是新的,所以没有东西能去重,也没有任何缓存。在一个你能盯着看的终端里手动运行它,如果文件树很大,要做好等上一阵子的准备。
borg create --stats --progress --compression zstd,3 \ --exclude-from /etc/backup-excludes ::'prod-{now}' \ /etc /home /root /srv /var/www /var/lib/docker/volumes /var/backups/dbrestic backup --verbose --exclude-file /etc/backup-excludes \ /etc /home /root /srv /var/www /var/lib/docker/volumes /var/backups/db然后做那件几乎没人做的事,它只花两分钟,却能抓住首次尝试时的大部分错误 — 看看你实际备份下来的到底是什么:
borg list ::prod-... | less restic ls latest | less你要检查的是方向相反的两种失误。一种是漏掉了重要的东西,原因是路径写错了,或者某条排除规则比预想的更贪婪。另一种是混进了不该有的庞然大物 — 一个缓存目录、一条区块链、一堆日志归档 — 因为这决定了你的仓库是能在那块磁盘上安稳放上好几年,还是撑不到来年春天就把它塞满。趁仓库还小、重跑一遍不花什么成本,现在就把列表改好。
- 用 systemd 定时器实现自动化,而不是一行 cron
把整套流程 — 转储、备份、清理、汇报 — 放进一个脚本里,这样出了问题时,要读的东西只有一个:
install -m 700 /dev/null /usr/local/sbin/backup-run nano /usr/local/sbin/backup-run # set -eu; dumps; borg create; ping the monitor接着是一个 service 和一个 timer。在这里为 systemd 多写两个文件是值得的:退出状态会被记录下来,输出进入 journal,而不是进入一封没人看的邮件;
Persistent=true能把机器休眠期间错过的那次运行补上;而且失败可以直接触发一个告警单元。# /etc/systemd/system/backup.service [Unit] Description=Encrypted off-site backup OnFailure=backup-alert.service [Service] Type=oneshot ExecStart=/usr/local/sbin/backup-run Nice=10 IOSchedulingClass=idle# /etc/systemd/system/backup.timer [Unit] Description=Nightly encrypted backup [Timer] OnCalendar=daily RandomizedDelaySec=45m Persistent=true [Install] WantedBy=timers.targetsystemctl daemon-reload && systemctl enable --now backup.timer systemctl list-timers backup.timer journalctl -u backup.service -n 50Nice和IOSchedulingClass=idle能让每晚的备份在运行时不拖慢网站。RandomizedDelaySec比看上去更重要:没有它,你名下的每台机器都会在同一秒开始备份,这对目标机不友好,对你自己也不友好。 - 收回生产服务器的删除权限
现在来补上大多数方案都没补上的那个缺口。在备份目标机上编辑
/srv/backup/.ssh/authorized_keys,把生产服务器的密钥锁定到一条删不了任何东西的命令上:command="borg serve --append-only --restrict-to-path /srv/backup/prod",restrict ssh-ed25519 AAAA...这里有两个机制在协同工作。
--append-only让服务器拒绝一切删除请求,不管客户端怎么要求;--restrict-to-path则把它限制在自己的仓库里,这样一台被攻破的主机就碰不到其他主机的仓库。restrict选项会关闭端口转发、agent 转发以及其他所有功能,所以这把密钥除了这件事,什么都打不开。restic 的等价做法,是在目标机上运行它的 REST 服务器,由它来强制执行同样的规则:
rest-server --path /srv/backup --append-only --private-repos在生产服务器上认真测试一遍,方法就是故意去做那件被禁止的事:
borg delete ::prod-... # must fail borg create ::test-{now} /etc # must succeed如果删除成功了,说明
command=那一行没有生效 — 通常是文件里还留着第二步时加的另一把不受限制的密钥。从现在起,清理在目标机上进行,仓库在那里是本地的:在那边设一个每周运行borg prune和borg compact的定时器,生产服务器对此完全没有发言权。 - 动手恢复——然后把下一次演练写进日历
到这里为止的一切都只是准备。这一步,才让它真正成为一份备份。
先做完整性检查,它检验的是仓库自身的一致性。要抽样读取实际数据,而不只是元数据,因为只查元数据的检查,在无法恢复的仓库上照样能通过:
borg check --verify-data :: restic check --read-data-subset=10%然后做一次真正的恢复,恢复到一个临时目录,或者更好的是,恢复到一台新部署、用完就销毁的服务器上。挂载是浏览内容最快的方式;而解包,才是你凌晨三点真正要做的事:
mkdir /mnt/r && borg mount ::prod-... /mnt/r # browse borg extract ::prod-... etc/nginx # restore one path restic mount /mnt/r restic restore latest --target /tmp/restore --include /etc/nginx现在来做与文件无关的那部分。把数据库转储导入一个正在运行的数据库引擎,再让应用连上它打开看看。能解压的转储,不等于能导入的转储,而这两者之间的差距,已经葬送过不止一家公司:
zstd -dc /tmp/restore/var/backups/db/all-2026-09-03.sql.zst | mariadb记下整个过程花了多长时间,以及中途有哪些东西需要现查。这个数字才是你真实的恢复时间,而不是你想当然的那个;这些笔记则能让第二次演练快上很多。然后安排好下一次 — 每季度一次是合理的间隔,而一条日历提醒,比“打算要做”靠谱得多。
保留策略:为一个你还没察觉的问题留足历史
保留策略,是人们悄无声息地弄丢本来还在的数据的地方。保留七个每日快照,感觉挺负责,实际上只能防住你一周之内就发现的问题。静默损坏、被某个 bug 写成截断状态的数据库、四月删掉而六月才需要的文件、一个在做出任何可见动作之前先潜伏了很久的入侵者 — 这些都是在那七天早已轮换完之后很久才被发现的,到那时,每一个快照都忠实地保存着损坏后的状态。
标准做法是分层保留,而这几乎不花什么成本,因为有了去重,变化缓慢的数据做一个月度快照,占用的空间小到可以忽略不计:
restic forget --keep-daily 7 --keep-weekly 5 --keep-monthly 12 --keep-yearly 2 --pruneborg prune --keep-daily=7 --keep-weekly=5 --keep-monthly=12 --keep-yearly=2 ::
borg compact有两个机制上的细节值得了解,因为它们都让人吃过一惊。在 restic 里,forget 只是把快照从索引中移除,并不回收任何空间;真正删除数据的是 prune 这一步,所以命令里才带着这个参数。在 Borg 里,prune 会删除归档,但仓库并不会变小,直到 borg compact 运行为止 — 它被做成一条单独的命令,正是为了能单独安排运行时间。
这和上一节确实存在矛盾:清理需要删除权限,而你刚刚把删除权限从客户端手里收走了。这是正确的取舍,而不是设计缺陷。解决办法是在备份主机上做清理,在那里仓库是本地的,客户端没有发言权 — 在目标端设一个每周运行的定时器,全程不需要生产服务器参与。如果你嫌这套机制太重,替代方案是准备第二把不带 --append-only 的 SSH 密钥,不放在生产服务器上,每隔几个月手动用一次。你不该做的,是为了让一个 cron 任务简单点,又把不受限制的密钥放回服务器上。
监控,因为备份失败时悄无声息
一个停止运行的备份任务,和一个无事可做的备份任务,输出完全一样:什么都没有。这是备份在运维上的决定性特征,也是大多数坏掉的备份会一直坏上好几个月的原因。有三样东西值得接上,按价值从低到高排列:
- 失败告警。systemd 单元可以通过
OnFailure=在失败时触发另一个单元,由那个单元给你发一条消息。必要,但却是三者中最弱的,因为它只有在某个东西真的运行了、并且报了错时才会触发。 - 时效告警。询问仓库最后一次收到数据是什么时候,如果答案超过一两天,就发出告警。这能抓住被屏蔽(mask)的定时器、写满的磁盘、解析不了的主机名 — 所有那些因为根本没跑、所以从来不会报错的故障。
- 死人开关。把逻辑反过来:任务成功时去 ping 一个 URL,一旦 ping 停了,就由外部的某个服务向你告警。这是唯一一种在整台服务器都没了的情况下依然有效的办法,而这种故障,你最不希望是从客户那里得知的。
最划算的是时效检查,只要三行:
#!/bin/sh
# alert if the newest snapshot is older than 36 hours
last=$(restic snapshots --json --latest 1 | grep -o '"time":"[^"]*"' | head -1 | cut -d'"' -f4)
[ $(( $(date +%s) - $(date -d "$last" +%s) )) -lt 129600 ] || echo "BACKUP STALE: $last"目标端的磁盘也要盯着。备份卷一旦写满,此后的每个任务都会失败,而且是在最后才失败 — 在传输完成之后,这是发现问题代价最高的方式。在用量达到百分之八十时告警,能给你留出几周的提前量;不设告警,就一点提前量都没有。
真正让人丢掉数据的那些失败方式
大致按它们把备份变成纪念品的频率排序:
- 数据库在运行时被直接拷贝。在写入中途被抓取的 InnoDB 数据目录,恢复出来是一个启动不了的数据库,而且这一切悄无声息 — 备份顺利完成,文件一个不少,大小看起来也对。先转储。它被排在第三步,是有原因的。
- 口令只存在服务器上。一个完美无瑕的加密仓库,如果它的口令放在那台已经烧毁的机器的
/root里,那它就只是一堆高质量的噪声。口令存在密码管理器里、而这个密码管理器唯一的副本又是通过同一台服务器同步的,也是一样。 - 从来没人做过恢复。排除列表过于激进,数据库转储连续三个月都是空的,路径写错了。这些问题在恢复之前全都看不见,而一旦做了恢复,十分钟之内就一目了然。
- 备份放在同一个账户里。同一家服务商、同一个登录、同一段付费关系。应对磁盘故障没问题,除此之外一概无用。
- 客户端能执行删除。勒索软件会在碰数据之前先删掉备份,因为它的整个商业模式就建立在这上面。要么启用仅追加,要么接受这种情况你防不住。
- 目标端悄悄被写满了。保留策略从来没执行过清理,或者包含列表里新加了一个大目录。此后的每个任务都会失败,而如果你没做时效监控,没有任何东西会告诉你。
- 区块链数据进了备份。备份 Bitcoin 或 Monero 的数据目录,等于花钱存储一本你随时可以免费重新下载的公共账本。备份钱包和配置文件;把链数据排除掉。
- 排除列表误吞了东西。一条本来针对
/var/cache的规则,同时匹配到了某个应用的缓存目录,而那里面存着应用永远不会自己重建的状态。把第一次备份的文件列表认认真真读一遍。 - 恢复需要的密钥就存在仓库里。Borg 的
repokey模式把密钥保存在仓库内部,这很方便,直到你丢的恰好就是仓库本身。一开始就用borg key export把它导出到别处,做一次就够。
备份做不到的事
老老实实说清四个局限,因为一份把备份吹过头的指南,和一份没测试过的备份,干的是同一件事。
备份不是高可用。它能缩短停机时间,却不能避免停机。恢复一台服务器,意味着开通机器、拉取数据、解包、重新配置 — 规模小的也得足足一个小时,要是你上一次尝试恢复是“从来没有”,花的时间还要多得多。如果一个小时的停机你无法接受,答案是一台备用机,而备份依然必不可少,因为备用机会忠实地复制一切,包括你犯的错。
加密并不会让副本隐形。它让副本无法被读取,这是两回事。托管仓库的一方依然能看到它的存在、它有多大、它最后一次变动是什么时候 — 而为它付费的那个账户,和其他任何信息一样,都是能识别身份的细节。如果当初让你以免实名方式租下原服务器的理由依然成立,那它对第二台机器同样成立;同一套结账流程两台都适用。
静态加密并不等于在任何意义上都是端到端加密。restic 和 Borg 保护的是仓库。它们保护不了正在运行的服务器,那里的数据按定义就是明文;如果口令连同保存它的机器一起失守,它们也无能为力。源服务器上的全盘加密,和一份在两台机器之外另行保管的口令,是互补的控制措施,而不是重复的。
还有,放在另一个国家的副本,带来的是阻力,而不是豁免。司法辖区改变的是谁需要向谁提出请求,以及要花多长时间 — 离岸主机托管词条对这一点有详细论述。这里的两台机器都遵守同一份明文写出的可接受使用政策,底线也完全相同。这种安排真正换来的,是任何单一故障 — 一块坏掉的磁盘、一条敲错的命令、一个入侵者、一个被冻结的账户 — 都不会同时带走原件和副本。这正是备份存在的意义,而这就够了。

