パスフレーズが、壊れたマシンにしか残っていなかった
リポジトリは無傷で、暗号化されており、恒久的に読み取り不可能だ。パスフレーズ — Borg の場合はエクスポートした鍵も — を、最初のスナップショットを取る前にパスワードマネージャーと紙の両方に保管しておく。

VPS の暗号化バックアップを、無防備なサーバーから第二法域にある検証済みリポジトリまで6ステップで構築する — restic と Borg の比較、きれいに復元できるデータベースダンプ、systemd タイマー、そして侵害されたサーバーからでも消去できない append-only リポジトリ。2台目のサーバーは月額 $3.90。Debian 12 で検証済み。
棚卸し
作り直せないもの
インストール
restic か Borg か
宛先
第二の拠点
初期化
リポジトリ + 初回実行
自動化
ダンプ、タイマー、保持ポリシー
訓練
リストアして時間を計る
バックアップをめぐる議論のほとんどは、最初の1分で食い違う。話している2人が、それぞれ違う障害を思い浮かべているからだ。一方は故障したドライブを思い描いている。もう一方は、ある火曜日の朝にアカウントが消えている光景を思い描いている。両者には別々の答えが必要であり、前者にしか耐えられない構成は、後者で試されるまでは安全に見え続ける。
対策を設計する価値のある障害モードは4つあり、それらは同じテーマのバリエーションではない — それぞれが異なる防御を打ち破る。
ハードウェア。 NVMe が故障する、ホストノードが落ちる、アレイが同時に2台のメンバーを失う。これは誰もが備えている障害であり、現代の仮想化インフラでは最も稀な部類でもある。冗長ストレージはこれに対処する。冗長ストレージが対処するのはそれだけであり、だからこそ RAID はバックアップではないし、これまでもそうだったことは一度もない。RAID はあらゆる書き込みを忠実に複製する。「すべて削除する」という書き込みも含めて。
人的要因。 本番環境に対して実行された移行スクリプト。間違ったターミナルで叩いた DROP TABLE。引数を逆にした rsync。実際のデータ損失の原因として、これが圧倒的に多い。そして唯一の防御は履歴だ — ミスの前の時点のコピーを、ミスが届かなかった場所に保管しておくこと。鍵となるのは時間だ。唯一のコピーが30秒遅れのミラーでしかないなら、そこにはすでにミスが含まれている。
悪意ある攻撃。 誰かが root 権限を奪うか、パッチの当たっていないアプリケーション経由でランサムウェアが侵入する。現代のランサムウェアがまず行うのは、バックアップの設定 — 認証情報、マウントされた共有、クラウドの鍵 — を探し出し、見つけたものを破壊することだ。動作するリストアが一日の作業で済む復旧を可能にしてしまうからこそ、そこを狙う。削除権限を持つバックアップ用の認証情報は、攻撃者がそのまま受け継ぐ認証情報でもある。このガイドの append-only の章は、まさにこの失敗のために存在する。
カストディアル。 何も壊れておらず、何も攻撃されていない。ただアクセスを失っただけだ。自動不正検知シグナルによるアカウント停止、旅行中のカード決済失敗、異議申し立て、コンプライアンス審査、プロバイダーに対して出された命令。マシンは無傷のまま到達不能になり、そのアカウント内のすべてのスナップショットも道連れに到達不能になる。これは、1つのプロバイダー内でどれだけ冗長性を積んでも答えられない障害であり、まともなバックアップが別の屋根の下に置かれるべき理由でもある。
| 防御 | ハードウェア | 人為的ミス | ランサムウェア | アカウントの喪失 |
|---|---|---|---|---|
| RAID / 冗長ストレージ | はい | なし | なし | なし |
| プロバイダーのスナップショット、同一アカウント | はい | 一部 | なし | なし |
| オフサイトのリポジトリ、書き込み可能な鍵 | はい | はい | なし | はい |
| オフサイトのリポジトリ、append-only、別の法域 | はい | はい | はい | はい |
最後の行こそ、このガイドの残り部分全体で組み立てていくものだ。その上の行より高くつくわけではない — append-only 設定は無料であり、2つ目の法域も1つ目と同じ $3.90 で済む。
3-2-1 ルールを、単一の VPS 向けに言い換える。 データのコピーを3つ、2種類の異なるストレージに、そのうち1つはオフサイトに。この古典的な定式化はテープとオフィスサーバーの時代に生まれたものだが、字面よりも精神のほうがよく生き残っている。セルフホストの1台構成に当てはめると、こうなる — VPS 上の稼働データ、どこか別の場所にある2台目のマシン上のリポジトリ、そして本当に取り返しのつかないものについては、自宅のディスクや引き出しの中など、自分の手元に置ける3つ目のコピー。実際に重要な数字は3ではない。データが失われるまでに、いくつの独立した要因が同時に失敗しなければならないか、その数だ。持つ価値のある最低ラインは2つ。
探しているカテゴリーは「重複排除・暗号化スナップショットツール」と呼ばれるものだ。すべてのファイルを内容に基づくチャンクに分割し、データを所有するマシン上で各チャンクを暗号化し、いくつのスナップショットから参照されていても一度だけ保存する。これにより3つの性質が同時に得られる — 1つ分とほぼ変わらない容量で多数の復元ポイントを持てること、平文が送信元から一切出ないこと、そして変更分だけを転送すること。
この分野を席巻しているのは2つのプログラムで、どちらも優秀だ。restic は幅広いストレージバックエンドに対応する、単一の静的な Go バイナリ。BorgBackup は、遠端にある自分自身のコピーと SSH で会話する Python プログラム。以下は、両者の正直な違いだ。
| 項目 | restic | BorgBackup | rsync / rclone |
|---|---|---|---|
| クライアント側暗号化 | 常時有効、選択の余地なし | 常時有効、選択の余地なし | rclone crypt リモート経由のみ |
| 重複排除 | コンテンツ定義チャンク | コンテンツ定義チャンク | なし、またはハードリンクツリー |
| スナップショット履歴 | できる、保持ポリシー付きで | できる、保持ポリシー付きで | 今この瞬間のミラー |
| 宛先側の要件 | 不要 — SFTP、S3、REST、rclone | 両端に Borg をインストール | SSH アカウントまたは API |
| Append-only の強制 | rest-server またはオブジェクトロック経由 | 組み込み、素の SSH 経由 | なし |
| 向いている用途 | ほぼ全員、ほとんどのバックエンド | 自分が所有する SSH マシン、append-only | ミラーリングであってバックアップではない |
rsync が表に入っているのは、多くの人がまず最初に手を伸ばすツールだからであり、同時に、それが正真正銘の不適切なツールだからでもある — ミラーは削除も忠実に再現してしまうし、保存時暗号化されたディスクのミラーは、それ自体はどこにも暗号化されていない。rsync がバックアップの中で果たすべき役割は、完成したアーカイブそのものではなく、完成したアーカイブを運ぶ手段としてだ。
このガイドでは、コマンドが異なる箇所ではすべて restic と Borg の両方を示しているので、どちらを使っていても読み進められる。1つを選ばなければならない箇所 — 実例、タイマー、スクリプト — では restic を使う。宛先側に必要なのが SSH アカウントだけで済み、2台目のマシンを退屈な存在のままにしておけるからだ。
ファイルシステム全体をバックアップするのは、正当化はできるが無駄の多い選択だ。サーバーの大部分はパッケージマネージャー1つで再現できるものであり、それを1ギガバイト運ぶたびにリポジトリの prune は遅くなり、保持コストは上がり、急いでいるときの検索も遅くなる。有用な作業はその逆だ — 新規インストールでは戻ってこないものだけを洗い出す。
典型的なセルフホスト VPS であれば、そのリストは短く、常に次の5つを含む:
このリストを、送信元サーバー上の2つのファイルにする。include リストは意図を見える形にし、exclude リストはノイズを締め出す。どちらもバックアップコマンドが読み込むものであり、どちらもバックアップ自体に含めておくべきだ。
/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 の行については、つまずきやすい点が2つある。named volumes は /var/lib/docker/volumes 以下にあり、これこそが欲しいものだ。bind mounts は置いた場所ならどこにでもあり、たいていは Compose ファイルのすぐ隣にあって、/opt でカバーされる。コンテナイメージはデータではない — pull すれば戻ってくるものであり、これを含めたバックアップは不要なギガバイトを運んでいるにすぎない。
最後に、何かを組み立てる前にまず結果を計測する。その数字が、どのリポジトリの容量プランを注文すべきか、そして最初の実行が4分で終わるのか40分かかるのかを教えてくれる:
du -sh --exclude=/var/lib/docker/overlay2 /etc /opt /root /home /var/lib/docker/volumes
データを所有するマシンにツールをインストールする。Debian と Ubuntu ではどちらもパッケージ化されているが、restic についてはディストリビューションのパッケージが1〜2リリース遅れていることが多い — リポジトリの機能や性能改善はポイントリリースで入るため、これは無視できない。パッケージをインストールしたら、restic 自身にその場でアップデートさせる:
apt update && apt install -y restic
restic self-update
restic version
Borg については、ディストリビューションのパッケージが正解だ。送信元側と宛先側のバージョンが互いを理解し合う必要があり、同じディストリビューションのリリースから揃えたペアが、それを実現する最も苦労の少ない方法だからだ:
apt install -y borgbackup
borg --version
何年分もの履歴を1つのリポジトリに預ける前に、メジャーバージョンについて一言。Borg のリポジトリ形式はメジャーライン間で変更されており、その境界をまたいでリポジトリを移すのはアップグレードではなく変換だ。インストールするバージョンのリリースノートを読み、宛先を送信元と同じメジャーラインに保つこと。restic はリリース間で前方互換性を保っており、機能追加は明示的なリポジトリバージョンの背後で行われるため、同種の懸念はそれより小さい。
どちらのツールも、送信元にデーモンやエージェント、開いたポートを必要としない。これは明言しておく価値がある — 導入するバックアップシステムは、保護対象のマシンに待ち受けサービスも新たな攻撃対象領域も一切追加しない。行うことはすべて、スケジュールに沿った、SSH 経由の送信のみだ。
リポジトリホストの仕事は1つだけで、必要なものもほとんどない。CPU コアは要らない — データを読むことは一切なく、開けない暗号化された blob を保持するだけだからだ。メモリも要らない — 重複排除の処理は送信元側で行われる。必要なのはディスクと安定したアドレス、そして送信元マシンのトラブルが波及しない場所であることだけだ。
パネルでは Order → VPS → Sentinel、イメージは Debian 12、そして — ここが肝心なのだが — 送信元が動いている拠点とは別の拠点を選ぶ。稼働は Helsinki、保管は Reykjavík。アカウント開設にメールアドレスは不要で、請求は Monero、Bitcoin、Lightning、その他対応する資産のいずれかで決済されるため、2台目のサーバーが、1台目が慎重に避けてきた身元情報を再び持ち込むことはない。
| リポジトリの宛先 | 月額 | ストレージ | 扱えるワーキングセット | リストアのコスト |
|---|---|---|---|---|
| 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 | 伸縮自在 | 任意 | 従量課金の送信データ |
「ワーキングセット」列は、通常程度の変化がある状態で毎日のスナップショットを1年間保持することを前提としている。変化の少ないファイルが中心のリポジトリならもっと長く持つし、大きな再書き込みバイナリばかりなら短くなる。最後の行は規模感のため、そしてまさかの日まで人が忘れがちな点のためにある — オブジェクトストレージはダウンロードにも課金され、緊急時に行うのはまさにそのダウンロードだ。
宛先側では — 権限のないユーザーと1つのディレクトリ。
adduser --disabled-password --gecos "" backup
install -d -m 0700 -o backup -g backup /srv/restic/web01
バックアップ用ユーザーにはパスワードも sudo 権限もなく、ログインする対象すら持たない。存在意義はディレクトリを所有することだけだ。複数のマシンをバックアップする場合は、送信元ごとに専用のディレクトリを与える — 送信元ごとに1つのリポジトリにしておけば、1台が侵害されても他の履歴には影響しない。
送信元側では — 他の何にも使わない鍵。
ssh-keygen -t ed25519 -f /root/.ssh/id_backup -N "" -C "backup:web01"
cat /root/.ssh/id_backup.pub
ログイン用の鍵を使い回さないこと。この鍵は、無人のタイマーが深夜3時に使う必要があるため、ディスク上に暗号化されずに存在する — だからこそ、宛先1つ・用途1つに限定した鍵にしておく必要がある。
話を宛先側に戻す — その鍵にできることを制限する。 公開鍵を /home/backup/.ssh/authorized_keys に、先頭にプレフィックスを付けて貼り付ける。restic を SFTP 経由で使う場合:
restrict,from="198.51.100.7" ssh-ed25519 AAAAC3NzaC1lZDI1... backup:web01
restrict オプションは、ポートフォワーディング、エージェントフォワーディング、X11、PTY 割り当てをひとまとめに無効化し、残るのはファイル転送だけになる。from= 句は鍵を送信元アドレスに固定するため、サーバーから盗まれたコピーは他の場所ではまったく使えない。Borg ではさらに一歩進めて command を強制する。ここに append-only モードが宿っている:
command="borg serve --append-only --restrict-to-path /srv/borg/web01",restrict ssh-ed25519 AAAAC3NzaC1lZDI1... borg:web01
この1行に、ランサムウェア対策のすべてが集約されている。送信元が何を送ってこようと、宛先側は常に borg serve だけを、そのパスの中だけで、追記モードのみで実行する。送信元で root シェルを奪われても、この鍵で先月分を消すことはできない。
宛先の名前は、送信元側で一度だけ設定する。 /root/.ssh/config に書いておけば、以降のコマンドはすべて短く済み、アドレスはただ1つのファイルにのみ存在する:
Host rkv-repo
HostName 198.51.100.42
User backup
IdentityFile /root/.ssh/id_backup
IdentitiesOnly yes
続いて、一度だけ手動で接続して — ssh rkv-repo — ホスト鍵を承認する。無人で動くタイマーはフィンガープリント確認に答えられず、初回のバックアップが1週間黙って固まったままになるのはよくある話だ。宛先マシンにいる間に、他の所有マシンと同じ扱いをしておく — 最初の1時間のハードニングチェックリストをここでも実行する。すべてのコピーを保持するマシンなのだから、まる1時間をかける価値がある。
自分では絶対に入力しないパスフレーズを生成する。スクリプトがファイルから読み込むので、長くしてもコストはゼロで、覚えやすくする理由もない:
openssl rand -base64 32 > /root/.restic-pass
chmod 600 /root/.restic-pass
cat /root/.restic-pass
ここで一度手を止めて、その文字列をこのサーバー以外のどこかにコピーしておく。 パスワードマネージャー、引き出しの中の紙のメモ、2台目のサーバー — 送信元を失っても残る場所ならどこでもいい。これは、セルフホストのヴォールトや自動化エンジンを破壊するのと同じ失敗だ — すべてを復号する鍵が、すべてのすぐ隣に保管されていて、両方が同時に失われる。パスフレーズが保護対象のマシンにしか存在しなかったリポジトリはバックアップではない。それは、実によく整理されたノイズの山にすぎない。
以降のすべてのコマンドが読み込む環境ファイルを書く:
/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
最初のスナップショット。 まず手動で実行し、様子を見ながら完了させてから自動化する。これは遅い実行になる — すべてのチャンクが新規だからだ — そして include リストが正しかったかどうかを教えてくれる実行でもある:
restic backup \
--files-from /etc/backup/include.txt \
--exclude-file /etc/backup/exclude.txt \
--tag nightly --one-file-system --verbose
送信元がトラフィックを処理中で、アップロードが回線を飽和させている場合は絞り込む。このフラグはキビバイト毎秒で指定するので、20000 はおよそ 20 MB/s になる:
restic backup --limit-upload 20000 --files-from /etc/backup/include.txt
完了したら、実際に何が得られたかを確認する。次の3つのコマンドは、今すぐ、そしてこのことを意識しなくなった半年後にもう一度、実行すべきものだ:
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
3つ目のコマンドには立ち止まる価値がある。壊れたバックアップの半分は実はまったく壊れていない — 間違ったディレクトリを完全かつ健全にリポジトリ化しているだけだ。最初のスナップショットの時点で、見つかるはずのパスを一覧表示しておけば、include ファイルの内容がまだ頭に新しいうちにこれを発見できる。
データベースに関するルールを、一度だけ述べる。 稼働中のデータベースエンジンは状態をメモリに保持し、自分のタイミングでディスクに書き出す。その最中にファイルを読み取ると、いくつもの異なる時点のページが混在したものが手に入る — 正常にリストアできるかもしれないし、壊れた状態でリストアされるかもしれないし、問題なく開けるのに直近1時間分が黙って欠けているかもしれない。外から見てどれに該当するかを知る方法はない。だからこそバックアップは稼働中のファイルには一切触れない。まずダンプをディスクに書き出し、バックアップはそのダンプを拾い上げる。
すべてを1つのスクリプトにまとめる。/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)"
あのスクリプトには、見た目以上に効いている3つの細部がある。冒頭の set -eu は、ダンプが失敗したときに実行を中断させる。これがないと、昨日のダンプを半年間黙ってスナップショットし続けることになる。forget と prune を1つのコマンドにまとめているのは、容量を回収しない保持ポリシーはただ肥大化するディスクにすぎないからだ。そして日曜のチェックはインデックスだけでなく実データの一部を読む — リポジトリの破損は稀で、しかも無音で起きる。それを見つけられるのは実際に読むことだけだ。
タイマー。 2つの小さなユニットファイル。ここで正しいスケジューラは cron ではなく systemd だ。タイマーなら 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 の奪い合いで消費される。開始時刻を45分の範囲に分散させるのに費用はかからず、原因不明の「重い夜」をまるごと排除できる。
そして、失敗を目立たせる。 これは誰もが省略しがちなステップであり、それまでのすべてが意味を持つかどうかを左右するステップでもある。動かなくなったバックアップは何も知らせてこない — タイマーは律儀に発火し続け、サービスは失敗し続け、ダッシュボードは緑のままで、最後の正常なスナップショットだけがどんどん過去へ遠ざかっていく。実際に目にする形で通知が届くよう、サービスに OnFailure= ユニットを追加し、月に一度スナップショット一覧の先頭をのぞく。1行の設定と、1つの習慣。
restic snapshots --latest 3 # is the newest one from last night?
systemctl list-timers nb-backup.timer # is the timer still armed?
訓練は2種類あり、それぞれ異なる問いに答える。小さいほうは「データは本当にそこにあり、読み取れるか」を問い、90秒で済む。大きいほうは「実際にサービスを復旧させるのにどれくらいかかるか」を問い、年に一度、半日かかる。小さいほうは毎月行う。大きいほうは少なくとも一度は行う — その答えは、人が予想するものとは決して一致しないからだ。
小さな訓練。 1つのディレクトリと1つのダンプを一時パスに取り出し、稼働中のものと比較する:
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 レコードがどこにも書き留められていなかった、バインドマウントされたディレクトリが include パスの外にあった、リストアに必要なパッケージのバージョンがもうデフォルトではなくなっていた、どのコンテナを最初に起動すべきか誰も把握していなかった、といったものだ。
時間を計る。その数字を日付とともにランブックの冒頭に書き込む。リポジトリのサイズでもスナップショットの数でもなく、その数字こそが、サービスが実際にどれだけ守られているかを測る唯一の正直な指標だ。
そして、たまにはデータを実際に読む。 夜間チェックが検証するのは構造だけだ — インデックスに矛盾がなく、blob が欠落していないこと。blob の中身までは読まない。月に一度、あるいは日曜の実行時に、その一部を実際に読む — ローテーションで5パーセントずつ読めば、長時間かけることなく数か月でリポジトリ全体をカバーできる:
restic check --read-data-subset=5%
borg check --verify-data # the Borg equivalent, slower and thorough
ここまでの内容はすべて、事故に対する防御だった。この章が防御するのは敵対者であり、その設計上の問題は、一度見えてしまうと居心地が悪い — 送信元のマシンは毎晩リポジトリに到達しなければならず、それは送信元マシンがリポジトリに対する有効な認証情報を保持していることを意味する。送信元を掌握した者が、その認証情報も掌握する。認証情報が削除できるなら、攻撃者も削除できる — そして現代のランサムウェアは、何かを暗号化する前に、まさにこれを意図的に行う。リストア可能な被害者は身代金を払わないからだ。
解決策はより強固なパスワードではない。非対称な権限だ — 送信元はデータを追加できるが、削除はできない。これを実現する方法は3つあり、正しく作りやすい順に並べると次のとおり。
1つ目 — SSH 経由の強制 append-only。 これは Borg の得意分野であり、自分で所有するマシンで使える中では最もすっきりした答えだ。ステップ03 の authorized_keys のエントリが borg serve --append-only を強制するため、送信元が何を要求してこようと宛先側は削除を拒否する。prune はそれでも必要だが、それは宛先側から、送信元が関与できないスケジュールで、送信元が到達できないアカウントによって実行される。送信元で root を奪った攻撃者はリポジトリをゴミで埋め尽くすことはできても、侵入前夜のアーカイブを取り除くことはできない。
2つ目 — append-only な REST エンドポイント。 restic の相棒である rest-server にも --append-only モードがあり、同じ性質を持つ — 新しい blob は受け入れるが、既存のものは削除できない。宛先で TLS の背後に置いて実行し、RESTIC_REPOSITORY を sftp: の URL ではなく https:// の URL に向ければ、防御の形はまったく同じになる。宛先でサービスが1つ増える代わりに、同じ保証が手に入る。
3つ目 — オブジェクトロック。 S3 互換ストレージでは、バージョニングとオブジェクトロックの保持期間を組み合わせることで、期間が満了するまで削除が不可能になる。これはプログラムではなくストレージ層によって強制される。3つの中で最も強力で、最も高価であり、プロバイダーアカウントを新たに持ち込むことになる — それは、このガイド全体が分散させようとしているカストディアルリスクを呼び戻すことでもある。3つ目のコピーとしては理にかなっているが、唯一のコピーとしては据わりが悪い。
そのどれも当てはまらない場合 — 素の SFTP で、書き込めるゆえに削除もできる鍵しかない場合 — それを見て見ぬふりをしないこと。送信元がまったく触れられない、独立した2つ目のコピーを追加する。push ではなく pull にする — 宛先側が送信元にログインし、必要なものを読み取って保存する。こうすれば認証情報は露出していないマシン側に存在することになり、送信元を侵害した攻撃者はバックアップを指し示すものを何も見つけられない。構築の手間は少し増えるが、リスクをまさに正しい方向へ反転させてくれる。
どれを選ぶにせよ、他のセキュリティ対策と同じ方法で検証する — 実際に破ろうとしてみることだ。送信元から、バックアップ用の鍵を使って削除を試みる。正しい結果は「拒否される」ことだ。
borg delete ::name-of-an-old-archive
# → Remote: Repository is in append-only mode. Refusing to delete.
どのサービスにも、データでも設定でもない小さな状態が1つ存在し、それを失ってもサービスが壊れるわけではない — たまたま同じ名前を持つ別のサービスに置き換わるだけだ。古いリンクは切れ、フェデレーションの相手は新しい身元を拒否し、クライアントは鍵を作り直す。以下は、このサイトがガイドを用意しているスタックごとの、そうした部分の一覧だ。
データディレクトリ、config/config.php、そしてデータベースダンプ。ダンプを取る間はメンテナンスモードを有効にし、終わったら解除する — そうしないと Nextcloud が両方に同時に書き込んでしまう。
データディレクトリ全体 — db.sqlite3 はコピーではなく .backup で取得したもの、それに rsa_key ファイル、attachments、sends。
署名鍵、homeserver.yaml、メディアストア、そして PostgreSQL のダンプ。署名鍵を失うと、フェデレーション ID は永久に失われる。
メールストア本体、仮想ユーザーテーブル、そして DKIM の秘密鍵 — DKIM 鍵を新しくすると、DNS が反映されるまですべてのメッセージが未署名になる。
まず N8N_ENCRYPTION_KEY、それから PostgreSQL のダンプ。この鍵を欠いたデータベースは、あらゆる認証情報が読み取れないワークフローの一覧にすぎない。
隠しサービスのディレクトリ。このわずかなファイル群が .onion アドレスそのものであり、これを失うとサービスは別の名前で復活してしまい、それまでのリンクはすべて無効になる。
/etc/wireguard をまるごと。小さくて地味だが、5分で再構築できるか、所有するすべてのデバイスの鍵を作り直す羽目になるかの分かれ目になる。
シードとチャネル状態を、そのノード自身の流儀で。古いチャネルデータをリストアすると資金を失いかねない — 汎用的なファイルコピーではなく、実装ごとのバックアップ手順に従うこと。
なぜバックアップを別の場所に置くべきなのかと大半の人に尋ねれば、火災、水害、データセンターの停電という答えが返ってくる。どれも事実であり、どれも年々まれになっており、どれも100キロメートルの距離で解決する。しかし2026年に実際に事業を潰しているのは事務的な障害であり、距離だけではそれに対処できない。
1つのアカウントは1つの単一障害点だ。 自動不正検知シグナルによる停止、飛行機に乗っている間のチャージバック、コンプライアンス審査、マシンではなく会社宛てに届く削除要請 — そのどれもが、スナップショットを保持しているサーバーを含め、そのアカウント配下のすべてのサーバーに同時に及ぶ。技術的な冗長性は完璧だったが、無意味だった。2つのプロバイダー、あるいは少なくとも2つの異なる法的主体のもとにある2つのアカウント — これに答えられる構造はそれしかない。
1つの法域は1つの法的枠組みだ。 北欧の4つの拠点は互換ではない — スウェーデン、フィンランド、ノルウェー、アイスランドはそれぞれ少しずつ異なるものを保護しており、ノルウェーとアイスランドは完全に欧州連合の外にある。稼働中のマシンとそのリポジトリをこの2拠点に分けるということは、単に「別の場所」にあるだけでなく、独立した別の法体系の下に置くということだ。北欧の法域リファレンスでは、各法令を1つずつ解説している。
そして宛先側は何も知ることができない。 これがあるからこそ、分散配置はトレードオフではなく単なるコストで済む。restic も Borg も、データが送信元を出る前に暗号化するため、リポジトリホストは鍵を持たない blob を保存しているにすぎない。どのファイルを、いくつ、どんな名前で保持しているのかすら把握できない。第二者に信頼を広げているのではなく、自分自身を読めないディスクを借りているだけだ。「完全には信用していないプロバイダーをバックアップ先に使ってよいのか」という問いへの正直な答えもここにある — 暗号化されたリポジトリでは、信頼はほとんど問題にならない。
遅延は問題にならない。 Helsinki と Reykjavík の間は幹線上で30ミリ秒、Helsinki と Stockholm は8ミリ秒、Stockholm と Oslo は11ミリ秒。毎晩のバックアップはそのどの数字も気にしないし、ディスクの転送速度で頭打ちになるリストアも同様だ。ペアはネットワーク上の距離ではなく、法的な距離で選ぶこと — 北欧域内のネットワーク距離など誤差の範囲でしかない。
もう一つ、はっきり言っておく価値がある。これこそが、このガイドが一般的なシステム管理ブログではなくこのサイトに置かれている理由だ — 2台目のマシンは、1台目が答えたはずのすべての問いを再び開いてしまう。稼働中の VPS を身分証明書なしで注文し、Monero で支払ったとしても、そのバックアップ用のマシンを会社のカードとパスポートのスキャンで注文してしまえば、そのペアは弱い方の半分と同じだけ身元を特定できてしまう。匿名 VPS ホスティングの柱となるガイドでは、サインアップ・支払い・ネットワークという3つの層を扱っており、それらはウェブサーバーと文字通り同じように、バックアップ先にも当てはまる。
リポジトリは無傷で、暗号化されており、恒久的に読み取り不可能だ。パスフレーズ — Borg の場合はエクスポートした鍵も — を、最初のスナップショットを取る前にパスワードマネージャーと紙の両方に保管しておく。
ダンプではなく、稼働中のデータファイルがそのままコピーされていた。バックアップの前段階でダンプを書き出し、そのダンプをバックアップすること — include リストを稼働中のエンジンのデータディレクトリに向けてはならない。
侵害されたマシン上の書き込み可能な認証情報は、攻撃者にとっても書き込み可能な認証情報だ。宛先側で append-only を強制するか、2つ目のコピーを push ではなく pull で取得すること。
prune を伴わない forget はスナップショットに印を付けるだけで、容量は一切回収しない。両者は一緒に実行し、宛先には空き容量を確保しておき、コンテナイメージや node_modules は絶対にバックアップしないこと。
アップグレード後にタイマーが失敗していたが、誰にも知らされなかった。OnFailure= ユニットを追加し、月に一度スナップショット一覧の先頭を確認する — 10秒で済む。
/opt の外にあるバインドマウント、移行の際に移動したボリューム、意図した以上にマッチしてしまった exclude パターン。新しいスナップショットのたびに既知のパスを一覧表示し、スタックに変更を加えるたびに include ファイルを読み直す。
構築中に出てくる10の質問 — そして、あとになって一度だけ出てくる2つの質問。
いいえ — それはロールバックであり、便利ではあるが、バックアップが存在する理由となる、まさにその瞬間に機能しない。スナップショットは、コピー元のサーバーと同じプロバイダーアカウントの中に存在する。アカウントが停止されたり、支払いに異議が申し立てられたり、サポート担当者が誤って削除したり、攻撃者がパネルの認証情報を手に入れたりすれば、スナップショットもマシンと一緒に消える。細かい単位での復元もできない — 昨日削除した1つのファイルだけを取り出すことはできず、ディスク全体を巻き戻して、それ以降のすべてを失うことしかできない。スナップショットは残しておくべきだ — 失敗したアップグレードを取り消す最速の方法だから。ただし、それを、元の問題が届かない場所にあるコピーと混同してはならない。目安はこうだ — 1つの侵害された認証情報で両方のコピーが破壊できるなら、それは1つのコピーしかないのと同じだ。
どちらもクライアント側で暗号化し、どちらもチャンク単位で重複排除を行い、どちらも成熟しており、どちらも仕事はこなす。restic を選ぶべきなのは、単一の静的バイナリが欲しい場合、リポジトリを SFTP・S3 互換オブジェクトストレージ・REST サーバー・rclone が届く場所ならどこにでも置きたい場合、そして宛先側には一切何もインストールしたくない場合だ。Borg を選ぶべきなのは、宛先が自分の管理下にある SSH マシンで、オブジェクトロックストレージなしで得られる最も強力な append-only の強制が欲しい場合、そしてそのやや高い圧縮率が気に入っている場合だ。決め手となる実務上の違いはこうだ — Borg は両端に borg のインストールが必要で、SSH かローカルパスでしか話せない。restic は prune の際にリポジトリを短時間だけ排他ロックするパスが必要になる。決められないなら restic を使う — 宛先側の可動部品が少ないことは、どんなベンチマークよりも価値がある。
起点にすべきは、実際にバックアップする対象のサイズであって、それが載っているディスクのサイズではない。典型的なセルフホスト構成では、データベースと設定ファイルで数ギガバイト、それにユーザーがアップロードしたものが加わる程度だ。そこに重複排除と圧縮が効いてくる — 12か月の保持ポリシーで 20 GB のワーキングセットを毎日スナップショットすると、変更されたチャンクだけが二重に保存されるため、通常はリポジトリが 40〜80 GB に収まる。Sentinel($3.90/月、120 GB NVMe)でも十分に賄える。複数のサーバーが同じリポジトリホストにバックアップする場合は Garrison(240 GB、$7.90)へ、ワーキングセット自体が数百ギガバイトに達する場合は Bulwark(960 GB、$32.90)へ移行する。リポジトリのサイズを見積もったら、それを2倍にしておく — 空き容量のないリポジトリは prune できず、prune できないリポジトリは肥大化する一方だ。
コピーすることはできる。しかしリストアできるとは限らない。読み取っている最中のデータベースがディスクに書き込みを続けていると、複数の異なる時点が入り混じったファイル群を渡されることになる。これがまさに「破れたバックアップ」の定義であり、破損したデータベースとしてリストアされるか、さらに悪いことに、問題なく開けるのに行が黙って欠落しているデータベースとしてリストアされる。解決策はダンプだ — PostgreSQL なら pg_dump または pg_dumpall、InnoDB テーブルの MariaDB / MySQL なら mariadb-dump --single-transaction、SQLite なら sqlite3 db .backup out.db または VACUUM INTO。ダンプをファイルに書き出し、そのファイルをバックアップする。データベースが大きすぎて毎晩ダンプできない場合の代替手段は、エンジンを短時間静止させた状態で取るファイルシステムスナップショットか、エンジン自身が持つ物理バックアップツールだ — とはいえ単一の VPS で動くもの程度なら、ダンプで十分かつシンプルだ。
バックアップは失われる。restic も Borg もクライアント側で暗号化しており、どちらにも復旧経路もマスターキーも、リポジトリを解錠してくれるサポートチケットも存在しない。それこそが要点だ — 宛先ホストは、たとえ自分の所有物でないホストであっても、平文を一切目にすることがない。同時にこれは、パスフレーズが保護対象と同じだけの価値を持つデータになったことも意味し、バックアップ対象のマシンだけに置いておいてはならない。パスワードマネージャーに保存し、さらに物理的な場所にもコピーを置いておく。Borg の場合は、borg key export でリポジトリの鍵もエクスポートし、一緒に保管する。repokey モードでは鍵がリポジトリ内部に存在するため、到達できなくなったリポジトリは鍵ごと失われる。
追加はできるが削除はできない認証情報を与える。これは今回の作業全体の中で最も重要な設計判断だ。現代のランサムウェアはまずバックアップの設定を探し、そこを辿って本丸まで来るからだ。実現方法は3つ。SSH 経由の Append-only: 宛先の authorized_keys で、コマンド borg serve --append-only --restrict-to-path /srv/borg を強制する — 送信元は新しいアーカイブを書き込めるが、古いものを削除することはできない。REST 経由の Append-only: restic の rest-server を --append-only 付きで実行し、リポジトリを HTTPS 経由でそこに向ける。オブジェクトロック: バージョニングと保持ロックを備えた S3 互換バケット。素の SFTP ではこのどれも得られない — リポジトリに書き込める鍵は、それを消すこともできる鍵だからだ。そのため転送に SFTP を使う場合は、送信元で盗まれた認証情報が届かない宛先側から取得する、pull 方式の2つ目のコピーと組み合わせる。
逆から問うてみるといい — どれだけの作業ならやり直してもいいか? その数字が復旧時点目標であり、それが実行間隔を決める。毎晩1回は、メールサーバー、Nextcloud、Matrix ホームサーバー、ワークフローエンジンなど、セルフホストサービスのほぼすべてに適している。データがトランザクション的で、再入力が不可能な場合は毎時1回の価値がある。保持ポリシーについては、現実に耐えるパターンは --keep-daily 7 --keep-weekly 4 --keep-monthly 12 だ — 1週間分のきめ細かい取り消し、1か月分の週次チェックポイント、1年分の月次チェックポイントで、合計およそ23のスナップショットになる。長い裾野は思われている以上に重要だ。そこで捕まえる障害は故障したディスクではなく、6週間誰にも気づかれなかった破損や削除だからだ。
別の建物というのは技術面での最低条件にすぎない。別の法域こそ、大半の人が省略して、あとで後悔する部分だ。火災、水害、ラック単位の障害は距離だけで解決する。距離が解決しないのは法的・商業的な問題だ — 一つのプロバイダーによるアカウント停止、支払いをめぐる紛争、同じ法人の傘下にあるすべてのマシンに及ぶ削除要請、一つの会社に対して出された命令。両方のコピーが同じプロバイダーのもとにあるなら、一通の書面が両方に届く。北欧の2つの拠点にペアを分散させる — 稼働マシンは Helsinki、リポジトリは Reykjavík、幹線上で30ミリ秒の距離 — ことは、遅延の面では何のコストもかからず、もう一つの法的枠組みを手に入れられる。データはどちらの場合も送信元を出る前に暗号化されているため、宛先側はそれを保持していても何も知ることができない。
暗号化はボトルネックではない — 現代の CPU は、ギガビット回線が結果を運べる速度よりも速く AES を処理でき、両ツールとも利用可能な場合はハードウェアアクセラレーションを使う。ボトルネックになるのは最初のアップロードだ。すべてが新規だからだ — 1 Gbps のアップリンクなら 20 GB のワーキングセットは回線速度で数分だが、宛先側が制限されていたり、ファイル数が多く小さかったりすると、もっと長くかかる。それ以降の実行は変更されたチャンクだけを転送するため、典型的な構成なら数十メガバイト程度で、1分もかからず終わる。最初の実行が本番のワークロードと競合するなら、レート制限をかける — restic は --limit-upload を KiB/s 単位で受け取り、Borg は --upload-ratelimit を受け取る — そして nice と ionice の配下で実行する。
そうすべきであり、その理由は心配性だからではない — よくある故障は静かに起きるからだ。パッケージのアップグレード後に発火しなくなったタイマー。アップロードディレクトリを黙って飲み込んでいた exclude パターン。バックアップが一度もカバーしていなかったパスに書き出されていたデータベースダンプ。誰も読まないログの中で、何週間も整合性チェックに失敗し続けていたリポジトリ。これらはどれも自ら名乗り出てはこない。すべて、ファイルを1つリストアして中身を見るだけの90秒で見つかる。小さなリストアは毎月行う — データベースダンプとデータディレクトリを1つずつ一時パスに取り出して差分を取る — そして完全な再構築を年に一度、新しい VPS で、時間を計りながら行う。その再構築から出てくる数字こそが本当の復旧時間であり、それは大抵、予想していた数字とは一致しない。
Sentinel — 月額 $3.90 で 120 GB の NVMe。設置場所は Helsinki、Stockholm、Oslo、Reykjavík のいずれか。帯域無制限のため、リストアにも費用はかからない。登録時にメールアドレスは不要、身分証明書も不要。そして保持しているデータを1バイトも読み取れない宛先。
最終レビュー · 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.
エージェントをノートPCから移す — サイズ選び、systemd、シークレット、支出の上限。