Nginx Proxy Manager から Caddy に乗り換えたら、設定ファイルが 1 つになって Git に入った — 自宅サーバーのリバースプロキシを選び直す

自宅のラズパイで写真の保管や録画をしていて、「外から HTTPS で見たい」と思った瞬間に必要になるのがリバースプロキシです。入口を 1 つにまとめ、証明書を取り、各アプリに振り分ける役目です。この用途で最も広く使われているのが Nginx Proxy Manager(NPM)で、私も長く使っていました。この記事では、NPM から Caddy(公式サイト)に乗り換えて何が変わったか、なぜ Caddy の方が自宅サーバーに向いていると判断したか、そして乗り換えの手順をまとめます。

自宅サーバーの構成と、リバースプロキシが HTTPS を提供する仕組み

まず、リバースプロキシが何をしているかを図で押さえます。ラズパイ 1 台の上で複数のアプリ(写真の Immich、録画の EPGStation、認証の Authelia など)が Docker コンテナとして動き、その手前に 1 つだけリバースプロキシが立っています。

自宅サーバーの構成図:外に開くのは 443 だけで、リバースプロキシ(Caddy)が HTTPS を終端して各アプリに振り分ける
図 1:外に開いているのは 443 だけ。証明書と HTTPS はリバースプロキシが一手に引き受け、各アプリは内部で HTTP のまま動く

流れはこうです。ブラウザが https://photos.example.com にアクセスすると、ルーターが 443 番をラズパイに転送し、リバースプロキシが受け取ります。そして、プロキシは Let's Encrypt の証明書で HTTPS を終端し、ホスト名を見て内部の Immich コンテナに HTTP で渡します。つまり、アプリ側は HTTPS も証明書も知らなくてよい。これが「入口を 1 つにする」価値です。

Nginx Proxy Manager は、なぜ広く使われているか

NPM は、この役目を「ブラウザの管理画面だけで」こなせるようにしたツールです。例えば、Docker で起動し、画面でホスト名と転送先を入れ、ボタンを押せば Let's Encrypt の証明書が取れる。そのため、nginx の設定を一行も書かずに済み、自宅サーバー入門の定番になりました。優れたツールで、私も不満なく使っていました。

しかし、構成が育ってくると不満が出てきました。

  • 設定がどこにあるか分からない。 画面で入れた内容は SQLite に入り、そこから nginx の設定が自動生成され、証明書は別のディレクトリに置かれます。そのため、「今の構成」を一覧で見るには画面を一つずつ開くしかありません。
  • 変更の履歴が残らない。 画面で変えた結果は、誰がいつ何を変えたか残りません。だから「先週まで動いていたのに」の原因を追えません。
  • 再現できない。 例えば、ラズパイを作り直すとき、NPM のデータベースごと復元するか、画面を見ながら手で入れ直すかしかありません。
  • 詳細設定は結局 nginx を書く。 認証の連携やヘッダーの追加など、画面に無い設定は「カスタム設定」欄に nginx の文法で書きます。つまり、GUI の楽さと nginx の複雑さの両方を背負うことになります。
  • 管理画面そのものが攻撃面になる。 NPM の管理画面は Web アプリで、過去に既定パスワードや脆弱性の問題がありました。守るべき対象が 1 つ増えます。

Caddy が優れている点

Caddy は Go で書かれたオープンソースの Web サーバーで、「HTTPS を自動で、設定を最小で」を設計思想にしています。次に、NPM と比べて自宅サーバーに向いていると判断した理由を挙げます。

1. 設定ファイルが 1 つ

すべての設定は Caddyfile という 1 つのテキストファイルに書きます。どのホスト名をどこにどう転送し、認証はどこにかけているか、全部が 1 画面で見渡せます。つまり、「設定はどこにあるか」という問いが消えます。

2. Git で管理できる

テキストファイルなので、Git に入れて変更ごとにコミットし、差分を見て、間違えたら戻せます。また、ラズパイを作り直すときは Caddyfile を置くだけで同じ構成が再現します。NPM で「データベースごと復元」していた作業が、ファイル 1 つのコピーになります。

