SSD Nodes Learn 🎉 VPS $4.99/月〜
ガイド Matt Connor著者 Matt Connor

Vaultwardenのバックアップと復元方法 VPS編

Vaultwardenのdataフォルダーを安全にバックアップする方法です。sqlite3 .backupで稼働中のDBをコピーし、attachments、config.json、rsa_keyファイルを保持して復元まで検証します。

Vaultwarden のバックアップに含める必要があるもの

Vaultwarden のバックアップは、data フォルダー全体のコピーです。ただし、その中のデータベースは正しい方法でコピーする必要があります。書き込み中のデータベースを単純にコピーすると、開けないファイルになることがあるため、cp ではなく sqlite3 db.sqlite3 ".backup out.sqlite3" を実行してください。そのうえで、データベースと同じ場所にあるファイルも保持します。この部分を忘れる人が少なくありません。

Docker でインストールしている場合、data フォルダーは /data にマウントした場所です。これはホスト上のパスまたは名前付きボリュームです。bind mount と名前付きボリュームの違いによって、vault が実際にディスク上のどこに保存されるかが決まります。data フォルダーには次のファイルが含まれます。

  • db.sqlite3: すべてのアカウント、vault の全項目、すべてのフォルダー、すべての組織が含まれます。このファイルを失うと、vault を失います。
  • db.sqlite3-waldb.sqlite3-shm: write-ahead log (WAL) と共有メモリのインデックスです。SQLite が内容をメインファイルへ反映するまで、最近の書き込みはここに保存されます。
  • attachments/: vault の項目にユーザーが添付したファイルです。暗号化され、項目ごとに 1 つのディレクトリへ保存されます。
  • sends/: Bitwarden Send のリンクに対応するファイルです。
  • config.json: 管理ページで保存したすべての設定です。
  • rsa_key.pem、および古いインストールでは rsa_key.derrsa_key.pub.der: ログイントークンに署名する鍵です。
  • icon_cache/: ダウンロードした Web サイトのアイコンです。Vaultwarden が必要に応じて再取得するため、省略できるのはこのディレクトリだけです。

Vaultwarden のデータベースは安全ですか?ファイルに実際に保存されている内容

この疑問には、2 つのコマンドで答えられます。どちらも今すぐ実行できます。

sudo apt update && sudo apt install -y sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select email from users;"
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select name from ciphers limit 1;"

1 つ目のコマンドは、ユーザーのメールアドレスを平文で表示します。2 つ目のコマンドは、アイテム名を 1 件表示します。表示例は次のとおりです。

2.k9Qw1nQ0y7Yy2Xw==|E1r0J3l5s7d9f1g3h5j7k9==|Lm4nOp6qRs8tUv0wXy2zAb4cDe6fGh8i=

アイテム名、ユーザー名、パスワード、メモは、送信前にクライアントで暗号化されます。そのため、サーバーには読み取れない暗号文が保存されます。2. プレフィックスは Bitwarden の暗号化方式を示します。その後に、初期化ベクトル(IV)、暗号文、MAC(メッセージ認証コード)が続きます。各要素は base64 でエンコードされ、| で区切られます。復号に使う鍵はアカウントのマスターパスワードから導出されます。マスターパスワードが、利用可能な形式でサーバーに送られることはありません。Vaultwarden と公式サーバーのどちらを運用する場合も、この部分は同じです。Vaultwarden とセルフホスト Bitwarden の比較で詳しく説明しています。

データベースの残りの部分は暗号化されていません。メールアドレス、アカウント名、パスワードのヒント、2 要素認証のリカバリーコードは平文で保存されます。作成時刻や、アイテムを所有する組織などのメタデータも同じ場所に保存されます。そのため、バックアップファイル自体が秘密情報です。これを入手した人は、ユーザーが誰かを把握できます。また、暗号化されたデータを、自分のハードウェアが可能な速度でオフライン攻撃できます。この事実が、後述するストレージのルールを決めます。コピーはサーバーから持ち出す前に暗号化します。

