SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-26

VaultwardenをVPSで安全に自宅運用する方法

VaultwardenをDockerでVPSに構築し、Bitwarden公式クライアントを接続する手順です。HTTPS、管理者トークン、Fail2ban、バックアップと復元テストまで解説します。

構築するもの

完全に自分で管理するパスワードマネージャーです。HTTPS を終端するリバースプロキシの背後で、Vaultwarden を小さな 1 つのコンテナとして実行します。スマートフォン、ノートパソコン、ブラウザーでは、公式の Bitwarden アプリをこのサーバーに接続します。Vaultwarden は Rust で Bitwarden サーバー API を再実装しており、bitwarden.com と同じプロトコルを使用します。そのため、すべての公式クライアントを変更せずに接続できます。一方で、公式の複数コンテナ構成が必要とする容量に対し、メモリ使用量を約 100 MB に抑えられます。

インストール自体は、Compose の 12 行程度で完了します。実際に重要で、問題が起きやすい点は 3 つです。Web ボルトを初めて開く前に TLS を有効にすること、自分のアカウントを作成したら直ちに一般公開のサインアップを無効にすること、そしてデータボリュームをバックアップして復元テストを行うことです。その 1 つのディレクトリに、自分が管理するすべてのパスワードが保存されるためです。

前提条件と、事前に把握しておくべき注意点

  • Docker Engine と Compose plugin をインストールした VPS。root または sudo を使える、クリーンな Ubuntu 24.04 KVM 環境を想定します。RAM は 512 MB で十分です。1 GB なら余裕があります。これは実行できるサービスの中でも特に軽量で、セルフホストする価値があるサービスの候補でも上位に入ります。ただし、同じ VPS で動かす他のサービスも考慮して VPS の容量を決めてください。PhotoPrism や Immich のようなセルフホスト型フォトライブラリを同じ VPS に追加すると、必要な RAM は数 GB になります。一方、Vaultwarden による増加はわずかです。同じ計算は、後から追加するメディア用フロントエンドにも当てはまります。Jellyfin のライブラリを歩いて回れる 90 年代のレンタル店のように仕立てる場合は、常時稼働するコンテナが 1 つ増え、同じ予算内でトランスコード用の余裕も必要になります。
  • VPS を vault.example.com 指す A レコード(IPv6 を利用する場合は AAAA レコードも)が設定されたドメイン。TLS 証明書はこの正確な名前に対して発行されるため、開始前に DNS で名前解決できる必要があります。
  • インターネットからアクセスできる 80 番ポートと 443 番ポート。これらは reverse proxy で終端し、Vaultwarden が直接待ち受けてはいけません。80 番ポートは ACME 証明書チャレンジと HTTP から HTTPS へのリダイレクトにのみ使用します。
  • 最初に把握しておくべき最大の注意点は、Bitwarden クライアントが HTTPS ではないサーバーへの接続を拒否することです。「まず http でテストする」という方法はありません。この方法が機能しない具体的な理由を次に説明します。

公式の Bitwarden スタックではなく Vaultwarden を選ぶ理由

同じクライアントを使えて、必要なリソースは大幅に少なくなります。公式のセルフホスト型 Bitwarden は、MSSQL、Nginx、Identity、Api、Admin など複数のコンテナをまとめて提供し、約 2 GB の RAM を必要とします。Vaultwarden は単一のバイナリで、既定ではすべてのデータを SQLite データベースに保存します。アイドル時のメモリ使用量も数十 MB 程度です。個人、家族、小規模チームで使うなら、Vaultwarden が明確な選択肢です。Bitwarden API を忠実に実装しているため、データを Vaultwarden と bitwarden.com の間で移行できます。

一方で、エンタープライズ向け機能の大部分は利用できません。SCIM プロビジョニングには対応していません(ただし、試験的な OpenID Connect SSO は 1.35.0 で導入されました)。また、運用者は自分自身であるため、パッチ適用、HTTPS、バックアップを自分で管理する必要があります。このガイドでは、この 3 つの作業を扱います。

HTTPS は任意ではない

