Claude Media
Claude CodeでCaddyのリバースプロキシを設定する — validateとadaptを完了条件に

Claude CodeでCaddyのリバースプロキシを設定する — validateとadaptを完了条件に

Claude Codeに書かせたCaddyfileを、caddy fmt・validate・adaptで検査させる手順です。PostToolUse hookとCLAUDE.mdの書き方、稼働中にvalidateが失敗する例外まで扱います。

Claude CodeにCaddyのリバースプロキシを設定させるときは、「Caddyfileを書いた」を完了にしないのがコツです。caddy validate と caddy adapt が通るまでを完了条件にすると、書き間違いは本番に出る前にClaude Codeの手元で潰れます。

この記事では、Caddyfileの最小形、3つの検査コマンドの違い、編集のたびに検査を走らせるhookとCLAUDE.mdの書き方を順に示します。

完了条件は「書けた」ではなく「validateが通った」

Caddyfileは短く書けるぶん、見た目が正しくても動かない設定を作りやすい形式です。Claude Codeは構文として自然な行を書くので、読んだだけでは誤りに気づけません。

そこで、完了を判定する材料をコマンドの終了結果に置きます。使うのは次の3つです。

コマンド見ること向く場面
caddy fmt --diff見ること書式の差分向く場面書いた直後の整形確認
caddy validate見ることモジュールの読み込みと準備まで向く場面完了条件の本命
caddy adapt --pretty見ることCaddyfileをJSONに変換した結果向く場面意図どおりに解釈されたかの目視

3つとも、Caddyを起動せずに実行できます。Claude Codeに「この3つを順に実行して、出力を読んでから報告して」と頼めば、検査の結果が会話に戻ってきます。

まず最小のリバースプロキシを書かせる

Caddyfileでバックエンドへ転送する最小形は、サイトアドレスの中に reverse_proxy を1行置く形です。次は公式の例に沿った形です。

example.com {
	reverse_proxy localhost:9005
}

Claude Codeへの依頼は、ホスト名・転送先・守りたい条件を具体的に渡します。