Vaultwarden の実行中に db.sqlite3 をコピーしてもバックアップにならない理由

Vaultwarden はデフォルトで SQLite を WAL モードで実行します(ENABLE_DB_WAL=true)。書き込みは最初に db.sqlite3-wal に記録され、チェックポイント処理が行われて初めて db.sqlite3 に反映されます。db.sqlite3 だけをコピーすると、取得できるのは最後のチェックポイント時点のデータベースです。そのため、10 分前に保存したパスワードが、警告なしにアーカイブから欠落することがあります。

cp を使って 3 つのファイルをすべてコピーしても、解決にはなりません。コピーはわずかに異なる時点で取得されるため、保存した WAL が、保存したメインファイルと一致しないページバージョンを示すことがあります。SQLite は一方のファイルからもう一方を復旧しますが、結果は正しくありません。問題に気付くのは、はるか後になってからです。

Error: database disk image is malformed

.backup ではこの問題を回避できます。SQLite の Online Backup API を使用するためです。SQLite のドキュメントでは、アクティブに使用されている可能性があるデータベースをコピーする方法として、この API が示されています。読み取りロック中にページを読み取り、書き込み処理によってファイルが変更された場合は最初からやり直します。そのため、ディスクに保存されるのは、ある一時点における一貫した状態です。

sqlite3 .backup でデータベースのコピーを作成する

sudo apt update && sudo apt install -y sqlite3
sudo install -d -m 700 /var/backups/vaultwarden
OUT=/var/backups/vaultwarden/db-$(date '+%Y%m%d-%H%M').sqlite3
sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 ".backup '$OUT'"
sudo sqlite3 "$OUT" "PRAGMA integrity_check;"

最後のコマンドは、単独の行に ok と出力します。それ以外の場合、そのコピーは使用できません。保存せず、以前のコピーも削除しないでください。この一連のコマンドは稼働中のサーバーに対して実行されるため、ログアウトは発生せず、コンテナも再起動しません。

sqlite3 ツールは Vaultwarden コンテナ内にありません。このイメージは debian:trixie-slim をベースに ca-certificatescurllibmariadb3libpq5openssl で構築されているため、docker exec vaultwarden sqlite3 ... は次のエラーで失敗します。

exec: "sqlite3": executable file not found in $PATH

代わりに、マウントされたパスに対してホスト上で実行してください。上記のコマンドはこの方法で実行しています。データが名前付きボリュームにある場合、docker volume inspect <name>/var/lib/docker/volumes/ 配下のホスト側パスを確認できます。

Vaultwarden には、version 1.32.1 以降、独自のバックアップコマンドも用意されています。サーバー上で次を実行します。

docker exec -it vaultwarden /vaultwarden backup

このコマンドは VACUUM INTO を実行し、db_YYYYMMDD_HHMMSS.sqlite3 をデータフォルダーに書き込みます。注意点が2つあります。コピーは同じディスク上で元のデータの隣に作成されるため、これはステージングであり、まだバックアップではありません。また、SQLite にのみ対応しています。MariaDB または PostgreSQL では The database type is not SQLite. Backups only works for SQLite databases で停止します。

忘れられやすいファイル

attachments/ には、識別しにくい名前で暗号文が保存されています。各添付ファイルに対応するデータベース行には、暗号化されたファイル名と、クライアントがファイルを復号するために必要な鍵素材が格納されています。データベースがなければ添付ファイルは読めないデータになり、添付ファイルがなければデータベースの項目をダウンロードできません。同じ実行で両方を取得してください。

config.json には、管理者ページから保存したすべての設定が格納され、対応する環境変数よりも優先されます。これは利点にも問題にもなります。古い config.json を復元すると、compose ファイルの設定が気付かないうちに上書きされます。また、このファイルには SMTP パスワードや管理者トークンが含まれる可能性があるため、機密情報として扱う必要があります。そのトークンは平文ではなく、Argon2id PHC (password hashing competition) 文字列として保存してください。docker run --rm -it vaultwarden/server /vaultwarden hash で生成できます。