Bitwarden の Web vault とブラウザー拡張機能は、Web Crypto API(window.crypto.subtle)を使用してブラウザー内で暗号鍵を導出します。ブラウザーが crypto.subtle を公開するのは、secure context、HTTPS、または特別なケースである http://localhost に限られます。通常の http://vault.example.com では undefined であるため、アプリが鍵を導出した瞬間に例外が発生し、コンソールには次のように表示されます。

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'importKey')

ページがハングするか、一般的な暗号エラーが表示され、ログインできません。desktop、mobile、browser の各 client は self-hosted URL に対して独自のチェックを実行します。http または到達不能な endpoint に対しては、次のエラーで接続を拒否します。

This is not a recognized Bitwarden server. You may need to check with your provider or update your server.

どちらも原因は同じです。有効な HTTPS がありません。そのため、最初に TLS を構成し、vault は http では一度も開きません。動作確認のためであっても同様です。

手順 1、DNS とリバースプロキシ(TLS を先に設定)

レコードを VPS に向け、正しいアドレスへ名前解決されることを確認します。

dig +short vault.example.com

出力される行は VPS の IP でなければなりません。空白または誤った値になる場合は DNS を修正し、TTL が切れるまで待ちます。名前解決できない名前に対しては、証明書の発行に失敗します。

HTTPS フロントエンドには Traefik を使用します。Traefik は Let's Encrypt の証明書を自動的に発行・更新でき、Compose にそのまま組み込めます。まだ運用していない場合は、先にTraefik のリバースプロキシと自動 TLS の設定に従ってください。この設定により、外部 Docker network(以下の proxy)と ACME resolver(letsencrypt)が作成されます。Vaultwarden サービスはこれらに接続します。手動で発行した証明書を使う通常の nginx でも、Vaultwarden 側の動作は同じです。

Traefik の代わりに nginx と Certbot を使用しますか? Vaultwarden を 127.0.0.1:8080 に配置し(サービスに ports: ["127.0.0.1:8080:80"] を追加し、Traefik の labels を削除します)、証明書を発行して Vaultwarden へ proxy します。証明書の部分は、Certbot と nginx で Let's Encrypt 証明書を発行する方法で説明しています。追加で重要なのは、notifications パスでの WebSocket upgrade です。

