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

1台のVPS向けSecrets Manager比較

1台のVPSで使うならどれが適切ですか。OpenBao、Infisical、SOPS with age、systemd credentials、mode 600のenvファイルを、運用負荷と費用で比較します。

パスワードマネージャーにはない、セルフホスト型 Secrets Manager の機能

セルフホスト型 Secrets Manager はプロセスに認証情報を渡します。パスワードマネージャーは人に認証情報を渡します。その他の違いは、すべてこの違いから生じます。パスワードマネージャーは、目の前にいて操作している人がロックを解除します。Secrets Manager は、誰も起きていない 03:00 にアプリケーションへデータベースのパスワードを渡さなければなりません。

障害の発生形態も異なります。ロックされたパスワードマネージャーは不便なだけです。マスターパスワードをもう一度入力すれば済みます。封印された Secrets Manager は障害になります。封印中に再起動したサービスは、認証情報なしで起動し、そのまま停止し続けます。自分専用のパスワードマネージャーとして Vaultwarden を運用することで、人に関する問題は適切に解決できます。しかし、マシンに関する問題は解決できません。そもそも、そのために作られたものではありません。

1 台のサーバーで現実的な選択肢は、2 つのグループに分かれます。OpenBao と Infisical はサービスです。API、データベース、TLS (transport layer security)、ログイン処理、そして稼働を維持する必要があるプロセスが必要です。SOPS と age、systemd credentials、Docker secrets はファイルです。保存時は暗号化され、すでに稼働している何かによって復号されます。追加で監視するものはありません。

先に率直な結論を示します。1 台のサーバーを 1 人か 2 人で使うなら、通常はファイルベースの選択肢が適しています。誰も適切に unseal せず、誰もローテーションしない OpenBao は、mode 600 の env ファイルより悪い選択です。構成要素が 1 つ増え、間違ったバックアップが必要になる一方で、手作業で行っていなかったローテーションを新たに実現できるわけではないからです。

mode 600 の env ファイルで十分ですか?

多くの場合は十分です。防ぐ対象は、同じサーバー上の別ユーザーがデータベースのパスワードを読み取ることです。Unix のファイル権限で防止でき、ネットワークが起動する前にも適用されます。

sudo install -d -m 750 -o root -g myapp /etc/myapp
sudo install -m 640 -o root -g myapp /dev/null /etc/myapp/env
sudoedit /etc/myapp/env

両方の側から確認します。

sudo -u myapp cat /etc/myapp/env
sudo -u nobody cat /etc/myapp/env

1つ目のコマンドはファイルを表示します。2つ目のコマンドは cat: /etc/myapp/env: Permission denied を表示します。nobodymyapp グループに所属しておらず、ファイルにも他ユーザー向けの権限がないためです。これがセキュリティモデルの全体であり、実際に機能します。

問題は、その後に発生する情報漏えいです。EnvironmentFile= を持つ systemd unit は、これらの値をプロセス環境にコピーします。プロセス環境は読み取り可能です。

[Service]
User=myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/local/bin/myapp
sudo cat /proc/$(pgrep -n myapp)/environ | tr '\0' '\n'

このコマンドは Secret を平文で表示します。/proc/<pid>/environ は root と、プロセスを実行するユーザーから読み取り可能だからです。クラッシュレポーターが環境変数をレポートに添付する場合も、同じ内容を取得できます。同じアカウントで実行されるツールも同様です。そのため、AI エージェントから Secret を遠ざけることは、まず Secret を環境変数から取り除くことから始まります。ファイルと 権限を限定した専用サービスユーザーを組み合わせ、「プロセスを実行するユーザー」が root にならないようにします。

age による SOPS: git にコミットできる暗号化 Secret

SOPS (secrets operations) は、YAML または JSON ファイルの値を暗号化し、キーは平文のまま残します。age は、小規模な暗号化ツールです。1 組の鍵ペアを使用し、鍵サーバーは必要ありません。これらを組み合わせると、secrets.enc.yaml をコードの隣にコミットできます。また、git diff によって、設定値そのものを読者に知らせずに、どの設定が変更されたかを確認できます。