rsa_key.pem は、クライアントのログイン状態を維持する JSON web token (JWT) に署名します。起動時にこのファイルがない場合、Vaultwarden は新しい鍵を生成します。そのため、古い鍵で署名されたすべてのトークンを検証できなくなり、すべてのクライアントがログアウトされます。Vault の内容は、master password から導出された鍵で暗号化されているため、その影響を受けずに保持されます。鍵ファイルを復元すれば、一斉ログアウトを回避できます。

sends/ には、Send リンクの配下にあるファイルが保存されています。これがないと該当するダウンロードだけが失敗し、それ以外には影響しません。

全体を1つのスクリプトにまとめる

#!/bin/bash
set -euo pipefail

DATA=/opt/vaultwarden/data
DEST=/var/backups/vaultwarden
STAMP=$(date '+%Y%m%d-%H%M%S')
STAGE=$(mktemp -d /tmp/vw-stage.XXXXXX)

install -d -m 700 "$DEST"
sqlite3 "$DATA/db.sqlite3" ".backup '$STAGE/db.sqlite3'"
test "$(sqlite3 "$STAGE/db.sqlite3" 'PRAGMA integrity_check;')" = "ok"
cp -a "$DATA"/rsa_key* "$STAGE/"
for extra in config.json attachments sends; do
  if [ -e "$DATA/$extra" ]; then cp -a "$DATA/$extra" "$STAGE/"; fi
done
tar -C "$STAGE" -czf "$DEST/vw-$STAMP.tar.gz" .
chmod 600 "$DEST/vw-$STAMP.tar.gz"
rm -rf "$STAGE"
tar -tzf "$DEST/vw-$STAMP.tar.gz"

/usr/local/sbin/vw-backup.shとして保存し、chmod 700してから、root として実行します。testの行は実際の処理を行います。sqlite3PRAGMA integrity_checkが破損を報告しても終了ステータス 0 を返すため、出力をokと比較することで、不正なコピーをスクリプトの失敗として扱えます。set -euo pipefailによって処理全体が停止するため、tarが破損したデータベースを正常なアーカイブとしてまとめてしまうこともありません。

最後のtar -tzfには、実際に取得した内容が一覧表示されます。最初に実行したときは、この内容を確認してください。./db.sqlite3./rsa_key.pem./config.json./attachments/が含まれ、./db.sqlite3-walが含まれていないことを確認します。journalctlの出力と失敗を報告する unit が必要な場合は、cron ではなくsystemd の service と timerを使って毎晩実行します。

バックアップを一時ディレクトリに復元して検証する

テストしていないバックアップは推測にすぎません。一時ディレクトリに復元すれば、1 分で確認でき、本番環境には何も影響しません。

sudo install -d -m 700 /tmp/vw-check
sudo tar -C /tmp/vw-check -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
ls -l /tmp/vw-check
sudo sqlite3 /tmp/vw-check/db.sqlite3 "PRAGMA integrity_check;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from users;"
sudo sqlite3 /tmp/vw-check/db.sqlite3 "select count(*) from ciphers;"
sudo du -sh /tmp/vw-check/attachments

重要なのは 4 つの結果です。integrity_checkok を出力します。ユーザー数は、把握しているアカウント数と一致します。暗号文の数は、sudo sqlite3 /opt/vaultwarden/data/db.sqlite3 "select count(*) from ciphers;" で確認できる稼働中の値に近くなります。使用中の vault で 0 になることはありません。attachments ディレクトリのサイズは、想定値とおおむね一致します。誰も attachments をアップロードしない場合は、この確認を省略できます。続いて sudo rm -rf /tmp/vw-check を実行します。このディレクトリには、すべてのデータのコピーがもう 1 つ保存されているためです。