server {
    listen 443 ssl;
    server_name vault.example.com;

    client_max_body_size 525M;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

X-Real-IP の行に注目してください。これにより、Fail2ban は 127.0.0.1 ではなく実際の攻撃元を認識できます。Traefik と nginx のどちらを前段に置く場合でも、それ以外の手順は同じです。

手順2、Compose ファイル

最初にプロジェクトディレクトリを作成します。このガイドでは /opt/vaultwarden を使用します。これにより、Compose プロジェクト名とデータボリューム名を vaultwarden_vw-data として予測可能にできます。以下の Fail2ban とバックアップの手順は、この正確な名前に依存します。

sudo mkdir -p /opt/vaultwarden
cd /opt/vaultwarden

そのディレクトリ内に、管理者 Secret 用の .env と Compose ファイルを作成します。

# .env
ADMIN_TOKEN=paste-a-strong-token-here

openssl rand -base64 48 でこのトークンを生成し、貼り付けます。(より強固なハッシュ形式については次で説明します。最初は長いランダム文字列で問題ありません。)

# docker-compose.yml
services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    environment:
      DOMAIN: "https://vault.example.com"
      SIGNUPS_ALLOWED: "true"          # closed in Step 4, keep true just to register
      ADMIN_TOKEN: "${ADMIN_TOKEN}"
      IP_HEADER: "X-Forwarded-For"     # X-Real-IP if your proxy sends that instead
      LOG_FILE: "/data/vaultwarden.log"
      LOG_LEVEL: "warn"
    volumes:
      - vw-data:/data
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.vw.rule=Host(`vault.example.com`)"
      - "traefik.http.routers.vw.entrypoints=websecure"
      - "traefik.http.routers.vw.tls.certresolver=letsencrypt"
      - "traefik.http.services.vw.loadbalancer.server.port=80"

volumes:
  vw-data:

networks:
  proxy:
    external: true

このファイルには、設計全体を支える重要な点が2つあります。ports: のマッピングはありません。そのため、Vaultwarden には Traefik とその TLS 経由でのみアクセスできます。ホスト上でポートを公開すると、誤って http で vault を提供することになります。また、DOMAIN には完全な公開 HTTPS URL を指定する必要があります。この値は添付ファイルのリンク、WebAuthn 2FA、通知エンドポイントに埋め込まれるため、サイトが読み込めても、値が間違っていたり http だったりするとこれらが動作しません。latest タグは、通常の「latest しない」というルールに対する意図的な例外です。Vaultwarden は安定版リリースを単一のローリングイメージとして提供し、:testing は別個のプレリリースチャネルです。そのため、目的を持って更新し、pull する前にリリースノートを確認してください。ただし、この例外の適用範囲は限定的です。長期間稼働するコンテナの多くは、正確なタグに固定する方が適しています。これにより、同じ VPS 上で常時稼働する自己ホスト型エージェントを再起動や pull の後も予測可能な状態に保てます。

起動してログを監視します。

docker compose up -d
docker compose logs -f vaultwarden

正常に起動すると、最後に Rocket has launched from http://0.0.0.0:80 のような行が表示されます。Traefik が証明書を取得するまで数秒待ってから https://vault.example.com を開きます。有効な南京錠が表示され、証明書警告のない Bitwarden Web vault が表示されます。

Step 3、強力な ADMIN_TOKEN と $$ の罠

ADMIN_TOKEN は、インスタンス上のすべてのユーザーと設定を読み取れるパネルである /admin を保護します。root パスワードと同じように扱ってください。使用できる形式は 2 つあります。

簡単な形式は、openssl rand -base64 48 で生成済みのランダム文字列です。base64 には $ が含まれないため、エスケープせずに .env へそのまま設定できます。

より安全な形式は Argon2 PHC ハッシュです。これにより、平文のトークンがディスクに保存されることはありません。同じイメージを使用して生成します。

docker run --rm -it vaultwarden/server /vaultwarden hash --preset owasp

2 回入力を求められ、その後 $argon2id$v=19$... で始まる文字列が表示されます。ここで、1 時間を失いがちな罠があります。Docker Compose は $ を変数展開として扱うため、ハッシュを Compose ファイルへ貼り付ける際は、$ 1 つごとに $$ 2 つへ置き換える必要があります。environment: の直下に直接記述し、.env 経由にはしないでください。また、引用符で囲まないでください。

    environment:
      ADMIN_TOKEN: $$argon2id$$v=19$$m=19456,t=2,p=1$$c29tZXNhbHQ$$RdescudvJCsgt3ub+b+dWRWJTmaaJObG

$ が 1 つのままだと、Compose は The "argon2id" variable is not set と警告してトークンを空にします。その結果、/admin は正しいパスワードを拒否します。docker compose up -d を実行し、プロンプトで入力した平文は自分のパスワードストアに保管してください。

手順 4: アカウントを登録してから、アクセスを閉じる

SIGNUPS_ALLOWED: "true"https://vault.example.comを開き、アカウントを作成をクリックして、メールアドレスと強力なマスターパスワードを使用して登録します。このマスターパスワードは復元できず、リセットもありません。先に、確実に保管できる場所へ保存してください。

次にアクセスを閉じます。Compose ファイルを編集して、サインアップを無効にします。

      SIGNUPS_ALLOWED: "false"

docker compose up -dで再適用します。これは後回しにできるハードニングではありません。有効なままにすると、URLを見つけた人が、実際にはクローラーも含めて、サーバー上にアカウントを作成できます。あなたの vault を読み取ることはできませんが、リソースを消費し、非公開のインスタンスを公開サービスに変えてしまいます。無効化し忘れたことを示す兆候は、/adminに自分で作成していないアカウントが表示されることです。

後から家族やチームメンバーを追加する場合は、公開サインアップを再び有効にせず、/adminユーザーを招待ボタンを使用します。この方法では SMTP の設定が必要です。招待されたユーザーがリンクを受け取るためです。

Step 5、/admin にアクセスする

https://vault.example.com/admin にアクセスし、平文の admin token を入力します(ランダムな文字列、またはハッシュ化したパスワードそのものではなく、ハッシュ化する前のパスワードを指定します)。管理画面では、ユーザーの一覧表示、設定の調整、テストメールの送信、データベースのスナップショット取得を実行できます。

ページが 404 Not Found を返す場合、ADMIN_TOKEN が空または未設定です。この状態では管理画面全体が無効になります。管理画面を使用しない場合は、この設定でも問題ありません。ページは表示されるものの token が拒否される場合は、以下の失敗例にある $$ のエスケープに関する問題を確認してください。token を忘れた場合、復旧用のプロンプトはありません。.env または Compose file を編集して新しい token を設定し、docker compose up -d します。

手順 6: Bitwarden クライアントを接続する

公式クライアントはすべてセルフホストサーバーを指定できます。通常のストアから Bitwarden のデスクトップ、モバイル、またはブラウザークライアントをインストールしてください。専用の Vaultwarden ビルドは必要ありません。

ログインする前に、ログイン画面の設定ギアを開きます(Self-hosted または Region → Self-hosted と表示されます)。Server URLhttps://vault.example.com を設定して保存します。登録したメールアドレスとマスターパスワードでログインすると、クライアントはすぐに接続し、認証情報の入力と保存を提案します。

クライアントに This is not a recognized Bitwarden server. You may need to check with your provider or update your server. が表示される場合は、URL が正しくないか、http を使用しているか、証明書が信頼されていません。まずブラウザーで https://vault.example.com が問題なく読み込めることを確認してください。他のデバイスで更新が遅れる場合は WebSocket プッシュが原因であり、後述します。

ログインエンドポイント用のFail2ban jail

Vaultwarden は、LOG_FILEで指定したファイルにログイン失敗をすべて記録します。これはブルートフォース攻撃対策に必要な情報です。Fail2banをまだ実行していない場合は、インストール手順と基本設定をFail2ban SSH hardening guideで確認できます。ここではVaultwarden用のjailを1つ追加します。

まず、Fail2banがログを読み取れるように、名前付きvolumeがホスト上のどこにあるかを確認します。

docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}'

