Supabaseセルフホストのセキュリティ設定チェックリスト|公開前に直す5項目(ポート・JWT・RLS)を実機で確認【2026年版】
2026-10-26
※2026年10月7日時点の情報です。弊社のサーバーで Supabase 公式リポジトリの docker-compose(Studio 2026.09.07、Postgres 17.6)を動かし、公式ドキュメントと照らし合わせて執筆しています。スクリプト名や設定項目は変更されるため、最新の内容は公式ドキュメントをご確認ください。
前回の「Supabaseをセルフホストして実機検証」では、8GB の VPS に11個のコンテナが約1GB で収まることを確認しました。構築手順・実測値・料金についてはそちらで紹介し、本記事ではインターネットに公開する前の安全設定に絞って解説します。弊社は手順通りに進めたところ、約3分間、既定の鍵のまま API を全世界に公開してしまいました。その失敗を繰り返さないためのチェックリストです。
結論:Supabaseをセルフホストで公開する前に直す5項目
公式の docker-compose と .env.example は、あくまで「動かすため」の設定です。「公開するため」の設定ではないため、既定のまま起動すると、次の5つがすべて開いた状態になります。
| 項目 | 既定の状態 | やること | 確認方法 |
|---|---|---|---|
| 1. 公開ポート | 8000・5432・6543 が 0.0.0.0 で公開される。ufw では止められない | compose のポート行に 127.0.0.1 を付ける | docker compose ps と外部からの接続試験 |
| 2. JWT シークレットと API キー | GitHub で公開されている値 | 起動前に公式スクリプトで生成し直す | .env が .env.example と異なること |
| 3. ダッシュボードの認証 | ユーザー名は supabase、パスワードも公開値 | 同じスクリプトで生成する | 既定値で Studio に入れないこと |
| 4. DB パスワード | 公開値 | 初回起動前に同じスクリプトで生成する | psql で既定値が通らないこと |
| 5. RLS(行レベルセキュリティ) | SQL で作ったテーブルでは無効。anon キーで全行を読める | テーブルごとに有効化してポリシーを書く | Studio の赤いバッジ、pg_tables の rowsecurity |
作業は鍵(2〜4)→ ポート(1)→ 起動 → RLS(5)の順に進めます。DB パスワードなど、初回起動を済ませると変更しにくいものがあるためです。
弊社の失敗:なぜ3分間の「全公開」が起きたのか
弊社では「ポートを 127.0.0.1 に限定する」と決めていました。それでも公開してしまった原因は、compose のポート行の書き方にあります。API の入口となる api-gw の行は、次のように記述されています(2026年10月7日に GitHub で確認)。
ports:
- ${API_GW_HTTP_PORT:-${KONG_HTTP_PORT:-8000}}:8000/tcp
これは、変数の中にさらに変数が入る入れ子の既定値です。「8000:8000」という文字列はどこにもありません。弊社はその文字列を置換しようとして、この行を見落としたまま起動してしまいました。もう1つの原因が、ufw への過信です。Docker 公式には「Docker で公開したポートへの通信は、ufw の設定を通る前に迂回する」と明記されています(2026年10月7日確認)。つまり、ufw で 8000 を閉じても、外からの通信は届きます。
その結果、起動から約3分間、既定の JWT シークレット・anon キー・ダッシュボードのパスワードのまま 8000 番が開いていました。データが無かったため実害はありませんでしたが、既定の鍵は GitHub で誰でも読めるものです。すぐに停止し、鍵を差し替えた上で作り直しました。
公開ポートを127.0.0.1に限定する:composeの修正と確認コマンド
公開ポートの設定は、api-gw と supavisor の2サービスにあり、合計3行です。各行の先頭に 127.0.0.1: を付けてください。変数部分を変える必要はありません。
# api-gw
ports:
- 127.0.0.1:${API_GW_HTTP_PORT:-${KONG_HTTP_PORT:-8000}}:8000/tcp
# supavisor
ports:
- 127.0.0.1:${POSTGRES_PORT}:5432
- 127.0.0.1:${POOLER_PROXY_PORT_TRANSACTION}:6543
Docker 公式の説明では、「127.0.0.1 を付けると Docker ホストからしか届かない」とされています。何も付けない場合は、既定ですべてのインターフェース(0.0.0.0)に公開されます。修正後は、次の3段階で確認します。
- 起動前に、ports の下にある3行を目視で確認する。
grep 127.0.0.1はヘルスチェックの行にも当たるため、これだけを当てにしない - 起動後は
docker compose psの PORTS 列を見て、127.0.0.1:8000->8000/tcpになっていることを確認する。0.0.0.0:8000なら外部に開いている - 別のネットワークから
nc -zv -w 3 サーバーのIP 8000を実行し、つながらないことを確認する。5432・6543 も同様に確かめる
外部から利用する際は、SSH トンネル、または後述する HTTPS 付きリバースプロキシを経由させます。
JWTシークレットとAPIキー・パスワードを生成し直す:公式の手順
.env.example の冒頭には、大文字で「コンテナを初めて起動する前に、すべての既定値を変更しなければならない」と記されています(2026年10月7日に GitHub で確認)。主な変更対象は次の通りです。いずれも公開リポジトリに掲載されている値です。
| 変数 | 既定値(公開されている) | 役割 |
|---|---|---|
| JWT_SECRET | your-super-secret-jwt-token-with-at-least-32-characters-long | 認証トークンの署名鍵 |
| ANON_KEY/SERVICE_ROLE_KEY | JWT_SECRET で署名した JWT | アプリ用の公開鍵/サーバー用の全権鍵 |
| DASHBOARD_USERNAME/PASSWORD | supabase/this_password_is_insecure_and_should_be_updated | Studio の Basic 認証 |
| POSTGRES_PASSWORD | your-super-secret-and-long-postgres-password | DB の管理パスワード |
| SECRET_KEY_BASE/VAULT_ENC_KEY など | 固定の文字列 | 各サービスの暗号化鍵 |
これらを手作業で作る必要はありません。公式ドキュメントの「Securing your services」では、docker フォルダで次の2つを順番に実行する手順が案内されています。
sh utils/generate-keys.sh
sh utils/add-new-auth-keys.sh
1つ目では、openssl の乱数を使って JWT_SECRET(30バイトを base64 にした40文字)・DB とダッシュボードのパスワード・各暗号化鍵を生成します。さらに、同じ JWT_SECRET で anon と service_role の JWT に署名します。変更前の .env は .env.old として保存されます。2つ目は、新形式の API キー(publishable/secret)と非対称鍵を追加するスクリプトです。実行には Node.js 16 以上か Docker が必要です(2026年10月7日に GitHub のスクリプトで確認)。
anon キーは JWT_SECRET から作るため、シークレットだけを変更すると認証が通らなくなります。また、POSTGRES_PASSWORD は DB の初期化時に使われるものです。既定値で一度起動してしまった場合は、データが入る前に停止して作り直すのが確実です。弊社もこの方法を取りました。
RLSを有効にする:anonキーで誰でも読める状態を解消する
anon キーは、ブラウザやアプリに埋め込む前提の公開鍵です。そのため、読み書きを許可する行は、テーブルごとの RLS(行レベルセキュリティ)で定めます。公式には「公開スキーマにある RLS 無しのテーブルは、権限を持つどのロールからも読み書きできる。すべてのテーブルで RLS を有効にせよ」と記載されています(2026年10月7日確認)。
前回は、弊社で psql を使って public.memo を作り、anon に select を許可した上で、REST から読めることを確認しました。このテーブルを Studio の Table Editor で開くと、上部には赤い「RLS disabled」のバッジが表示されます。Postgres では、テーブルを作るだけでは RLS は無効のままです。したがって、SQL で作ったテーブルはすべてこの状態になり、anon キーを持っていれば誰でも全行を読めます。