Caddyfileを新規に作成して。
- example.com を localhost:9005 へリバースプロキシする
- /api/* だけを転送し、ヘルスチェックのパスは /healthz
- 書き終えたら caddy fmt --diff、caddy validate、caddy adapt --pretty を順に実行し、
  エラーがあれば直して、3つとも通ってから報告する

/api/* だけを転送する書き方と、ヘルスチェックを足した形は、公式のリバースプロキシのページにある例が土台になります。次のような形が出てくれば妥当です(例示であり、実際の出力はその場の依頼内容で変わります)。

example.com {
	reverse_proxy /api/* localhost:9005 {
		health_uri /healthz
		lb_try_duration 5s
	}
}

health_uri を指定するとアクティブヘルスチェックが有効になります。lb_try_duration は、次の候補が落ちているときにバックエンドを選び直す時間の上限です。既定では再試行は無効(0)なので、使うなら明示します。

ヘルスチェックの間隔の既定は30秒、タイムアウトは5秒、正常とみなす応答は200です。この既定で足りない場合は、health_interval health_timeout health_status を依頼文に書き足します。

validateとadaptは何が違うのか

どちらもCaddyfileを検査しますが、通る範囲が違います。違いを知らないと、adaptだけ通して「検査済み」と報告されてしまいます。

くらべる

adaptとvalidateが見る範囲

変換まで

caddy adapt

Caddyfileを caddy adapt でCaddy本来のJSON設定に変換し、標準出力に書きます。構文の誤りは拾えますが、変換できることと動くことは別です。

準備まで

caddy validate

設定を読み込み、全モジュールを準備(provision)したうえで、実際には起動せずに終わります。読み込み時と準備時のエラーまで出ます。

公式のコマンドラインのページには、この差を示す例があります。次のCaddyfileです。

localhost
 
tls cert_notexist.pem key_notexist.pem

caddy adapt --config Caddyfile はエラーなしで成功します。一方、--validate を付けると、証明書ファイルが存在しないことが準備の段階で見つかり、次の形のエラーで落ちます。

adapt: validation: loading app modules: module name 'tls': provision tls: loading certificates: open cert_notexist.pem: no such file or directory

つまり、caddy adapt の成功は検査の合格を意味しません。caddy adapt --validate は変換と検証を1回で行えます。依頼文では「adaptだけでなくvalidateも」と書いておくと、取りこぼしがありません。

稼働中のCaddyではvalidateが落ちることがある

caddy validate は、モジュールを本当に準備します。そのため、排他的に資源を握るモジュールを使う設定では、すでにCaddyが動いていると失敗します。

公式の説明にある例は acme_server ディレクティブです。このディレクティブはデータベースを開き、同時に開けるのは1プロセスだけです。稼働中のCaddyがそれを握っていると、設定自体が正しくても、validateはデータベースのタイムアウトで失敗します。

この場合の検査は caddy reload に切り替えます。reloadは稼働中のプロセスの中で新しい設定を準備するので、資源を取り合いません。準備に失敗したときは、動いている設定がそのまま残ります。成功すれば新しい設定が適用されます。

状況使うコマンド
Caddyが動いていない / 資源を取り合うモジュールがない使うコマンドcaddy validate
稼働中のCaddyに対して変更を適用したい使うコマンドcaddy reload
同じ設定でも再準備させたい使うコマンドcaddy reload --force

caddy reload は管理用エンドポイント(admin endpoint)を使うため、無効化していると使えません。また、Caddyfileという名前のファイルや .caddyfile で終わるファイルは、--adapter なしでCaddyfileとして扱われます。ほかの名前にするなら --adapter caddyfile を付けます。Claude Codeに作らせるファイル名は Caddyfile に揃えると、コマンドが短くなります。

編集のたびに検査するPostToolUse hookを足す

依頼文に検査を書くだけだと、長い作業の途中で省かれることがあります。hookにすれば、Claude CodeがCaddyfileを書き換えるたびに、確実にvalidateが走ります。

PostToolUse hookは、ツール呼び出しが成功した直後に動きます。終了コード2で抜けるとstderrがClaude Codeに渡されます。ツールはすでに実行済みなので、書き込みを取り消す動きにはなりません。ただ、エラーの全文が会話に返るため、直しにつながります。

まず、プロジェクトの .claude/hooks/caddy-validate.sh に次のスクリプトを置き、chmod +x で実行可能にします。jq が必要です。

#!/usr/bin/env bash
f=$(jq -r '.tool_input.file_path // empty')
case "$f" in
  */Caddyfile|*.caddyfile) ;;
  *) exit 0 ;;
esac
if ! out=$(caddy validate --config "$f" 2>&1); then
  echo "$out" >&2
  exit 2
fi

hookはJSONを標準入力で受け取り、Edit と Write などのファイル系ツールでは tool_input.file_path が常に絶対パスで入ります。上のスクリプトは、末尾がCaddyfileのものだけを検査し、それ以外は何もせず通します。

次に、.claude/settings.json へ登録します。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/caddy-validate.sh"
          }
        ]
      }
    ]
  }
}

matcher はツール名に対する絞り込みです。Edit|Write なら、ファイルの編集と新規作成のどちらでも動きます。同じ型のhookをactionlintで組んだ例はClaude Codeのactionlint活用に、markdownlintで組んだ例はClaude Codeでmarkdownlint-cli2を回すにあります。検査を失敗時に止める側へ寄せたいときはhooksのonFailureが参考になります。

CLAUDE.mdに検査の手順を書いておく

hookはvalidateだけを受け持つので、fmtとadaptの確認はCLAUDE.mdで指示します。Caddyfileを触る作業で守らせたい規約の断片を示します。

## Caddyfile
 
- Caddyfile を編集したら、完了報告の前に次を順に実行する
  1. `caddy fmt --overwrite Caddyfile`
  2. `caddy validate --config Caddyfile`
  3. `caddy adapt --config Caddyfile --pretty`