手動でコピーしたデータフォルダーを復元する場合は、サーバーを起動する前に db.sqlite3-waldb.sqlite3-shm を削除してください。削除しないと SQLite は、別のコピーに属するログを使って復元したデータベースをリカバリしようとします。その結果、正常な状態で復元されたデータベースが破損します。上記のスクリプトが作成するアーカイブには、これらのファイルは含まれません。.backup が完全なデータベースを 1 つ書き出すためです。

サーバーへの復元

これらのコマンドは、コンテナを停止した状態で、自分のサーバー上で実行します。データフォルダーの内容を変更している間、Vaultwarden が書き込みを行わないようにしてください。

cd /opt/vaultwarden
docker compose stop vaultwarden
sudo mv data data.old.$(date '+%Y%m%d-%H%M%S')
sudo install -d -m 700 data
sudo tar -C data -xzf /var/backups/vaultwarden/vw-20260805-030000.tar.gz
sudo chown -R root:root data
docker compose start vaultwarden
docker compose logs --tail 20 vaultwarden

chown には、コンテナが実行するユーザーを指定する必要があります。標準イメージは root として実行されるため、compose ファイルで user: を設定していない限り、root:root で問題ありません。その場合は、その uid と gid を使用してください。サーバーが書き込めないデータフォルダーを指定すると、ログインページでどのリクエストも失敗し、ログにもその理由が記録されます。

正常に起動すると、最後に次の Rocket の行が表示されます。

[INFO] Rocket has launched from http://0.0.0.0:80

次に、ブラウザーからログインし、項目を開いて、添付ファイルを1つダウンロードします。ログインは成功するのに添付ファイルのダウンロードが失敗する場合、アーカイブにはデータベースは含まれていても attachments/ が含まれていなかったことを意味します。すべてを確認できるまで data.old.* を保持し、その後で削除してください。ロールバックも同じ3つの手順で行いますが、ディレクトリを逆に入れ替えます。

パスがここで示したものと一致しない場合は、VPS 向けの Vaultwarden インストールガイドに、これらのコマンドが前提とする compose ファイルが記載されています。

バックアップを置いてはいけない場所

  • データフォルダーと同じディスクには置かないでください。1 つのボリューム障害で両方のコピーが失われます。誤ったパスに対する 1 つの rm -rf でも同様です。
  • 同じサーバーには置かないでください。2 つ目のボリュームであっても同じです。攻撃者が root に到達すると、同じセッションでバックアップにも到達できます。
  • 暗号化せずにオブジェクトストレージへ置かないでください。アーカイブにはメールアドレス、パスワードのヒント、リカバリーコード、vault の暗号文が含まれており、オフラインで攻撃される可能性があります。
  • プロバイダーのスナップショットだけに依存しないでください。高速に復元できるため用意する価値はありますが、サーバーと同じアカウント内に保存されます。そのため、アカウントの問題が発生するとスナップショットも影響を受けます。

オフサイトコピーには restic が適しています。restic リポジトリは、アップロード前にマシン上で暗号化されるためです。サーバー上で次を実行します。

sudo apt install -y restic
export RESTIC_REPOSITORY=s3:https://s3.example.com/vaultwarden-backups
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic init
restic backup /var/backups/vaultwarden --tag vaultwarden
restic snapshots --tag vaultwarden
restic forget --tag vaultwarden --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune

restic の対象には稼働中のデータフォルダーではなく、アーカイブディレクトリを指定してください。これにより、事前に確認した一貫性のあるコピーがアップロードされます。リポジトリのパスワードは、保護対象のサーバー以外の場所に保管してください。そのパスワードを失うと、設計上、スナップショットを読み取れなくなります。ストレージが対応している場合は、書き込みはできても削除はできない認証情報をサーバーに付与してください。そうすれば、サーバーが侵害されても、そのサーバー自身の履歴を消去されずに済みます。VPS で restic バックアップを設定するでは、リポジトリとスケジュールを詳しく説明しています。まだ選択していない場合は、restic と BorgBackup の比較で選択基準を確認できます。