3. 速い設定が最初から入っている

Caddy は、何も書かなくても現代の推奨設定で動きます。HTTPS は自動(証明書の取得・更新まで)、HTTP/2 と HTTP/3 は既定で有効、TLS の暗号方式も安全なものだけです。そのため、nginx で「速くて安全な設定」のために集めていたテンプレートが不要になります。HSTS や圧縮も 1 行です。

4. 証明書で悩まない

ホスト名を書けば、その証明書は Caddy が自分で取り、期限前に自分で更新します。ポート 80 を開けたくない場合は DNS 認証(DNS-01)に切り替えれば、ワイルドカード証明書も内部ホストの証明書も取れます。例えば Cloudflare なら、API トークンを 1 つ渡すだけです。

5. 認証の連携が 1 行

Authelia や Authentik のような認証サービスを手前に置く設定は、nginx だと数十行の決まり文句が要ります。一方、Caddy は forward_auth という 1 つの命令で済みます。さらに「スニペット」として一度書いておけば、守りたいホストに import authelia と書くだけで同じ保護がかかります。

6. 更新で止まらない、間違えても落ちない

設定の再読み込みは接続を切らずに行われます。また、再読み込みの前に caddy validate で文法を検査でき、間違った設定を読ませても古い設定のまま動き続けます(起動時の文法エラーだけは止まるので、validate を習慣にします)。

7. 小さくて安全な部品

Caddy はメモリ安全な Go で書かれた 1 つの実行ファイルです。NPM のように nginx と Node.js とデータベースを抱えたコンテナではなく、プロセス 1 つです。そのため、コンテナを非 root で動かし、ファイルシステムを読み取り専用にする締め付けも簡単に通ります。さらに、管理画面が無いので守る必要もありません。

8. 静的サイトもそのまま

file_server と書けば、そのホストは静的ファイルの配信になります。つまり、公開ページやメンテナンスページのために別のコンテナを立てる必要がありません。

NPM にあって Caddy に無いもの

公平のために書いておくと、ブラウザの管理画面はありません。設定はファイルを編集して再読み込みです。ただし自宅サーバーの規模では、画面で 5 か所クリックするより 1 行書いて reload する方が速く、しかも履歴が残ります。また、TCP のそのままの転送(データベースのポートを外に出すなど)は標準には無く、プラグインが要ります。

Caddy の設定ファイル(Caddyfile)の実例

そして、乗り換え後の設定を、ホスト名を例に置き換えて載せます。これが私の家の HTTPS の「全部」です。

{
	email you@example.com
	acme_dns cloudflare {env.CF_API_TOKEN}      # DNS 認証。ポート 80 を開けずに証明書を取る
}

# 共通設定: 圧縮、HSTS、JSON のアクセスログ
(common) {
	encode zstd gzip
	header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
	log {
		output file /var/log/caddy/{args[0]}.log {
			roll_size 10MiB
			roll_keep 5
		}
		format json
	}
}

# 認証サービス(Authelia)で守る
(authelia) {
	forward_auth authelia:9091 {
		uri /api/authz/forward-auth
		copy_headers Remote-User Remote-Groups Remote-Email Remote-Name
	}
}

# 認証ポータル
auth.example.com {
	import common auth
	reverse_proxy authelia:9091
}

# 写真(Immich)。Immich は自前の認証を OIDC で Authelia に委ねているのでここでは素通し
photos.example.com {
	import common photos
	reverse_proxy immich-server:2283
}

# 録画(EPGStation)。パスキー認証を手前にかける
tv.example.com {
	import common tv
	import authelia
	reverse_proxy epgstation:8888
}

# 公開ページ(静的ファイル)
www.example.com {
	import common www
	root * /srv/www
	file_server
}
example.com {
	redir https://www.example.com{uri} permanent
}

ホストを 1 つ足すときは、ブロックを 1 つ書き足して再読み込みするだけです。そして、証明書は Caddy が勝手に取ります。

