0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SSLとDB再起動を「見える化」するステータスページをつくってみた:暗号化されているかと、止まっていた時間を1画面で

0
Last updated at Posted at 2026-10-22

対象: 前の記事でSSL強制を試して、「で、運用している人はこれを毎回どう確認するの?」が気になった方
所要時間: 30〜40分 / 使うもの: ブラウザ、ターミナル、Supabaseのプロジェクト(Freeプランで可)
確認日: 2026年10月(画面や料金は変わることがあります)
前の記事(手順編): https://qiita.com/tomozilla/items/c211b4676776bbd61eb3
ビジネス編(やさしい解説・note): https://note.com/tomozilla/n/n11ff9cfb7b67
サンプルコード一式(GitHub): https://github.com/tomozilla/ssl-enforcement-check
記事の中のコードは日本語コメントです。リポジトリの本体は英語コメントで、日本語コメント版(この記事と同じもの)は ja/ フォルダーにあります。

前の記事では、「SSLを強制」のスイッチを押して、何が起きるかを手作業で確かめました。psql でわざと間違ったパスワードを試したり、SQL Editorで起動時刻を聞いたり。

終わってみて、思いました。これ、毎回やるのは大変だな。そして、これを見たい人は、SQLを書かない人かもしれない。

運用の担当者、情報システムの人、受託開発で「うちのデータ、暗号化されてますか?」とお客さまに聞かれた人。そんな人が見たいのは、きっと1枚の画面です。そこで、前の記事で確かめた3つのこと(暗号化・再起動・止まっていた時間)を、ステータスページにしてみました。

0. 先に伝えておきたいこと(正直ベース)

  • 今回のページが見ているのは、アプリ(Edge Function)からデータベースへの接続だけです。外から psql などでつなぐ接続は、前の記事のスクリプトで確かめます(同じ画面では見られません)。
  • 「止まっていた時間」は、定期ジョブ(cron)の実行が途切れた時間から求めた目安です。ユーザーが実際に困った時間そのものではありません。
  • データは、小さな空のデモプロジェクトのものです。
  • 費用:このページは30秒ごとに security-status 関数を呼びます。1つのタブを開きっぱなしにすると、1日に約2,880回、1か月で約8万6千回の呼び出しです(計算した目安)。Freeプランの月50万回(ドキュメントより)に対して、1枚のタブで約17%です。タブを何枚も開きっぱなしにすると、すぐ増えます。 私は実際の使用量は測っていません。他にかかるお金はありません。
  • セキュリティ:このページには、データベースの稼働状況が出ます。関数はVerify JWTをオフにして、「アプリのpublishableキー」を確認するだけの簡単な守りにしています。社外に公開するページには、本物のログインを付けてください。
  • 未検証:verify-full と証明書での接続、大きなデータベースでの再起動、Fast database reboot の実測、スマホでの表示。

1. つくったもの:接続セキュリティ・ステータス

ステータスページ(再起動の約6分後)

画面は4つのブロックでできています。

ブロック 見えること どこから取る?
アプリ → データベースの接続 暗号化されているか、TLSのバージョン、暗号方式 pg_stat_ssl
データベースの稼働 最後の再起動の時刻、稼働時間 pg_postmaster_start_time()
定期ジョブの実行:直近60分 1分ごとの実行回数(棒グラフ) cron.job_run_details
止まっていた時間帯 直近72時間で、実行が25秒より長く途切れた所 同上

全体像

まず驚いたことがあります。前の記事では、「Edge FunctionからDBへの接続は、最初からSSLだと、ドキュメントに書かれていた」とだけ書いて、私は確かめていませんでした。今回、データベース自身に聞いたら、**TLS 1.3、TLS_AES_256_GCM_SHA384(256bit)**と返ってきました。「ドキュメントでは…」が、「私の環境で確かめました」に変わりました。

2. つくり方

API(Edge Function)

security-status は、読むだけの関数です。3つのことを聞きます。

// この関数 → データベースの接続が暗号化されているか(pg_stat_ssl は、自分の接続の状態を見られる)
const [conn] = await sql`
  select ssl, version as tls_version, cipher, bits
  from pg_stat_ssl where pid = pg_backend_pid()`;