- adapt だけを通して「検査済み」と報告しない。validate の結果を必ず確認する
- Caddy が稼働中のホストでは validate の代わりに `caddy reload` を使う
- reverse_proxy の転送先とヘルスチェックのパスは、変更前に確認する
- エラーが出たら握りつぶさず、メッセージ全文を読んで原因を直す

caddy fmt --overwrite は整形結果を入力ファイルへ書き戻します。標準出力に出すだけの --diff と違い、ファイルが書き換わる点に注意が要ります。差分を見てから整形したいときは、依頼文を caddy fmt --diff に変えます。

fmt は差分があると終了コード1で抜けます。Claude Codeがこれをエラーと受け取って手を止めないよう、CLAUDE.mdに「整形の差分は異常ではなく、書き戻して続ける」と一言添えておくと安定します。

adaptのJSONで、意図どおりに解釈されたかを読む

caddy adapt --pretty の出力は、人が読める形に整えたJSONです。Claude Codeに「このJSONを読んで、/api/* だけが転送されることを確認して」と頼むと、パスの絞り込みを機械的に再確認できます。

reverse_proxyは、既定でX-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Hostを設定します。ただし、これらのヘッダーは、受信したリクエストに付いていても既定では無視されます。なりすまし防止のためです。

Caddyの前にCDNなどがある構成では、trusted_proxies で信頼するIP範囲を指定します。公式の説明では、プロキシ単位ではなくグローバルの servers > trusted_proxies で指定することが強く推奨されています。サーバー内のすべてのプロキシに効き、クライアントIPの解釈も有効になるからです。

CDNを前段に置く構成をClaude Codeに設定させるなら、依頼文に「前段のプロキシのIP範囲」を渡します。範囲を渡さずに「いい感じに」と頼むと、推測の値が入りかねません。値の根拠は人間が持つ情報なので、依頼側で明示します。

つまずきやすい点

validateが通ってもバックエンドの疎通は保証されない。コマンドラインのページの説明は、設定の読み込みと準備までです。転送先が実際に応答するかは、curl でステータスを確かめるのが確実です。

curl -sS -o /dev/null -w "%{http_code}\n" https://example.com/api/healthz

この確認をClaude Codeに任せるときは、「ローカルで起動した状態で実行」と条件を付けます。本番のドメインに繰り返しリクエストを送らせない配慮です。

Caddyが失敗したら再起動を繰り返さない。終了コード1は起動失敗を表し、設定を直さない限り同じ理由で再び失敗します。公式にも「自動で再起動しない」とあります。Claude Codeには、起動失敗の後でsystemdなどを再起動し続けさせず、validateのエラーへ戻らせます。

試作だけなら設定ファイルが要らない。 caddy reverse-proxy --from <アドレス> --to <アドレス> は、1行でリバースプロキシを立てるコマンドです。デモや開発向けで、管理APIは無効になります。常用する設定は、ここで確かめた内容をCaddyfileに起こして管理するほうが追跡しやすくなります。

--insecure は安易に使わせない。このフラグは、転送先のTLS証明書の検証を無効にします。CLAUDE.mdに「--insecure を使うときは理由を報告する」と書いておくと、黙って入る事故を避けられます。

プロジェクト全体の設定(CLAUDE.mdとhooks)の組み方は、Claude CodeでGoアプリを開発する手順にも例があります。

まとめ

Caddyのリバースプロキシは、reverse_proxy 1行で書けます。だからこそ、書けたかどうかではなくvalidateが通ったかで完了を判定するのが肝です。adaptの成功だけでは、証明書ファイルの欠落のような準備段階のエラーは見つかりません。

運用では、PostToolUse hookでvalidateを自動化し、CLAUDE.mdでfmtとadaptの確認を指示します。稼働中のCaddyではreloadに切り替え、疎通はcurlで別に確かめます。この4点があれば、Claude Codeに任せる範囲を広げても、設定ミスが本番まで届く確率を下げられます。

この記事を共有:XはてブLinkedIn