age は Ubuntu 24.04 にパッケージとして含まれています。SOPS は含まれていないため、リリースページから .deb を取得します。2026 年 8 月時点の現行バージョンは 3.13.3 でした。

sudo apt update && sudo apt install -y age
curl -LO https://github.com/getsops/sops/releases/download/v3.13.3/sops_3.13.3_amd64.deb
sudo apt install -y ./sops_3.13.3_amd64.deb
sops --version

鍵ペアを生成します。age-keygen は秘密鍵をファイルに書き込み、公開鍵を表示します。そのため、Public key: age1... で始まる行が表示されます。

mkdir -p ~/.config/sops/age
age-keygen -o ~/.config/sops/age/keys.txt
chmod 600 ~/.config/sops/age/keys.txt
age-keygen -y ~/.config/sops/age/keys.txt

コマンドラインで recipient を毎回指定せずに済むように、リポジトリのルートにある .sops.yaml に公開鍵を記述します。

creation_rules:
  - age: age1s3cqcks5genc6ru8chl0hkkd04zmxvczsvdxq99ekffe4gmvjpzsedk23c
sops encrypt secrets.yaml > secrets.enc.yaml
sops decrypt secrets.enc.yaml

path_regex のないルールはすべてに一致します。最初はこの設定で問題ありません。後から追加する場合は、sops に渡すファイルに一致するよう記述してください。ルールは出力をリダイレクトする先のファイルではなく、入力パスに対して評価されるためです。

実行時は、値を 1 つのプロセスだけに渡します。

sops exec-env secrets.enc.yaml './myapp'

sops exec-env はメモリ上で復号し、子プロセスの環境変数に値を設定します。そのため、平文がディスクに書き込まれることはありません。前のセクションで説明した環境変数に関する注意点は、その子プロセスにも適用されます。

ここでは 2 つの問題がよく発生します。systemd で Failed to get the data key required to decrypt the SOPS file というエラーが出る場合、ほとんどは SOPS が誤った home ディレクトリを参照しています。unit はユーザーの HOME を継承しないためです。unit で Environment=SOPS_AGE_KEY_FILE=/etc/sops/age.txt を使用してパスを明示的に設定します。もう 1 つは、.sops.yaml を編集しても、既存のファイルは再暗号化されないことです。同僚の公開鍵を追加しても、新しいファイルにしか適用されません。そのため、既存の各ファイルで sops updatekeys secrets.enc.yaml を実行します。設定をすでに Ansible で管理している場合は、同じ値を Ansible Vault で暗号化することで、別のツールを追加せずに同じ結果を得られます。

systemd credentials: 環境変数に到達しない Secret

Ubuntu 24.04 には systemd 255 が含まれているため、インストールは不要です。systemd-creds はホスト上で Secret を暗号化し、systemd はその Secret を、対象サービスだけが読み取れるプライベートディレクトリに復号します。

sudo systemd-creds setup
sudo install -d -m 700 /etc/myapp
echo -n 'hunter2' | sudo systemd-creds encrypt --name=db_password - /etc/myapp/db_password.cred
[Service]
User=myapp
LoadCredentialEncrypted=db_password:/etc/myapp/db_password.cred
ExecStart=/usr/local/bin/myapp

サービスは、$CREDENTIALS_DIRECTORY が示すディレクトリ内の db_password というファイルから値を読み取ります。この値は環境変数に含まれないため、/proc/<pid>/environ を実行しても有用な情報は表示されません。また、平文が root ファイルシステムに書き込まれることもありません。

unit から参照する前に、ファイルを復号できることを確認します。

sudo systemd-creds decrypt /etc/myapp/db_password.cred -

どの鍵で暗号化したかを把握してください。バックアップが役に立つかどうかは、それで決まります。デフォルトの --with-key=auto は、利用可能で使用できる場合は TPM2(trusted platform module version 2)チップを使い、それ以外の場合はホスト鍵を使います。ほとんどの VPS インスタンスには TPM2 がありません。

systemd-analyze has-tpm2

