Vaultwardenは安全?管理トークンとバックアップの対策
Vaultwardenはボルト内容をクライアント側で暗号化しますが、メールアドレスなどのメタデータは平文です。管理トークンとバックアップファイルを重点的に対策します。
Vaultwarden は安全ですか?簡単な回答
Vaultwarden は、最も重要な部分で安全です。各 vault 項目は、サーバーに届く前にデバイス上で暗号化されるためです。サーバーには、暗号化された vault の内容と、平文のアカウントメタデータが保存されます。データベース全体をコピーした人物も、内容を読むには vault の暗号鍵が必要です。ただし、アカウントのメールアドレスとパスワードのヒントはすぐに取得できます。
この回答は多くの要素を含んでいます。そして、問題が起きるのは設定した部分です。推測しやすいトークンで保護された管理パネル。インターネット全体に公開されたコンテナポート。平文の config.json。同じホストのホームディレクトリに置かれたバックアップの tarball。これらはいずれも暗号化の問題ではありません。セルフホストの vault が空にされる原因は、すべてこれらです。
以下では、インストールが正常に完了していることを前提にします。まだインストールしていない場合は、先にVPS 向け Vaultwarden インストールガイドに従ってセットアップしてください。その後、この一覧を上から順に進めてください。
サーバーが実際に保存するデータ
Vaultwarden は Bitwarden のデータモデルを実装しています。ボルト項目の名前、ユーザー名、パスワード、メモ、URI は、リクエストを送信する前に、クライアント上でランダムに生成された項目キーを使って暗号化されます。このキーはユーザーまたは組織の対称鍵で保護され、パスワードから導出された鍵がユーザーの対称鍵を保護します。添付ファイルの内容には別の添付ファイルキーが使われ、そのキーは項目キーで保護されます。サーバーが受け取るのは、不透明なデータに UUID(汎用一意識別子)が付加されたものです。
暗号文にならないデータもあります。どのデータが該当するかを正確に把握してください。
- アカウントのメールアドレスとマスターパスワードのヒントは、平文で保存されます。
- KDF(鍵導出関数)の設定と salt は、次回のログイン時にクライアントが鍵を再構築するために必要です。
- クライアントが送信するマスターパスワードのハッシュに対する、サーバー側のハッシュ。ログイン自体の認証に使われます。
- メタデータ: 組織への所属、デバイス名、最終ログイン時刻。
- 認証アプリを使う 2 要素ログインの TOTP secret。サーバーは入力されたコードと照合する期待値を計算する必要があるため、
twofactorテーブルに暗号化されずに保存されます。これは、ボルト項目の内部に保存する TOTP(時間ベースのワンタイムパスワード)secret とは別のものです。ボルト項目内の TOTP secret は、他のフィールドと同じように暗号化されます。
データフォルダーは小規模です。Docker のインストールでは、/data にマウントした場所が該当します。
sudo ls -l /vw-data/db.sqlite3 には、ほぼすべての状態が保存されます。attachments/ には UUID ディレクトリごとにアップロードファイルが保存され、データベーステーブルの外部に置かれます。sends/ には Send の添付ファイルが保存されますが、ここは一時的な用途を想定しています。icon_cache/ は破棄可能なデータです。rsa_key.pem とその関連ファイルは、ログイン済みユーザーの JWT(JSON Web Token)に署名します。そのため、この秘密鍵のコピーがあれば、ボルトへのログインセッションを偽造できます。config.json には、管理者ページから保存した設定が格納されます。SMTP の認証情報は平文で保存され、設定済みの管理者トークン値も含まれます。以下の Argon2 の手順に従うと、平文のパスワードではなく管理者トークンのハッシュが保存されます。
したがって、実際の脅威モデルで重視すべきなのは、ネットワーク暗号ではなくファイルシステムへのアクセスです。この 1 つのディレクトリへの読み取りアクセスを許すと、全ユーザーのメールアドレス、ログイン用 2FA secret、セッションを偽造できる鍵、そして時間をかけて攻撃できる全ボルトのオフラインコピーが取得されます。以下の手順はすべて、第三者をこのディレクトリに侵入させないためのものです。
管理者トークンを先に修正する
/admin は完全な管理パネルです。ユーザー一覧、招待、削除、すべての実行時設定を操作できます。保護に使われるのは 1 つの共有 Secret だけです。ユーザー名はありません。ユーザーごとの二要素認証もありません。
古いガイドでは、openssl rand -base64 48 を使って ADMIN_TOKEN を生成するよう案内しています。これでも動作しますが、設定した場所には Secret が平文で残ります。Vaultwarden は Argon2 PHC(password hashing competition)形式の文字列も受け付けるため、保存する値をハッシュにできます。実行中のコンテナに対して次を実行します。
docker exec -it vaultwarden /vaultwarden hash実行中のコンテナに一切変更を加えずに行う場合は、次を実行します。
docker run --rm -it vaultwarden/server /vaultwarden hashパスワードを 2 回入力すると、$argon2id$ で始まる行が出力されます。ベアメタル環境では ./vaultwarden hash を実行します。argon2 CLI を直接使う場合は、プロジェクトが OWASP の最小パラメーターを次のように案内しています。
echo -n 'MySecretPassword' | argon2 "$(openssl rand -base64 32)" -e -id -k 19456 -t 2 -p 1ここで、1 時間を失いやすい問題があります。PHC 文字列には $ 文字が多数含まれます。Docker Compose は $ を変数展開として扱います。これをエスケープせずに environment: ブロックへ貼り付けると、コンテナに渡る値が壊れます。そのため、正しいと分かっているトークンでも /admin が拒否します。安全な形式は 2 つあります。docker-compose.yml では、すべての $ を 2 つ重ねます。
environment:
ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$UUZxK1FZMkZoRHFQRlVrTXZvS0E3bHpNQW55c2dBN2NORzdsa0Nxd1JhND0$$cUoId+JBUsJutlG4rfDZayExfjq4TCt48aBc9qsc3UI.env ファイルではエスケープは不要ですが、単一引用符を使用します。
ADMIN_TOKEN='$argon2id$v=19$m=65540,t=3,p=4$MmeKRnGK5RW5mJS7h3TOL89GrpLPXJPAtTK8FTqj9HM$DqsstvoSAETl9YhnsXbf43WeaUwJC6JhViIvuPoig78'次に、管理パネルへのリクエスト数を制限し、セッション時間を短くします。
ADMIN_RATELIMIT_SECONDS=300
ADMIN_RATELIMIT_MAX_BURST=3
ADMIN_SESSION_LIFETIME=20これにより、クライアント IP ごとに管理者ログインを 3 回までバーストで許可し、その後は 5 分ごとに 1 回分ずつ上限を回復します。正しいパスワードも誤ったパスワードもカウントされます。リミッターがパスワード確認より前に実行されるためです。上限を超えたリクエストには HTTP 429 と Too many requests, try again later. が返されます。管理セッションはログインしてから 20 分後に期限切れになります。管理パネルを使い続けていても、操作によって有効期限は延長されません。
これよりもよい方法は、ページ自体を無効にすることです。ほとんどのインスタンスで必要になるのは、SMTP を設定して最初のユーザーを招待するときだけです。その後は不要です。無効にするには、ADMIN_TOKEN と DISABLE_ADMIN_TOKEN のどちらも設定せず、config.json から "admin_token" キーを削除して、コンテナを再作成します。ファイルからキーを削除することが重要です。管理ページが設定をそこへ書き込み、config.json の内容が環境変数より優先されるためです。変数だけを削除しても、ページは開いたままです。
ドメインを見つけられる前に登録を停止する
SIGNUPS_ALLOWED=false
SIGNUPS_VERIFY=true
SHOW_PASSWORD_HINT=falseSIGNUPS_ALLOWED の既定値は true です。このままにすると、ドメインにアクセスできる人は誰でもアカウントを作成でき、そのデータはあなたのデータと同じ db.sqlite3 に保存されます。false に設定し、管理ページから招待を送ってユーザーを追加してください。SMTP を設定すると招待メールが配信され、設定しない場合は、サーバーにそのアドレスへの招待が記録されます。INVITATIONS_ALLOWED も既定では true で、組織の管理者が他のユーザーを招待できます。ユーザーを信頼できる場合は問題ありませんが、単一ユーザー用のインスタンスでは false にしてください。特定のドメインだけを登録対象にする場合、SIGNUPS_DOMAINS_WHITELIST=example.com はオープン登録より制限が強いものの、招待方式よりはるかに弱い設定です。
SHOW_PASSWORD_HINT は既定で false であり、そのままにしてください。これを有効にした状態で SMTP を設定していない場合、ログインフォームに有効なメールアドレスを入力すると、そのアカウントのマスターパスワードのヒントが返されます。これによりヒントが漏えいするだけでなく、そのアドレスが存在することも確認されます。
インスタンスで一定期間でもオープン登録を有効にしていた場合は、自分だけがアカウントを持っていると判断する前に、管理ページを開いてユーザー一覧を確認してください。
意図せず公開してしまったポート
Dockerイメージはコンテナ内部で80番ポートを待ち受けます。ベアメタルインストール時のデフォルトは ROCKET_PORT=8000 です。ドキュメント化された実行コマンドでは、以下のように公開されます。
--publish 127.0.0.1:8000:80127.0.0.1: プレフィックスがすべての要点です。代わりに -p 8000:80 と記述すると、Dockerは 0.0.0.0 にバインドします。これは nat テーブルにDNAT(宛先ネットワークアドレス変換)ルールを書き込むことで実現されます。これらのルールは ufw が管理する filter チェーンよりも先に評価されるため、ufw status はポートを拒否済みと報告しますが、実際にはポートがインターネットに対して応答し続けてしまいます。この仕組みの詳細は Dockerポートがufwをバイパスする仕組みのガイド を参照してください。
実際に何が待ち受けているかを確認します。
sudo ss -tlnp | grep 8000正常な結果は、127.0.0.1:8000 にバインドされた1行のみが表示される状態です。0.0.0.0:8000 にバインドされている行がある場合、vaultが直接公開されています。マッピングを修正し、コンテナを再作成してください。ポートバインディングはコンテナ作成時に固定されるため、docker compose restart では変更できないからです。
docker compose up -d --force-recreate古いガイドには、もう1つポートが残っています。WebSocket用の独立した3012番ポートです。Vaultwarden 1.31.0でこのサポートは削除されました。通知トラフィックがメインのHTTPポートへ移行したためです。WEBSOCKET_ENABLED および WEBSOCKET_PORT は 1.29.0 以降非推奨となっており、無視されます。現在のスイッチは ENABLE_WEBSOCKET であり、デフォルト値は true です。ファイアウォールやcomposeファイルで3012番ポートを開放している場合は、閉じてください。
TLS は Rocket ではなくリバースプロキシで終端する
Vaultwarden は Web フレームワークの Rocket を通じて TLS(トランスポート層セキュリティ)を自身で提供できますが、プロジェクトは本番環境でこの構成を使わないよう案内しています。Rocket 組み込みの TLS は厳密な SNI(Server Name Indication)に対応していません。そのため、セキュリティ強化の案内でも、インスタンスにはホスト名でアクセスし、単独の IP アドレスではアクセスしないよう求めています。パブリック IP の範囲は常にスキャンされています。IP アドレスで応答する vault は、発見される vault です。
nginx の server block で重要な部分は次のとおりです。
client_max_body_size 525M;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}nginx の client_max_body_size のデフォルト値は 1m です。そのため、上書きしない場合に失敗するのは、この上限を超えるリクエストボディだけです。nginx はクライアントに HTTP 413 を返し、client intended to send too large body: ... bytes をログに記録します。拒否されたアップロードは Vaultwarden に到達しません。Upgrade と Connection のヘッダーは、WebSocket のハンドシェイクを /notifications/hub に渡します。これらを削除しても vault 自体は動作しますが、WebSocket 通知がブラウザーおよびデスクトップクライアントに届かなくなります。モバイルクライアントは、別途設定されたプッシュ通知を使用します。
Caddy では設定が短く、証明書も自動的に取得します。
vw.example.com {
reverse_proxy 127.0.0.1:8000 {
header_up X-Real-IP {remote_host}
}
}続いて、Vaultwarden にその情報を指定します。
DOMAIN=https://vw.example.com
IP_HEADER=X-Real-IPIP_HEADER のデフォルト値はすでに X-Real-IP です。そのため、プロキシが実際にこのヘッダーを設定していることを確認します。Vaultwarden は、IP_HEADER_TRUSTED_PROXIES に列挙されたアドレスからのヘッダーだけを受け入れます。この値のデフォルトは local です。これは、同じホスト上のプロキシや同じコンテナネットワーク上のプロキシを含む、グローバルではないすべてのアドレスを意味します。パブリックアドレスから接続するプロキシは、ここに追加する必要があります。ヘッダーがない場合や、信頼されていないアドレスから送られた場合、すべてのログ行とログインのレート制限でプロキシ自身のアドレスが使用されます。その結果、1 人の攻撃者による失敗が、インスタンス上のすべてのユーザーに対してカウントされます。IP_HEADER=X-Forwarded-For を使用する場合、1.37.4 は信頼されたプロキシではない右端のアドレスを使用します。そのため、nginx の前段に CDN がある場合も含め、チェーン内のすべてのプロキシを列挙してください。DOMAIN も実際の https URL に設定してください。Vaultwarden はこの値から招待リンクとアカウント復旧リンクを生成し、WebAuthn セキュリティキーはこの origin に紐付けられるためです。
見落とされがちな点が 1 つあります。WebSocket 接続は、/notifications/hub?access_token=[JWT] としてセッション token をクエリ文字列に含めます。この情報は、プロキシのアクセスログに平文で記録されます。ログ形式で access_token パラメーターをマスクするか、そのログが管理下にない場所へ送信されないようにしてください。
ログインエンドポイントでブルートフォース攻撃を阻止する
レート制限はデフォルトで有効です(LOGIN_RATELIMIT_SECONDS=60、LOGIN_RATELIMIT_MAX_BURST=10)。攻撃者の試行速度を下げますが、攻撃そのものは阻止しません。fail2ban なら阻止できます。ただし、Vaultwarden のログイン失敗を読み取れる場所に記録する必要があります。Docker ではログファイルを使う方法が簡単です。Vaultwarden は標準ではログファイルを出力しません。
LOG_FILE=/data/vaultwarden.log
LOG_LEVEL=info
EXTENDED_LOGGING=trueパスワードによるログインに失敗すると、次の形式の行が出力されます。フィルターはこの文字列に一致させます。
[2026-08-07 09:14:22.000][vaultwarden::api::identity][ERROR] Username or password is incorrect. Try again. IP: 203.0.113.10. Username: user@example.com.フィルターを /etc/fail2ban/filter.d/vaultwarden.local に記述します。
[INCLUDES]
before = common.conf
[Definition]
failregex = ^.*?Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =jail を /etc/fail2ban/jail.d/vaultwarden.local に記述します。
[vaultwarden]
enabled = true
backend = pyinotify
port = 80,443
filter = vaultwarden
banaction = %(banaction_allports)s
logpath = /vw-data/vaultwarden.log
maxretry = 3
bantime = 14400
findtime = 14400Vaultwarden をコンテナではなく systemd サービスとして実行している場合は、LOG_FILE を省略できます。プロジェクトの fail2ban wiki では、backend = systemd で journal を読み取り、logpath の代わりに Vaultwarden unit の journalmatch を使用します。
管理画面を残している場合は、failregex が ^.*Invalid admin token\. IP: <ADDR>.*$ となる 2 つ目の jail を追加してください。管理画面のログイン失敗は別のメッセージで記録されるため、ログイン用フィルターでは検出できません。その後、設定を確認します。
sudo systemctl restart fail2ban
sudo fail2ban-client status vaultwarden正常に動作している jail では、File list にログファイルが表示され、Currently failed: 0 が報告されます。別のネットワークから誤ったパスワードを 3 回入力すると、そのカウンターが増え、アドレスが Banned IP list に表示されます。カウンターがまったく増えない場合、通常の原因は logpath です。ここにはコンテナ内の /data/... パスではなく、ホスト上のファイルパスを指定する必要があります。もう 1 つの一般的な原因は、X-Real-IP がないか信頼されていないことです。その場合、すべての ban の対象が自分自身のプロキシになります。すでに実行しているはずの SSH jail を含む残りの設定については、Ubuntu 24.04 の fail2ban ガイドを参照してください。
マスターパスワードが依然としてシステム全体を左右する
クライアント側の暗号化では、マスターパスワードがユーザーの対称鍵を保護します。データベースを攻撃者にコピーされたインスタンスで短いマスターパスワードを使っている場合、この投稿の内容だけでは保護できません。攻撃者は、そのコピーに対して、自分のハードウェアが可能な速度でオフライン攻撃を実行できるためです。サーバー側の設定で、攻撃者自身のマシンに到達することはできません。
PASSWORD_ITERATIONS=600000 は誤解しやすい設定です。これは、クライアントが送信するログインハッシュをサーバー側でハッシュ化する際の反復回数を設定します。プロジェクトの設定テンプレートには、値を変更すると、既存ユーザーには次回のログイン時に適用されると記載されています。この設定はクライアント側の KDF には影響しません。保管庫へのオフライン攻撃のコストを決めるのは、クライアント側の KDF です。この設定は各アカウントに属し、Web 保管庫の Settings、Security、Keys で変更します。変更すると鍵が再暗号化されます。Bitwarden のドキュメントによると、2026.2.1 以降のクライアントは、ユーザーがマスターパスワードでログインまたはロック解除した際、600,000 回未満の PBKDF2 設定を自動的に引き上げます。2026 年 10 月 10 日時点では、この動作が Vaultwarden サーバーで機能することは確認されておらず、Argon2id の設定が変更されることもありません。そのため、数年前に登録したユーザーは、古くて低い KDF 設定のままになっている可能性があります。確認し、ユーザーにも伝えてください。サーバー側で代わりに変更することはできません。
次に、アカウントごとに二要素認証を有効にします。二要素認証は保管庫の暗号化には関与しないため、暗号文は保護しません。ただし、盗まれたパスワードだけではログインしてコピーを同期できない状態にします。メールを設定している場合、REQUIRE_DEVICE_EMAIL=true により、新しいデバイスからのログイン時にメール送信が必須になります。送信に失敗すると、ログインも失敗します。メールサービス自体を完全に無効にしている場合、この設定で新しいデバイスからのログインを阻止することはできません。
自己ホスト型 vault でバックアップを誤ると、これまでの対策が無効になります
ホームディレクトリ内の同じ VPS に tar czf のデータフォルダーを保存すると、上記の対策がすべて無効になります。そのアーカイブには、全ユーザーの暗号文を含む db.sqlite3、ログインセッションを偽造できる rsa_key.pem、設定済みの admin token 値と SMTP パスワードを含む config.json が格納されています。Argon2 の手順に従うと admin パスワードのハッシュは保存されますが、SMTP パスワードは平文のままです。その 1 つのファイルへの読み取りアクセスだけで、アカウントメタデータ、サーバーシークレット、暗号化された vault データが漏えいします。
守るべきルールは 2 つです。アーカイブをサーバー外へ移します。サーバーから移す前に暗号化します。
正確性に関する問題もあります。サービスの実行中に cp を使って db.sqlite3 をコピーすると、書き込み途中のファイルが作成されることがあります。そのファイルは開けず、復元するまで問題に気付けません。SQLite 自身のスナップショットを使用してください。
sqlite3 /vw-data/db.sqlite3 ".backup '/tmp/vw-db-backup.sqlite3'"誰もテストしないことが多い復元側については、Vaultwarden のバックアップと復元ガイドで説明しています。
ホスト型 Bitwarden と引き換えに失うもの
率直に整理します。Bitwarden のホスト型サービスは、運用を本業とする担当者が運用しており、第三者による監査結果も公開されています。セルフホストでは、その体制の代わりに自分でパッチ適用のペースを維持する必要があります。
Vaultwarden は、通常のリリースとしてセキュリティ修正を提供しており、その頻度も高いものです。プロジェクトのリリースフィードを 2026 年 11 月 2 日に確認した時点では、現在のリリースは 1.37.5 で、2026 年 11 月 1 日に公開されています。リリースノートには Medium のセキュリティアドバイザリ 2 件が記載され、できるだけ早く更新するよう強く推奨されています。また、web vault 2026.7.1 以降で 2 段階ログイン方式を有効または無効にする場合と、2026.9.0 以降で emergency access によってアカウントを引き継ぐ場合にも、このリリースが必要です。前のリリースである 1.37.4 は 2026 年 10 月 5 日に公開され、7 件のセキュリティアドバイザリに対応しました。最も深刻なものは High と評価されています。これは、2026 年 7 月 24 日の 1.37.0 以降で 4 回目のリリースでした。1.37.1 は 7 月 29 日、1.37.2 は 8 月 22 日に公開され、後者は 2026.8.0 以降のクライアントに必要です。1.37.3 は 9 月 13 日に公開され、認証情報または 2FA が変更されたときに 2FA の remember token を無効化し、prelogin および auth request のエンドポイントにレート制限を適用します。完全には信頼できない管理者がいる organisation を運用している場合、1.37.4 のリリースノートでは、更新後に各 organisation の API key もローテーションするよう推奨されています。1.37.4 では、X-Forwarded-For から読み取るアドレスも変更されています。これは上記の reverse proxy のセクションで説明しています。1 年前に構築して、そのまま忘れていたインスタンスは、1 年前のコードを実行しています。latest タグだけでは不十分です。実行中のコンテナは、docker compose pull を実行して再作成するまで、起動時に使用した image を保持します。ホストパッケージには Ubuntu で unattended upgrades を有効にする 設定を適用し、コンテナの更新は、実際に確認するカレンダーのリマインダーに登録してください。
率直に判断すれば、ここで使われている暗号方式は Bitwarden の設計に基づいており、十分な安全性を保っています。一方、運用上のリスクはすべて自分に移ります。パッチを適用し、別の場所にもバックアップを保存できるなら、自分で管理する VPS 上の Vaultwarden インスタンスは、パスワードを保管する場所として妥当です。その 2 つの習慣を実行できないのであれば、ホスト型サービスに料金を支払い、別の作業に時間を使ってください。機能ごとの比較は Vaultwarden とセルフホスト型 Bitwarden の比較 にあります。
コンテナの基盤となるホストを堅牢化する
Vaultwarden は Linux ホスト上で動作する 1 つのプロセスです。そのホストの root は、アプリケーションの設定に関係なく /vw-data を読み取れます。compose ファイルで user: "1000:1000" を指定し、権限のないユーザーとしてコンテナを実行します。データフォルダーの所有者も一致させます。コンテナが書き込まないものは、:ro を指定して読み取り専用でマウントします。
次に、入口を閉じます。VPS での SSH 堅牢化では、鍵のみのログインとパスワード認証の無効化について説明しています。これにより、ここまでの対策をすべてすり抜ける単純な攻撃を防げます。
FAQ
パスワードを盗まれた Vaultwarden データベースから読み取られる可能性はありますか?
直接読み取られることはありません。各 vault 項目はクライアント側でランダムに生成された鍵を使って暗号化され、ユーザーまたは組織の対称鍵によって保護されます。パスワードから派生した鍵は、ユーザーの対称鍵を保護します。db.sqlite3 には、暗号化された vault の内容と、平文のアカウントメタデータが格納されています。直ちに取得されるのは、各アカウントのメールアドレス、パスワードヒント、KDF 設定、ログインおよびデバイスのメタデータ、さらに twofactor テーブルにあるログイン用 TOTP Secret です。サーバーが期待されるコードを計算する必要があるため、これらは暗号化されずに保存されます。また、vault の暗号文にはオフラインで期限なく攻撃できます。そのため、結果を左右するのは master password の長さです。
ADMIN_TOKEN を使用するべきですか?それとも管理画面を完全に無効化するべきですか?
可能であれば無効化してください。ほとんどのインスタンスでは、SMTP の設定とユーザーの招待に一度使用するだけで、その後は必要ありません。無効化するには、ADMIN_TOKEN と DISABLE_ADMIN_TOKEN のどちらも設定せず、config.json から "admin_token" キーを削除してから、コンテナを再作成します。環境変数だけを削除しても不十分です。管理画面で書き込まれた設定は config.json に保存され、そちらが優先されるためです。管理画面を有効にしておく場合は、平文のランダム文字列ではなく、vaultwarden hash で生成した Argon2 hash として token を保存し、ADMIN_RATELIMIT_MAX_BURST=3 を設定してください。
ADMIN_TOKEN は正しいのに /admin で拒否されます。何が問題ですか?
ほとんどの場合、$ の補間が原因です。Argon2 PHC string には複数の $ 文字が含まれます。Docker Compose は docker-compose.yml environment: block 内でこれらを変数として展開するため、ファイル上の値が正しく見えても、コンテナには壊れた値が渡されます。compose file 内の $ をすべて $$ に倍にするか、値を単一引用符で囲んだ .env file に移してください。この場合、エスケープは不要です。その後、コンテナを再作成してください。再起動だけでは環境変数の変更は反映されません。
通知用に port 3012 も開く必要がありますか?
いいえ。port 3012 の WebSocket traffic のサポートは、通知がメインの HTTP port に移行したため、Vaultwarden 1.31.0 で削除されました。WEBSOCKET_ENABLED と WEBSOCKET_PORT は 1.29.0 以降 deprecated であり、無視されます。現在の設定は ENABLE_WEBSOCKET で、デフォルト値は true です。firewall で 3012 を閉じ、compose file からも削除してください。そのうえで、reverse proxy が Upgrade と Connection headers を転送することを確認します。ブラウザーと desktop の WebSocket 通知はこれらに依存します。モバイルクライアントは、別途設定した push 通知を使用します。