// Postgresがいつ起動したか(=最後の再起動)
const [db] = await sql`
  select pg_postmaster_start_time() as started_at, now() as now,
         current_setting('server_version') as server_version`;

「止まっていた時間」は、cronの実行履歴から、前の実行との間隔が25秒を超えた所を拾います。10秒ごとのジョブなので、25秒を超えたら、何かあったと考えます。

with runs as (
  select start_time, lag(start_time) over (order by start_time) as prev
  from cron.job_run_details where start_time > now() - interval '72 hours')
select prev as from_ts, start_time as to_ts,
       extract(epoch from (start_time - prev))::int as seconds
from runs where start_time - prev > interval '25 seconds'
order by start_time desc limit 20;

前の記事と同じ考え方(cronはデータベースの中で動くので、DBが止まっていると実行されない)です。ただし、これは10秒ごとのジョブが1つだけある前提です。ジョブが複数あると、間隔の計算がずれます。

関数の入り口では、前の記事のアプリと同じように、publishableキーが付いているかを確認しています(Verify JWTはオフのまま)。

画面

画面は、HTML 1枚です。30秒ごとに関数を呼んで、結果を描き直します。ライブラリは使っていません。棒グラフもSVGを自分で組み立てています。

動かす

  1. 前の記事のように、10秒ごとのcronジョブを用意します(sql/00_heartbeat_cron.sql)。
  2. security-status を、Edge Functionとしてデプロイして、Verify JWTはオフにします。
  3. app/config.example.js を config.js にコピーして、URLとpublishableキーを入れます。
  4. 配信します。
    cd app
    python3 -m http.server 8765 --bind 127.0.0.1   # → http://127.0.0.1:8765/index.html
    

3. 実際にプロジェクトを再起動してみた

見えるようにしたのなら、再起動で何が見えるかを試したくなりました。ただ、その前に一つ、私は勘違いをしていました。

前の記事で測った「約20秒」は、SSL強制のスイッチで起きた再起動です。ドキュメントには、この設定の変更は「fast database reboot(データベースだけの高速な再起動)」を起こす、と書いてありました。一方、ダッシュボードの Project Settings → General → Restart project は、別物のようです。画面には「Your project will not be available for a few minutes.」とあり、横の ▼ を開くと、こう出ます(2026年10月、私の画面での表示です)。

Fast database reboot
Restarts only the database service, with less downtime than a full project restart. Other project services remain running.

つまり、メインの Restart project は、データベースだけでなくプロジェクト全体(データベースの上で動くAPIやログイン、ストレージなどのサービスも含めた一式)の再起動で、fast database reboot は、それよりも軽い別メニューです。内部で何をどの順番で再起動しているのかは、私が調べた範囲では公開された説明が見つかりませんでした(ここは分かりません)。

操作 何が再起動するか 画面・ドキュメントの案内
SSL強制のスイッチ データベースのみ(fast database reboot) ドキュメント:小さなプロジェクトでは通常数秒、大きいと長くなることも。同じページの別の箇所と確認ダイアログには「数分」とも。
Restart project プロジェクト全体 画面:数分間、プロジェクトが使えない
▼ の Fast database reboot データベースのみ Restart projectより短いダウンタイム

そこで、Restart project のほうを押して、外から10秒に1回くらいの間隔で security-status を呼び続けました。

経過 結果
〜3秒 正常
7秒 エラー(HTTP 500)
32秒 応答なし(タイムアウト)
67秒 エラー(HTTP 500)
74秒 正常。起動時刻が新しくなっていた

応答が戻るまで、約70秒でした。ステータスページの「止まっていた時間帯」には、こう出ました。

回 きっかけ ページが示した空白
1回目 前の記事でSSL強制のスイッチ(fast database reboot) 約20秒(前の記事の手作業の測定)
2回目 今回、Restart project 1分17秒
3回目 今回、もう一度 Restart project 1分29秒