no はホスト鍵が使われたことを示します。この鍵は /var/lib/systemd/credential.secret に保存され、root だけが読み取れます。そのファイルがない状態で新しい VPS に db_password.cred を復元しても、復号できません。credential.secret を同じバックアップに含めるか、引き続きアクセスできる場所に平文を保管してください。

Docker secrets: のファイル

Compose はホスト上のファイルを読み込み、/run/secrets/<name> にマウントします。

services:
  app:
    image: myapp:latest
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
secrets:
  db_password:
    file: ./db_password.txt
docker compose exec app cat /run/secrets/db_password
docker compose exec app env | grep -i password

1つ目は Secret の内容を出力します。2つ目は DB_PASSWORD_FILE=/run/secrets/db_password だけを出力します。ここが重要です。値はコンテナの環境変数に入らないため、docker inspect の出力にも表示されません。多くの公式イメージはこの形式を前提としており、Postgres イメージも POSTGRES_PASSWORD_FILE をこの方法で読み取ります。

これは何かを明確にしておきます。Swarm モード以外では、どの層でも暗号化されません。./db_password.txt はホスト上の平文ファイルであり、保護手段はモードと所有者だけです。これらは自分で設定してください。Compose は、警告なしに誰でも読み取れるファイルをマウントするためです。通常の env_file の簡易的な方法と比較した、より広いトレードオフについては、Compose の env ファイルと Secret のガイドを参照してください。

OpenBao と Vault の実際の運用コスト

OpenBao は HashiCorp Vault の Linux Foundation 版フォークです。HashiCorp が 2023 年に Vault のライセンスを Business Source License に変更した後に開始されました。OpenBao は MPL 2.0(Mozilla Public License)のままです。2026 年 8 月時点の最新リリースは 2.6.2 です。フォーク後もコマンド体系は同じため、以下のほとんどは Vault にも当てはまります。

docker pull docker.io/openbao/openbao

アップグレードを apt で管理したい場合は、OpenBao のダウンロードページから Debian および Ubuntu 用のパッケージを入手できます。サーバーには、listener と storage backend を定義した設定ファイルが必要です。

listener "tcp" {
  address       = "127.0.0.1:8200"
  tls_cert_file = "/path/to/full-chain.pem"
  tls_key_file  = "/path/to/private-key.pem"
}

storage "raft" {
  path = "/path/to/raft/data"
  node_id = "raft_node_1"
}

次に、1 回起動します。

bao operator init

デフォルトでは、root key を 5 個の share に分割し、そのうち 3 個を指定しないと unseal できません。これらを指定するのが -key-shares-key-threshold の flags です。OpenBao は share と初期 root token を 1 回だけ表示し、以後は再表示しません。

ここからが、多くの比較記事で省略される部分です。再起動したサーバーは sealed 状態です。 OpenBao は root key をメモリ上だけに保持します。そのため、再起動後は、誰かが必要数の share を指定するまで、自身の storage を復号できません。したがって、kernel の更新や out of memory による kill が発生すると、サーバーは sealed 状態になり、アプリケーションはログインできなくなります。

1 人で運用する VPS では、Shamir split による保護はありません。5 個すべての share が、同じ人が管理する同じ password manager に保存されるためです。Auto unseal を使うと、key は信頼できる device または service に移されます。大規模な cloud では managed key service が一般的ですが、VPS では通常、保護対象のデータと同じ disk 上に key file を置くことになります。これはセキュリティを実際に低下させる代わりに、再起動後もサーバーが自動的に復旧できるようにする方法です。このトレードオフを理解したうえで選択し、どちらを採用したかを記録してください。

Infisical: UI、データベース、そして自分で保持し続けるマスターキー

Infisical は、Web インターフェース、プロジェクト、環境、ユーザー単位のアクセス制御を備えた Secret 管理プラットフォームです。Compose でセルフホストする手順は次のとおりです。

curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
docker compose -f docker-compose.prod.yml up -d

最後のコマンドを実行する前に .env を編集します。2 つの値は自分で設定する必要があり、そのうち 1 つは後から変更してはいけません。

openssl rand -hex 16
openssl rand -base64 32