次のような結果が表示されます。/var/lib/docker/volumes/vaultwarden_vw-data/_data。その中にログがあります。vaultwarden.log。filterを作成します。

# /etc/fail2ban/filter.d/vaultwarden.conf
[Definition]
failregex = ^.*Username or password is incorrect\. Try again\. IP: <ADDR>\. Username:.*$
ignoreregex =

次に、jailを作成します。

# /etc/fail2ban/jail.d/vaultwarden.local
[vaultwarden]
enabled   = true
filter    = vaultwarden
logpath   = /var/lib/docker/volumes/vaultwarden_vw-data/_data/vaultwarden.log
banaction = iptables-allports
chain     = DOCKER-USER
maxretry  = 5
findtime  = 600
bantime   = 3600

sudo systemctl restart fail2banでreloadし、sudo fail2ban-client status vaultwardenで確認します。

この構成で実際に保護できるかどうかは、3つのDocker上の点で決まります。まず、失敗した試行ごとにログへIP: 127.0.0.1またはプロキシのアドレスが表示される場合、Vaultwardenはプロキシをbanしています。IP_HEADERを、プロキシが実際に送信するheaderに設定してください。TraefikではX-Forwarded-For、上記のnginxブロックではX-Real-IP、Cloudflareの背後ではCF-Connecting-IPを使用します。

次に、適切なiptables chainはプロキシによって異なります。公開ポートを設定したcontainerとしてTraefikを実行している場合、trafficはDockerのFORWARD経路を通過します。そのため、上記のようにbanをDOCKER-USERへ設定する必要があります。一方、Step 1のhost-nginx構成を選んだ場合、接続はホストのINPUT chainにあるnginxで終端されます。そのため、DOCKER-USER banでは接続を検出できません。この場合はchain = DOCKER-USER行を削除し、Fail2banにデフォルトのINPUT chainを使用させます。