この状態を解消するには、テーブルごとに RLS を有効にし、必要な分だけポリシーを書きます。
-- 1. 有効にする(この時点で anon からは何も読めなくなる)
alter table public.memo enable row level security;
-- 2. ログイン済みの本人だけ読めるようにする(公式の例を memo に合わせたもの)
create policy "本人のメモだけ読める"
on public.memo for select
to authenticated
using ( (select auth.uid()) = user_id );
-- 3. RLS が無効のまま残っているテーブルを探す
select tablename from pg_tables
where schemaname = 'public' and not rowsecurity;
公式の説明では、「RLS を有効にすると、ポリシーを作るまで公開鍵経由では何も読めない」とされています。有効にした時点で安全側に倒れるため、まず全テーブルで有効化し、その後にポリシーを追加する順番が確実です。ただし、service_role キーは RLS を素通りするため、使用するのはサーバー側だけにします。
公開前に決めておきたいこと:HTTPS・バックアップ・更新
本番向けの構成として公式ドキュメントが勧めているのは、API ゲートウェイの前に Caddy か Nginx のリバースプロキシを置いて 443 番で TLS を終端する方法です(2026年10月7日確認)。8000 番の通信は平文なので、127.0.0.1 に限定し、プロキシだけを外部に公開する形が自然です。
公式によると、セルフホストでは管理されたバックアップや PITR(特定時点への復元)は使えず、バックアップと復旧は利用者の責任になります。更新は docker compose pull から始めますが、api-gw が Kong から Envoy に置き換わるなど、構成が大きく変わります。そのため、更新前に compose と .env の差分を読むことが欠かせません。
まとめ:Supabaseのセルフホストは「動いてから」が本番
公式の手順通りに起動した状態では、ポートは全公開、鍵とパスワードは公開値で、SQL で作ったテーブルの RLS は無効です。公開前に対応するのは5つの項目で、鍵の生成 → ポートの限定 → 起動 → RLS の順に進めます。弊社で起きた3分間の露出は、ポート行にある入れ子の既定値と、ufw への過信が原因でした。
- 構築手順・実測・料金については、前回の「Supabaseをセルフホストして実機検証」で紹介しています
- Supabase の概要と Firebase 代替の比較は、OSSMatch の Supabase ページに掲載しています
「公開前の設定を一緒に確認してほしい」「既定値で起動してしまったが何から直すか」とお考えの場合は、合同会社Edel Hearts のOSS導入支援でご相談を受け付けています。弊社自身が失敗した箇所だからこそ、見落としやすい点を具体的にお伝えできます。
出典
- Supabase Docs:Self-Hosting with Docker(Securing your services の鍵生成手順、ダッシュボード認証、HTTPS のリバースプロキシ、既定値で起動してはならない旨)・Self-Hosting(使えない機能と利用者の責任)(2026年10月7日確認)
- supabase/supabase:docker/docker-compose.yml(api-gw と supavisor の ports 行、ヘルスチェックの 127.0.0.1)・docker/.env.example(既定値と先頭の警告文)・docker/utils/generate-keys.sh・docker/utils/add-new-auth-keys.sh(2026年10月7日確認)
- Supabase Docs:Row Level Security(有効化の SQL、ポリシー例、ポリシー無しの挙動、service_role の bypassrls、grant とポリシーの関係)(2026年10月7日確認)
- Docker Docs:Packet filtering and firewalls(Docker and ufw)・Port publishing(127.0.0.1 への限定、既定の 0.0.0.0、daemon.json の ip)(2026年10月7日確認)
- 実機の記録:2026年10月7日に、弊社の検証サーバー(さくらのクラウド、2コア・8GB・SSD 40GB)で確認。約3分間の露出・RLS disabled のバッジ・psql で作ったテーブルの REST 確認は、いずれもこのときの記録
▶ 同じ環境で測った他の OSS については、OSS 実機検証シリーズの一覧 にまとめています。