スケジュールに従ってリストアをテストする

月に 1 日を選びます。restic restore latest --tag vaultwarden --target /tmp/vw-check で最新のスナップショットを作業用ディレクトリに取得し、同じ PRAGMA integrity_check を実行します。同じ行数も確認し、日付と行数を記録します。6 か月間誰もリストアしていないバックアップは、状態が不明なバックアップです。障害発生時にその状態を確認することになりますが、それは確認するタイミングとして最悪です。

年に 1 回は、完全な手順を実施します。リストアしたデータフォルダーを使用して、予備のポートで 2 つ目の Vaultwarden コンテナを起動し、実際のアカウントでログインします。これにより、マスターパスワードの処理経路を最初から最後まで検証できます。行数の確認だけでは検証できません。同じスケジュールで restic check --read-data-subset=10% も実行すると、保存されたデータが単に一覧表示されるだけでなく、読み取り可能であることを確認できます。

FAQ

実行中の Vaultwarden で cp を使って db.sqlite3 をコピーできますか?

いいえ。Vaultwarden は SQLite を WAL モードで実行するため、直近の書き込みは db.sqlite3-wal にあり、まだ db.sqlite3 には反映されていません。メインファイルだけを cp すると、これらの書き込みが気付かないまま失われます。2 つのファイルを別々にコピーすると、後で Error: database disk image is malformed として表面化する不整合な組み合わせになる可能性があります。代わりに sqlite3 /path/db.sqlite3 ".backup '/path/out.sqlite3'" を使用してください。これは SQLite の Online Backup API を使用し、サーバーがサービスを継続している間に一貫性のある 1 つのファイルを作成します。

バックアップを取得するために Vaultwarden コンテナを停止する必要がありますか?

いいえ。それが .backup の目的です。実行中のサーバーでもデータベースのコピーは安全です。Attachments と Send files はユーザーがアップロードした時点で書き込まれるため、データベースのコピーと tar の間に追加されたファイルは、その夜のアーカイブに含まれない可能性があります。最悪の場合でも、失うのは 1 つの添付ファイルです。数秒間の停止が問題にならない場合は、スクリプトの前に docker compose stop、後に docker compose start を実行すれば、その可能性もなくせます。

rsa_key ファイルなしで復元するとどうなりますか?

Vaultwarden は起動時に新しい鍵を生成します。この鍵はセッションを維持する JSON web token(JWT)に署名するため、既存のすべてのトークンが検証できなくなり、すべてのクライアントがログアウトされて再度サインインする必要があります。Vault の内容には影響しません。Vault は RSA 鍵ではなく、各ユーザーのマスターパスワードから導出された鍵で暗号化されているためです。残りのデータフォルダーと一緒に rsa_key.pem も復元すれば、復元に気付かれることはありません。

バックアップアーカイブをそのままオブジェクトストレージにアップロードしても安全ですか?

いいえ。アイテム名、パスワード、メモは暗号文ですが、メールアドレス、アカウント名、パスワードのヒント、2 要素認証のリカバリーコードはデータベース内に平文で保存されています。また、オフライン攻撃者は自分のペースで暗号文を解析できます。アーカイブがマシンから出る前に暗号化してください。restic リポジトリを使えば暗号化できます。gpg --symmetric --cipher-algo AES256 vw-20260805-030000.tar.gz は、任意のストレージに渡せる暗号化済みの 1 つのファイルを作成します。

PostgreSQL または MariaDB 上の Vaultwarden をバックアップするにはどうすればよいですか?

SQLite 用の手順は適用できず、組み込みコマンドは The database type is not SQLite. Backups only works for SQLite databases で拒否されます。pg_dump または mysqldump などのデータベース固有ツールでデータベースをダンプし、その他の規則はすべて同じにしてください。ダンプは attachments/sends/config.jsonrsa_key ファイルとともに 1 つのアーカイブに含め、同じ実行で取得し、暗号化してください。また、作成元のサーバー以外の場所に保存してください。