最後に、ポートベースのデフォルトではなくbanaction = iptables-allportsを使用してください。このjailではポートを定義していません。DOCKER-USERでall-ports banを設定すると、対象の攻撃元からホスト上の公開サービスすべてへの接続を確実にブロックできます。

手順 8: vault をバックアップし、実際に復元する

vw-data volume は、パスワードマネージャーそのものです。db.sqlite3(すべてのエントリ)、attachments/sends/ のディレクトリ、ログインセッションへの署名に使う rsa_key.* ファイル、管理パネルからの config.json が保存されています。これらのいずれかを含まないバックアップは、必要なときに復元できません。

Vaultwarden の書き込み中に db.sqlite3 をコピーすると、書き込み途中の破損したファイルが取得される可能性があります。そのため、停止した状態でスナップショットを取得します。停止時間は数秒です。

#!/usr/bin/env bash
set -euo pipefail
STAMP=$(date +%F)
DEST=/root/vw-backups
VOL=$(docker volume inspect vaultwarden_vw-data --format '{{ .Mountpoint }}')
mkdir -p "$DEST"
docker compose -f /opt/vaultwarden/docker-compose.yml stop vaultwarden
tar czf "$DEST/vw-$STAMP.tgz" -C "$VOL" .
docker compose -f /opt/vaultwarden/docker-compose.yml start vaultwarden

これを cron から毎晩実行し、.tgz をサーバーの外部にもコピーします。保護対象のサーバーだけに保存されたバックアップは、バックアップとはいえません。別のサーバーまたはオブジェクトストレージへの毎晩の restic バックアップ(別のサーバーまたはオブジェクトストレージへの毎晩の restic バックアップ)を使うのが確実です。アーカイブの暗号化と、重複するスナップショットの重複排除も自動的に行われます。管理パネルの Backup Database ボタンは、SQLite ファイルだけを取得する手軽なホットスナップショットです。ただし、添付ファイルと鍵は含まれません。

ここからが、本物のバックアップと期待だけのバックアップを分ける手順です。1 回復元し、正常に動作することを確認します。

mkdir -p /tmp/vw-restore
tar xzf /root/vw-backups/vw-2026-07-15.tgz -C /tmp/vw-restore
docker run --rm -p 127.0.0.1:8888:80 -v /tmp/vw-restore:/data vaultwarden/server

ノートパソコンから ssh -L 8888:127.0.0.1:8888 you@your-vps でトンネルを作成し、http://localhost:8888 を開きます。localhost はセキュアコンテキストであるため crypto.subtle を利用でき、この場所に限り vault は通常の http 経由で復号されます。マスターパスワードでログインし、エントリが存在することを確認します。エントリが存在すれば、データベース、RSA 鍵、マスターパスワードがすべて正常に復元できたことになります。新しい VPS 上でも数分で再構築できます。Ctrl-C でコンテナを停止し、/tmp/vw-restore を削除します。インターネットに公開すべきでない、サーバー上の他の管理 UI にもこのトンネルを使う習慣をつけてください。同じ方法で、自己ホスト型の open-kritt セキュリティスキャナーにも port 5173 経由でアクセスできます。

エラーパターンと表示される文字列

ブラウザーコンソールに Cannot read properties of undefined (reading 'importKey') と表示される場合。 Vault が http 経由で読み込まれているため、crypto.subtle は未定義です。https:// 経由でのみアクセスし、プロキシで HTTP から HTTPS へのリダイレクトを追加します。

クライアントに This is not a recognized Bitwarden server... と表示される場合。 Server URL が http、入力ミス、または証明書が信頼されていません。https://vault.example.com で有効な南京錠が表示されることを確認し、クライアントの self-hosted 設定に再入力します。

/admin が正しいパスワードを拒否する場合。 Argon2 ハッシュのエスケープが失われています。Compose ではすべての $$$ にする必要があります。または、ハッシュが表す平文ではなく、ハッシュ自体を入力しています。