1回目と2・3回目は、同じ「再起動」でも別の操作です。最初は「約20秒は運が良かった1回」と考えていましたが、データベースだけの再起動と、全体の再起動の違いのほうが、ありそうな説明です。ただし、▼ の Fast database reboot を、私は自分では測っていません。SSL強制のスイッチが fast database reboot だというのも、ドキュメントの記述からの推測で、画面で確認したわけではありません。どれも小さな空のデモでの1〜2回の結果で、本番のデータ量では変わるはずです。

本番で選ぶなら、「データベースの設定を変えたいだけ」のときは fast database reboot、「全体を立ち上げ直したい」ときだけ Restart project、と使い分けられそうです(私は試していません)。どちらも、止まっている間は security-status のようなAPIも応答しません。

4. 見つけた問題と、直したところ

① 障害のとき、古い表示が「いまの状態」のように見えた

再起動の最中に、ページを更新してみました。

再起動の最中のページ(直す前)

上にはエラーが出ていますが、その下は**ふだんどおり「暗号化されています」「稼働 6分18秒」**のままです。これは状態を伝えるページとして、まずいです。 止まっているのに、緑の丸が出たままで、稼働時間も止まったまま。見た人は「大丈夫そう」と思ってしまいます。

直し方は、取得に失敗したら、表示を灰色にして、「何時のものか」を書くことです。

} catch (e) {
  showErr('状態を読めませんでした:' + e.message + (lastOk ? `。下の表示は ${時刻} 時点の古い情報です。` : ''))
  if (lastOk) document.querySelector('main').classList.add('stale')   // 灰色にする
}

直したあとの画面です(実際の再起動中ではなく、通信を止めて、同じ状況をつくって撮りました)。

失敗したときは、灰色で「古い情報です」と出す

② 古い再起動は「原因は不明」になる

画面の「止まっていた時間帯」で、いちばん新しい再起動だけが「データベースの再起動」と判定されています。2回目の空白は、「不明」です。

再起動の直後。新しい空白だけが「再起動」と判定された

原因は、Postgresが教えてくれる起動時刻が最後の1回分だけだからです。過去の再起動の時刻は、どこにも残っていません。ページの注意書きにも、そう書きました。

ほんとうは、定期的に起動時刻を表に記録しておけば、過去の再起動も判定できそうです(私は試していません)。

③ 「不明」の空白は、私の作業だった

2回目より前の「3分3秒」の空白は、この日、私がcronジョブを消して作り直した時間でした。ステータスページは、それが「再起動」ではなく「ジョブの停止・作り直し」かもしれない、ということまでは分かりません。空白の原因は、ページだけでは決められない、というのが正直なところです。

④ 再起動の直後は、丸を橙にした

起動してから5分未満のときは、稼働時間の丸を橙にして、「再起動して間もありません」と出しました(上の画像)。

5. 実際のWebアプリ(運用画面)にするなら

どれも、私は試していません。

  • ログイン:今は、キーを知っていれば誰でも見られます。社内向けなら、ログインした人だけに。
  • 過去の記録:起動時刻や、空白を表に残すと、半年前の再起動も振り返れます。
  • 通知:空白や「暗号化されていない」を見つけたら、メッセージで知らせる。
  • 外からの確認:「SSLなしでの接続が断られること」を、サーバー側から定期的に試して、結果を表示する。
  • ジョブが複数ある場合:ジョブごとに間隔を見る。

まとめ

  • 暗号化・再起動・止まっていた時間を、1枚のステータスページにまとめられた
  • データベース自身に聞くと、TLS 1.3で接続していることが確認できた(ドキュメントの説明を、自分の環境で確かめられた)
  • Restart project(プロジェクト全体の再起動)では、応答が戻るまで約70秒。止まっていた時間帯は、1分17秒・1分29秒。前の記事の約20秒は、データベースだけの再起動(SSL強制のスイッチ)で、別の操作だった
  • 状態のページは、失敗したときの見せ方が大事。古い表示を、そのまま出さない
  • 空白の原因は、ページだけでは決められないことがある

コードは、上のGitHubにあります。次は、「会社ごとに見えるデータを分ける」(RLS)を、ログインできる請求書ポータルにしてみます。


スクリーンショットは、記事用に作った無料プランのデモ環境で撮影したものです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?