2026年9月6日

2026年9月6日

Caddy でSSLターミネーションする手順(WordPress の前段に置く構成)

Caddy をリバースプロキシとして WordPress の前に置くと、証明書の取得・更新を Caddy に任せたまま、HTTPS の処理(SSLターミネーション)を一か所にまとめられます。この記事では、Caddyfile の書き方、バックエンドとの受け渡し、WordPress 側で必要な wp-config.php の設定、つまずきやすい点と確認方法までを順に説明します。

SSLターミネーションの構成と Caddy を選ぶ理由

SSLターミネーションとは、ブラウザとの TLS 通信をプロキシで終わらせ、その先のアプリケーションサーバーには HTTP(または別の TLS 接続)で渡す構成です。証明書と暗号設定をプロキシ側だけで管理すればよくなり、バックエンドの Apache や Nginx、PHP-FPM は HTTPS を意識せずに済みます。

Caddy の特徴は「Automatic HTTPS」です。Caddyfile にドメイン名を書くだけで、ACME プロトコルを使って証明書を自動で取得し、期限前に更新します。既定の発行元は Let's Encrypt と ZeroSSL ですが、既定値や挙動はバージョンで変わることがあるため、導入するバージョンの公式ドキュメントで確認してください(2026年10月現在)。certbot の cron やデプロイフックを自分で組む必要がないのが、Nginx や HAProxy で終端する場合との大きな違いです。

この記事で想定する構成は次のとおりです。

  • Caddy が 80/443 番ポートで待ち受け、証明書を管理する
  • WordPress は同じサーバーの 127.0.0.1:8080 で動く Apache または Nginx + PHP-FPM
  • バックエンドは外部から直接アクセスできない

作業前には、サイトのファイルとデータベースのバックアップを取り、可能ならステージング環境で一度通してから本番に適用してください。

事前に確認すること

DNS とポート

証明書を自動取得するには、ドメインの A/AAAA レコードが Caddy を動かすサーバーを向いていて、インターネットから 80 番と 443 番に届く必要があります。ACME の HTTP-01 チャレンジは 80 番、TLS-ALPN-01 チャレンジは 443 番を使うためです。クラウドのセキュリティグループ、ufw や firewalld、上流の CDN の設定で塞がれていないかを先に見ておきます。

# 待ち受けているプロセスを確認(既存の Apache/Nginx が 80/443 を掴んでいないか)
sudo ss -tlnp | grep -E ':(80|443)\s'

# DNS が意図したサーバーを向いているか
dig +short example.com A
dig +short example.com AAAA

既存の Apache や Nginx が 80/443 番で動いていると Caddy は起動できません。バックエンドを 8080 番などに移してから切り替えます。AAAA レコードが古いサーバーを向いたまま残っていると、IPv6 経由の検証だけが失敗して原因が分かりにくくなるので注意してください。

Caddy のインストール

Caddy は公式サイトで各 OS 向けのパッケージリポジトリの追加手順を案内しています。パッケージで入れると systemd のサービス(caddy.service)と設定ファイル /etc/caddy/Caddyfile が用意されます。リポジトリの URL や鍵の配布方法は変わることがあるので、公式のインストール手順をそのまま参照してください。

Caddyfile を書く

最小構成は数行です。

example.com, www.example.com {
	encode zstd gzip

	reverse_proxy 127.0.0.1:8080

	log {
		output file /var/log/caddy/example.com.access.log
	}
}

サイトアドレスにドメイン名を書いたことで、Caddy は証明書を取得し、HTTP へのアクセスを HTTPS へリダイレクトします。reverse_proxy は受け取ったリクエストをバックエンドに転送するディレクティブで、既定で X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host を付け、元の Host ヘッダーもそのまま渡します。WordPress は Host を見てサイト URL を組み立てるので、この既定の挙動は都合がよいものです。

www ありとなしのどちらかに統一したい場合は、片方をリダイレクト専用にします。

www.example.com {
	redir https://example.com{uri} permanent
}

example.com {
	encode zstd gzip
	reverse_proxy 127.0.0.1:8080
}

書いたら、反映前に構文を検証してから再読み込みします。reload は接続を切らずに設定を入れ替えます。

caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile
sudo systemctl reload caddy

validate を挟むのは、記述ミスがあると再読み込みが失敗し、原因をログで探すことになるためです。ログ出力先のディレクトリが存在しない、または caddy ユーザーに書き込み権限がないと起動に失敗する点にも注意します。

WordPress 側の設定

HTTPS と判定させる

Caddy で TLS を終端すると、バックエンドに届くリクエストは HTTP です。WordPress は is_ssl() で $_SERVER['HTTPS'] やポート番号を見て HTTPS かどうかを判定するため、何もしないと「HTTP で来た」と判断します。その結果、管理画面へのログイン後にリダイレクトが繰り返される、CSS や画像が http:// で読み込まれて混在コンテンツになる、といった症状が出ます。