デバイス間の同期が遅く、コンソールに WebSocket connection to 'wss://vault.example.com/notifications/hub' failed と表示される場合。 プロキシが Upgrade/Connection ヘッダーを転送していません。Traefik では自動的に処理されますが、nginx では Step 1 の 2 行の upgrade 設定が必要です。Vault は引き続き動作しますが、開いたときだけ同期されます。専用の古いポート 3012 は v1.31.0 以降なくなったため、別の WebSocket ルートは不要です。

Fail2ban が ban を報告するのに、攻撃元からの接続が続く場合。 127.0.0.1 を ban しているためです。IP_HEADER が正しくないか、ban が誤った iptables chain に適用されています。chain = DOCKER-USERbanaction = iptables-allports を設定します。

アップグレード

新しいイメージを取得してコンテナを再作成します。名前付きボリュームとすべてのデータは保持されます。

docker compose pull
docker compose up -d

Vaultwarden は頻繁にリリースされます。パッチバージョンを固定するのではなく、プロジェクトのリリースノートを監視してください。リリースによっては移行に関する注意事項が含まれるためです。メジャーアップグレードの前には、毎回新しいバックアップを作成してください。ロールバックする場合は、tarball を新しいボリュームに復元できます。

FAQ

Vaultwarden は Bitwarden と同じものですか?

互換性のある独立したサーバーであり、公式サーバーではありません。Vaultwarden は Bitwarden のサーバー API を Rust で再実装しているため、公式のデスクトップ、モバイル、ブラウザー、CLI クライアントをすべて接続して使用できます。公式スタックより必要なリソースも大幅に少なくて済みます。Vault の形式は同じなので、エクスポートとインポートにより、どちらの方向にも移行できます。

HTTPS は本当に必要ですか?LAN 上なら http で実行できますか?

localhost テスト以外では HTTPS が必要です。Bitwarden の Web Vault と拡張機能はブラウザーの Web Crypto API を使用します。この API は secure context でのみ利用できるため、通常の http ではクライアントが Cannot read properties of undefined を返し、ログインできません。動作する http アドレスは http://localhost だけです。そのため、Step 8 の復元テストでは SSH トンネルを使用します。

知らない人がサーバーに登録するのを防ぐにはどうすればよいですか?

自分のアカウントを作成した直後に、Compose ファイルで SIGNUPS_ALLOWED: "false" を設定し、docker compose up -d を実行します。それ以降は、/adminInvite User ボタンから新しいユーザーを追加します。この操作には SMTP の設定が必要です。設定すると招待リンクがユーザーに届きます。管理画面のユーザー一覧を定期的に確認し、身に覚えのないアカウントが作成されていないことを確認してください。

Vaultwarden の Vault をバックアップするにはどうすればよいですか?

コンテナを短時間停止し、vw-data ボリューム全体、db.sqlite3attachments/sends/config.json および rsa_key.* ファイルをアーカイブします。そのアーカイブをサーバー外へコピーしてください。可能であれば、毎晩 cron で実行します。サーバーの稼働中に SQLite ファイルをコピーすると、スナップショットが破損するおそれがあります。そのため、停止した状態で取得してください。最も重要なのは、使い捨てのコンテナに一度復元してログインし、バックアップが実際に使えることを確認してから依存することです。

パスワードを自己ホストするのは本当に安全ですか?

このガイドで説明する3つの対策を実施すれば安全です。実際の HTTPS、登録の無効化と強力な管理者トークン、そしてテスト済みのバックアップです。Vault はマスターパスワードを使ってクライアント側で暗号化されるため、サーバーであってもパスワードの平文を見ることはできません。盗まれた db.sqlite3 も、マスターパスワードがなければ役に立ちません。代わりに、パッチ適用とバックアップが自分の責任になります。そのため、この構成では Fail2ban と復元手順を省略できません。これらを整えたら、自己ホストした Vault が実際にどこから攻撃されるのかを詳しく確認することが次の有用な手順です。エントリ自体はクライアント側で暗号化されているため、残る防御対象は管理者トークンとバックアップアーカイブです。