1 つ目は ENCRYPTION_KEY です。これは 16 byte の hex 文字列です。PostgreSQL 内で Secret を暗号化する鍵として使われるため、これを失うと、完全なデータベースバックアップがあっても暗号文の集合になってしまいます。稼働中のインスタンスで変更すると、既存の Secret を復号できなくなります。2 つ目は AUTH_SECRET です。これはセッションに使う 32 byte の base64 文字列です。SITE_URL には、実際にアクセスする絶対 URL をプロトコルも含めて指定する必要があります。指定を誤るとログイン後のリダイレクトが機能しません。

実際に必要なのが人向けの機能であれば、OpenBao より Infisical のほうが適しています。小規模チーム向けの Web インターフェースと、環境間の分離を利用できます。これは、データベース認証情報が自動的に期限切れになる構成とは異なります。その代わり、PostgreSQL、Redis、TLS 証明書が必要になり、これらすべてを自分で更新してバックアップする必要があります。

シークレットサービスが停止している状態でアプリを再起動するとどうなるか

この問題によって、シークレットサービスを単一のサーバーに配置できるかどうかが決まります。ファイルはネットワークの起動前でも読み取れます。サービスは読み取れません。

サーバーを再起動すると、アプリケーションと OpenBao が同時に起動します。アプリケーションがデータベースのパスワードを要求した時点で、OpenBao はまだ sealed の状態です。要求は失敗し、systemd は人が unseal shares を入力するまでアプリケーションを繰り返し再起動します。何かが壊れているわけではありません。ただし、何も稼働していない状態になります。

この問題には、現実的な方法が2つあります。unit の起動順序を設定し、アプリケーションに再試行させます。After= シークレットサービス、さらに Restart=on-failure と、API に過剰な要求を送らない十分な長さの RestartSec= を設定します。もう1つは、起動時ではなくデプロイ時に取得する方法です。シークレットを mode 600 のファイルまたは systemd credential に展開します。これにより、実行中のシステムは API ではなくファイルに依存します。

トークンの有効期限切れも、時間の進み方が遅い同じ問題です。OpenBao のトークンと lease には有効期間があります。そのため、更新を行わない長時間稼働プロセスは、デプロイとは無関係な時点でアクセス権を失います。その日に何も変更していないため、この障害は特に原因を把握しにくくなります。

ストア自体のバックアップ

ここで扱う各方式には鍵があり、その鍵がないバックアップには価値がありません。自分の鍵がどこにあるかを書き留めてください。

env ファイルの場合、ファイル自体が Secret なので、バックアップを暗号化する必要があります。SOPS の場合、暗号化したファイルは公開されている場所に置いて構いませんが、~/.config/sops/age/keys.txt にある age の秘密鍵を失ってはいけません。systemd credentials の場合は、.cred ファイルと一緒に /var/lib/systemd/credential.secret をバックアップしてください。Infisical の場合は PostgreSQL のダンプを取得し、ENCRYPTION_KEY をダンプとは別の場所に保存してください。

raft ストレージを使用する OpenBao では、専用のスナップショットを取得します。

bao operator raft snapshot save backup.snap
bao operator raft snapshot restore backup.snap

このスナップショットには暗号化されたストレージが含まれるため、新しいサーバーに復元する場合でも bao operator init の unseal share が必要です。スナップショットを毎晩 object storage にコピーしても、share をどこにも保存していなければ、何もバックアップしていないのと同じです。依存する前に、使い捨ての VPS で復元をテストしてください。

監査ログ: 誰がどの Secret を読み取ったか

ファイルだけでは監査証跡を確認できません。モードと所有者から分かるのは、誰が Secret を読み取れたかだけです。誰が実際に読み取ったかは分かりません。パスを監視する auditd は、最も近い代替手段です。ただし、報告できるのはファイルが開かれたことだけで、どの値が使用されたかまでは分かりません。

OpenBao は、明示的に有効化した監査デバイスにすべてのリクエストを記録します。

bao audit enable file file_path=/var/log/openbao_audit.log

