Caddy の自動 HTTPS:Caddy の自動 TLS の仕組みとトラブル対処
Caddy は設定内のすべてのドメインについて、ACME で TLS 証明書を取得・更新します。HTTP-01、TLS-ALPN-01、DNS-01 によるワイルドカード、オンデマンド TLS、よくある失敗例を解説します。
結論:Caddy は、公開ドメイン名をサイトアドレスに指定すると自動的に HTTPS を有効にします。ACME(デフォルトは Let's Encrypt または ZeroSSL)で証明書を要求し、ポート 80 と 443 上の HTTP-01 または TLS-ALPN-01 チャレンジでドメインの管理権を証明し、HTTP を HTTPS にリダイレクトし、期限切れ前に証明書を更新します。追加の設定は一切不要です。
Caddy リバースプロキシとは、Caddy がその HTTPS 接続を終端し、`reverse_proxy` ディレクティブで別のポート、コンテナ、ホスト上のアプリへリクエストを転送するだけのものです。数行で TLS とプロキシを両立できるこの組み合わせこそ、Caddy が選ばれる最大の理由です。
3行で SSL 付きリバースプロキシ
DNS がサーバーを指し、ポート 80 と 443 に到達できる状態なら、この Caddyfile だけで2つのホスト名に対応した完全な HTTPS リバースプロキシになります。Caddy は起動時に両方の名前の証明書を取得し、HTTP/1.1、HTTP/2、HTTP/3 で配信し、`X-Forwarded-For`、`X-Forwarded-Proto`、`X-Forwarded-Host` をバックエンドに渡します。`email` オプションは任意ですが、指定しておくと期限切れや失効した証明書について CA から連絡を受けられます。
公式 apt リポジトリからのインストールと systemd の基本はCaddy リバースプロキシガイドで、コンテナについてはDocker リバースプロキシとしての Caddyで解説しています。変更は `sudo systemctl reload caddy` で反映します。
# /etc/caddy/Caddyfile
{
email [email protected]
}
example.com, www.example.com {
reverse_proxy 127.0.0.1:8080
}Caddy の自動 TLS の内部動作
Caddy は設定を読み込むと、サイトアドレスからすべてのホスト名を集め、それぞれの証明書を誰が発行すべきかを判断します。公開ドメインは公開 ACME 発行者へ、`localhost`、IP アドレス、内部専用の名前は Caddy 自身のローカル CA から証明書を受け取ります(`tls internal` と書くのと同じです)。証明書、鍵、ACME アカウントは Caddy のデータディレクトリに保存されます。apt パッケージでは `/var/lib/caddy/.local/share/caddy`、公式 Docker イメージでは `/data` です。
更新は期限切れのかなり前にバックグラウンドで行われ、CA が対応していれば Caddy は OCSP レスポンスをステープリングします。発行に失敗すると、Caddy は指数バックオフで再試行し、本番のレート制限を消費しないよう再試行には CA のステージングエンドポイントを使います。同じストレージを共有する複数の Caddy インスタンスは連携し、特定の証明書を要求するのは1台だけになります。
| チャレンジ | CA が確認する内容 | 必要なもの | ワイルドカード |
|---|---|---|---|
| HTTP-01 | `http://domain/.well-known/acme-challenge/` で配信されるトークン | インターネットからポート 80 に到達できること | 不可 |
| TLS-ALPN-01 | TLS ハンドシェイクで提示される特殊な証明書 | インターネットからポート 443 に到達できること | 不可 |
| DNS-01 | `_acme-challenge.domain` の TXT レコード | DNS プロバイダーの API トークンと、そのプロバイダーモジュールを含む Caddy ビルド | 可 |
DNS-01 によるワイルドカード証明書
`*.example.com` のようなワイルドカード証明書は、DNS-01 チャレンジでしか発行できません。標準の Caddy バイナリには DNS プロバイダーのプラグインが含まれていないため、xcaddy でビルドする(または公式ダウンロードページでプロバイダーを選ぶ)必要があり、そのゾーンの DNS 編集だけに権限を絞った API トークンを Caddy に渡します。DNS-01 は、内部専用サーバーなどポート 80 と 443 にインターネットから到達できない場合にも使えます。
トークンは Caddyfile に書かないでください。systemd ユニットが使う環境ファイルに置き(`sudo systemctl edit caddy` を実行し、`[Service]` の下に `EnvironmentFile=/etc/caddy/caddy.env`)、`chmod 600` でファイルのアクセスを制限します。
# Build Caddy with a DNS provider module (example: Cloudflare)
xcaddy build --with github.com/caddy-dns/cloudflare
# Caddyfile: one wildcard certificate for all subdomains
*.example.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
@app host app.example.com
handle @app {
reverse_proxy 127.0.0.1:3000
}
handle {
respond "Unknown host" 404
}
}オンデマンド TLS とステージング
オンデマンド TLS を使うと、Caddy は初めて見るホスト名について、最初の TLS ハンドシェイク中に証明書を取得します。顧客が自分のドメインをあなたのサーバーに向けるようなプラットフォーム向けの機能です。必ず `ask` エンドポイントを設定してください。Caddy は `?domain=name` を付けてそれを呼び出し、200 応答の場合にのみ処理を続けます。これがないと、誰でも適当な名前をあなたの IP に向けて、レート制限に到達させることができてしまいます。
試行錯誤している間は、グローバルオプション `acme_ca` で Let's Encrypt のステージング CA に切り替えましょう。ステージングの証明書はブラウザに信頼されませんが、制限がはるかに緩いので、自由に壊して直せます。作業が終わったらオプションを削除し、必要に応じてデータディレクトリからステージング証明書を削除してください。
{
on_demand_tls {
ask http://127.0.0.1:5555/allowed
}
}
https:// {
tls {
on_demand
}
reverse_proxy 127.0.0.1:8080
}
# Global options block for testing
{
email [email protected]
# Test against Let's Encrypt staging; remove when everything works
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}Caddy の TLS でよくある失敗と対処法
- 01.DNS が向いていない:A または AAAA レコードがない、まだ古いサーバーを指している、あるいはキャッシュされている。CA は IPv6 があればそちらを優先するため、古い AAAA レコードは典型的な落とし穴です。`dig` で両方を確認しましょう。
- 02.ポート 80 または 443 が閉じている:ホストのファイアウォール、クラウドのファイアウォール、ルーターが受信トラフィックをブロックしているため、HTTP-01 も TLS-ALPN-01 も完了できません。80/tcp と 443/tcp(HTTP/3 用に 443/udp も)を開放します。
- 03.ポートが使用中:Apache、nginx、別のコンテナが 80 や 443 を使っているため、Caddy が待ち受けできません。他のサービスを停止するか、Caddy の背後に移します。
- 04.前段に CDN プロキシがある:オレンジ雲タイプのプロキシが Caddy より先に TLS を終端し、TLS-ALPN-01 が失敗することがあります。DNS-01 を使うか、発行中はレコードを DNS のみに設定します。
- 05.レート制限:失敗を繰り返したりデータディレクトリを消去したりすると、CA の制限に達します(Let's Encrypt は、ホスト名ごと1時間あたりの検証失敗回数と、1週間あたりの重複証明書数を制限しています)。根本原因を直し、ステージングを使い、データディレクトリを永続化してください。
- 06.CAA レコード:別の CA のみを許可する CAA レコードは発行をブロックします。`letsencrypt.org` と `sectigo.com`(ZeroSSL が使用)を許可するか、制限を削除します。
# Does the name resolve to this server (both IPv4 and IPv6)?
dig +short A example.com
dig +short AAAA example.com
curl -4 -s https://ifconfig.me ; echo
# Are ports 80 and 443 open in the host firewall?
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
# Is something else already bound to 80/443?
sudo ss -ltnp '( sport = :80 or sport = :443 )'
# What does Caddy say?
sudo journalctl -u caddy --no-pager -n 100バックエンドへの通信も HTTPS に
同じホスト上のバックエンドの多くは localhost で平文 HTTP を話しますが、それで問題ありません。上流が別のマシンにある場合や TLS を必須とする場合は、`https://` の上流を使い、検証する名前と信頼する CA を Caddy に指定します。`tls_insecure_skip_verify` は、プロキシとバックエンド間の盗聴に対する保護がなくなるため、簡単なテスト以外では避けてください。サーバー間の通信には、バックエンドの証明書を管理するより、プライベートなWireGuard トンネルの方がシンプルなことが多いです。
フル root 権限と専用 IPv4 アドレスを備えた NoctisNode のVPSでは、一般的な構成は次のとおりです。A レコードをサーバーに向け(サーバーに IPv6 がない限り AAAA は設定しない)、80 と 443 を開放し、残りは Caddy に任せます。HTTP 以外のトラフィックを転送する必要がある場合は、Caddy による TCP と SSH のプロキシを参照してください。
example.com {
reverse_proxy https://10.0.0.5:8443 {
transport http {
tls_server_name backend.internal
tls_trust_pool file /etc/caddy/backend-ca.pem
}
}
}よくある質問
QCaddy リバースプロキシとは何ですか?
ACaddy Web サーバーがクライアントの接続を受け付け、通常は HTTPS を終端し、`reverse_proxy` ディレクティブで1つ以上のバックエンドサービスへリクエストを転送するものです。そのための TLS 証明書は Caddy が自動で取得・更新します。
QCaddy は証明書を自動で更新しますか?
Aはい。ドメインが引き続きサーバーを指し、チャレンジ用ポートに到達できる限り、Caddy は期限切れのかなり前にバックグラウンドで証明書を更新し、再起動なしで読み込み直します。
QCaddy が証明書を取得できないのはなぜですか?
A最も多い原因は、DNS レコードが別の場所を指している(古い AAAA レコードを含む)、ポート 80 と 443 がブロックされている、別のプログラムがそれらのポートを使っている、失敗の繰り返しでレート制限に達した、のいずれかです。`journalctl -u caddy` で正確な ACME エラーを確認できます。
QCaddy はワイルドカード証明書を発行できますか?
Aはい。ただし DNS-01 チャレンジ経由のみで、お使いの DNS プロバイダーのモジュールを含む Caddy ビルドと、そのゾーン用の API トークンが必要です。
QCaddy の自動 HTTPS を無効にするには?
A特定のサイトだけなら `http://` のサイトアドレスを使い、全体ならグローバルオプション `auto_https off` を設定します。`auto_https disable_redirects` を使えば、証明書は維持したまま HTTP から HTTPS へのリダイレクトだけを止めることもできます。