Nginx Proxy Manager は画面・DB・生成設定・証明書に分散するが、Caddy は Caddyfile 1 つを Git で管理できる
図 2:NPM は画面・DB・生成ファイル・証明書に分かれるが、Caddy は Caddyfile 1 つを Git に入れれば全部が残る

NPM から Caddy への乗り換え手順

半日あれば終わります。また、切り替えの瞬間以外は NPM を動かしたままなので、サービスの停止はほぼありません。

1. NPM の設定を棚卸しする

まず、管理画面の Proxy Hosts を上から順に書き出します。ホスト名、転送先(IP とポート)、認証や特別な設定の有無です。SQLite から一覧を出すこともできます。

sqlite3 ./data/database.sqlite "select domain_names, forward_scheme, forward_host, forward_port from proxy_host where is_deleted = 0;"

2. Caddy の Docker イメージを用意する

DNS 認証を使うなら、Cloudflare などの DNS プラグイン入りのイメージを自分で作ります(数行の Dockerfile)。一方、HTTP 認証(ポート 80 を開ける)でよければ、公式の caddy イメージをそのまま使えます。

FROM caddy:builder AS builder
RUN xcaddy build --with github.com/caddy-dns/cloudflare

FROM caddy
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
# docker-compose.yml(抜粋)
services:
  caddy:
    build: .
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"      # HTTP/3
    environment:
      CF_API_TOKEN: ${CF_API_TOKEN}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./site:/srv/www:ro
      - caddy-data:/data        # 証明書と鍵。消さない
      - caddy-config:/config
volumes:
  caddy-data:
  caddy-config:

3. Caddyfile を書く

次に、棚卸しした一覧を上の例のように 1 ホスト 1 ブロックで書きます。NPM の「カスタム設定」欄に書いていた nginx の設定は、ほとんどが Caddy では不要か 1 行になります。書いたら文法を検査します。

docker run --rm -v ./Caddyfile:/etc/caddy/Caddyfile:ro caddy caddy validate --config /etc/caddy/Caddyfile

4. Caddy を別のポートで試す

まず、NPM を動かしたまま、Caddy を一時的に 8443 番で起動し、curl でホスト名を指定して動作を確認します。この段階で証明書の取得まで確認できます(DNS 認証ならポート 80 が無くても取れます)。

curl -I --resolve photos.example.com:8443:127.0.0.1 https://photos.example.com:8443/

5. 切り替える

NPM を止め、Caddy のポートを 80 と 443 に戻して起動します。ルーターの転送先は同じラズパイなので変更不要です。そして、各ホストをブラウザで開いて確認します。ここだけが数十秒の停止です。

docker compose -f /path/to/npm/docker-compose.yml down
docker compose up -d
docker compose logs -f caddy      # 「certificate obtained successfully」が出れば証明書は取れている

6. 後片付け

最後に、1 週間ほど問題なければ NPM のデータを消します。それまでは戻せるように残しておきます。Caddyfile は Git に入れ、変更するたびにコミットします。

Caddy に乗り換えて変わったこと

  • まず、ホストの追加が「ブロックを 1 つ書いて reload」になり、1 分で終わるようになりました。
  • また、「今の構成」が常に Git の最新コミットにあり、ラズパイを作り直す不安が消えました。
  • そして、認証(パスキー)を全ホストにかける作業が、import authelia を書き足すだけになりました。
  • さらに、HTTP/3 と HSTS と圧縮が、設定らしい設定なしで有効になりました。
  • 最後に、管理画面が無くなり、守る対象が 1 つ減りました。コンテナは非 root・読み取り専用で動いています。

NPM は自宅サーバーの入口として優れた入門ツールで、それで足りている間は替える必要はありません。ただ、ホストが増え、認証を足し、構成を人に説明したり作り直したりする段階になったら、Caddy の「1 ファイルで全部」は効いてきます。半日の作業で得られる安心としては、割がよいと思います。なお、サーバーを公開せずに VPN で届かせる構成は別記事で扱っています。

Author

新規CTA