このログには、サーバーの運用方法を変える重要な点が 2 つあります。リクエストとレスポンスに含まれる多くの文字列は、ソルトを使って HMAC-SHA256 でハッシュ化されます。そのため、既知の値とログを照合できますが、ログ自体に平文が含まれることはありません。整数とブール値は平文で記録されるため、数値の Secret はこのハッシュ化による保護を受けません。

次に、運用上の注意点があります。有効な監査デバイスがリクエストを記録できない場合、OpenBao はリクエストに応答しません。デバイスがブロッキング状態で失敗すると、誰かが修復するまでリクエストがハングします。/var/log のディスク使用量が上限に達すると、設計上、Secret API が停止します。最初の障害が発生してからではなく、初日から監査ログ専用の領域と logrotate のルールを用意してください。

どの self-hosted secrets manager を運用すべきか

マシン数と利用者数を確認してから選びます。

  1. 1 台のマシンを 1 人で使う場合: root が所有し、サービスユーザーが読み取る mode 600 の env ファイルを使います。値をプロセス環境から分離したい場合は、systemd credentials を追加します。
  2. 1 台のマシンを 2 人から 5 人で使い、設定がすでに git で管理されている場合: SOPS と age を使います。各利用者に鍵ペアを割り当て、.sops.yaml に復号を許可するすべての公開鍵を列挙します。
  3. 複数のマシンで 1 つの設定リポジトリを使い、有効期限付きの credentials が不要な場合: 引き続き SOPS と age を使います。各ホストに 1 つの recipient key を割り当てると、ホストの鍵が盗まれても、そのホストのファイルだけが復号されます。
  4. 複数のマシンと複数のチームがあり、有効期限付きのデータベース credentials と、実際に誰かが確認する監査証跡が必要な場合: OpenBao を使います。unseal と restore の訓練に、運用担当者の作業時間を月 1 時間確保します。

4 つすべてに共通する原則は同じです。口頭で説明できる要件を満たす最小限のものを運用してください。停止した secrets manager は、値が空の secrets manager と区別できないためです。

FAQ

1 台の VPS で自己ホスト型の Secret 管理サービスを使う価値はありますか?

OpenBao や Infisical のようなサービスを指すなら、通常はありません。1 台のサーバーを 1 人または 2 人で使う場合、mode 600 の環境ファイルや systemd の暗号化 credential で、別のローカルユーザーに対して同等の保護を実現できます。unseal 操作は不要で、追加のサービスにパッチを適用する必要もありません。複数のマシンと複数のユーザーがある場合や、誰も手動でローテーションしなくても期限切れになる credential が実際に必要な場合は、Secret サービスの導入効果が出ます。

password manager と Secret manager の違いは何ですか?

password manager は人が入力する credential を保存し、ユーザーがその場でロックを解除して使います。Secret manager はプロセスに credential を渡すため、誰も監視していない 03:00 にも動作しなければなりません。この点が両者を分けます。ロックされた password manager では master password の再入力が必要ですが、sealed 状態の Secret manager では、seal 中に再起動したすべてのサービスが停止します。

再起動後に OpenBao が sealed 状態になると、アプリケーションはどうなりますか?

アプリケーションは Secret を取得できないため起動に失敗し、誰かが unseal threshold を満たすまで systemd による再起動を繰り返します。デフォルトの threshold は 5 shares 中 3 です。OpenBao は root key をメモリにのみ保持するため、再起動するたびに再び seal されます。auto unseal を有効にすると、この問題を回避できます。ただし、1 台の VPS では unseal key がデータと同じディスク上に置かれることになります。別の方法として、deploy 時に Secret をファイルへ書き出し、起動が API に依存しないようにします。

SOPS で暗号化したファイルを公開 repository に commit できますか?

値は暗号化されているため、age private key を持たないユーザーからは保護されます。ただし、key 自体は暗号化されません。読者は、あなたが STRIPE_SECRET_KEYSMTP_PASSWORD を保持していること、およびそれぞれがどの程度の頻度で変更されるかを確認できます。この metadata は、ほとんどのプロジェクトでは許容できますが、一部のプロジェクトでは許容できません。age private key は repository の外部で管理し、recipient を追加または削除するたびに、既存のすべてのファイルに対して sops updatekeys を実行してください。