wp-config.php の /* That's all, stop editing! */ より上に、次を追加します。

// Caddy から転送された HTTPS リクエストを HTTPS として扱う
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] )
	&& 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'] ) {
	$_SERVER['HTTPS'] = 'on';
}

このヘッダーはクライアントが自由に付けられるものなので、バックエンドが Caddy 以外から到達できない状態(127.0.0.1 やプライベートネットワークだけで待ち受ける)であることが前提です。バックエンドを外部に公開したままにすると、誰でも HTTPS を装えてしまいます。

あわせて、「設定」→「一般」の WordPress アドレスとサイトアドレスが https:// になっているか確認します。管理画面に入れない場合は、wp-config.php で WP_HOME と WP_SITEURL を一時的に定義すると復旧できます。

訪問者の IP アドレス

バックエンドから見た接続元は Caddy(127.0.0.1)になります。コメントのスパム判定やログイン試行の制限など、訪問者の IP を使う処理は、Apache なら mod_remoteip、Nginx なら real_ip モジュールで X-Forwarded-For を信頼する設定にします。セキュリティ系プラグインの中には、どのヘッダーから IP を取るかを選べるものがあります。信頼するのは Caddy のアドレスからのヘッダーだけに絞ってください。

Caddy 自体の前にさらに CDN やロードバランサーがある場合、Caddy は既定では受け取った X-Forwarded-* を信頼しません。上流を信頼させるにはグローバルオプションの trusted_proxies で上流の IP 範囲を指定します。範囲を広げすぎると偽装を許すので、上流事業者が公開しているアドレス範囲に限定します。

HSTS とセキュリティヘッダー

HTTPS が安定して動くことを確認できたら、HSTS を追加できます。最初は短い max-age で始め、問題がないことを確かめてから伸ばすのが安全です。HSTS はブラウザに記憶されるため、長い値を送った後で HTTPS をやめると、その期間は HTTP でアクセスできなくなります。

example.com {
	header Strict-Transport-Security "max-age=300"
	reverse_proxy 127.0.0.1:8080
}

includeSubDomains や preload は、すべてのサブドメインが HTTPS に対応していることを確認してから検討します。

バックエンドが別サーバーにある場合

Caddy と WordPress が別のサーバーにある場合、その間の通信は既定では暗号化されません。同じプライベートネットワーク内であれば許容されることも多いですが、インターネットを経由するなら reverse_proxy https://backend.example.internal:443 のように TLS で接続するか、VPN やプライベート接続を使います。複数台に振り分ける場合は reverse_proxy に複数のアップストリームを並べ、lb_policy と health_uri で振り分け方とヘルスチェックを指定できます。ただし WordPress を複数台で動かすなら、アップロードファイルの共有やキャッシュの整合も合わせて設計する必要があります。

つまずきやすい点と確認方法

証明書が取れない

最も多いのは、80/443 番に外部から届かない、DNS が別サーバーを向いている、の二つです。journalctl で Caddy のログを見ると、どのチャレンジで失敗したかが分かります。

sudo journalctl -u caddy --since "30 min ago" --no-pager

検証を何度も失敗させると認証局のレート制限にかかり、しばらく発行できなくなります。原因を直してから再試行してください。テスト中はグローバルオプションで ACME のステージング環境を指定する方法もあります。

リダイレクトループ

ブラウザに「リダイレクトが多すぎます」と出る場合は、前述の X-Forwarded-Proto の処理が入っていないか、CDN 側の SSL 設定がオリジンへ HTTP で接続する方式になっていて Caddy の HTTPS リダイレクトと衝突しているケースが多いです。curl -I で Location ヘッダーを順に追うと、どこで戻されているかが分かります。

証明書の確認

curl -sI https://example.com | head -n 5
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -dates

発行者と有効期限が表示されれば、Caddy の証明書が配信されています。証明書や ACME アカウントの情報は Caddy のデータディレクトリに保存されるため、サーバー移行時はこのディレクトリも引き継ぐと再発行を避けられます。

まとめ

  • Caddy はドメイン名を書くだけで証明書の取得・更新まで行うため、SSLターミネーションの運用負担を減らせる
  • 80/443 番の到達性と DNS の向き先を先に確認し、既存のサーバーはバックエンド用のポートへ移す
  • caddy validate で検証してから systemctl reload caddy で反映する
  • WordPress 側は X-Forwarded-Proto を見て $_SERVER['HTTPS'] を設定し、バックエンドは外部に公開しない
  • 訪問者 IP の扱いと HSTS は、信頼範囲と max-age を絞って段階的に設定する

お気軽にご相談ください

お見積りへ お問い合わせへ
▲