自動HTTPS付きでCaddy Webサーバーをインストールする方法
公式リポジトリからUbuntuにCaddyを入れ、静的サイトとリバースプロキシ用のCaddyfileを書き、自動証明書、systemd、ログ、Nginxからの移行でよくある失敗を理解します。

Caddyは、既定でTLS証明書を取得・更新するWebサーバーです。1つか2つのドメインをホストする小型VPSでは、CertbotのタイマーとNginxのスニペットファイル一式が消えます。設定言語(Caddyfile)は短いです。リバースプロキシ、gzip、HTTP/2は普通の機能であり、週末がかりの追加モジュールではありません。
Nginxは、すでに実戦済みの設定がある店、または非常に特定のモジュールが必要な店では、まだ正しい選択です。このガイドはもう一方のよくあるケース向けです。includeスニペットを暗記せずに、新しいHiddence VPSで動くHTTPSが欲しい場合です。公式aptリポジトリからCaddyを入れ、Let's Encryptとのやり取りを説明し、静的サイトを出し、ローカルアプリをプロキシし、複数ドメインをホストし、ログを見、Apacheや古いNginxとのポート80の奪い合いを扱います。
Caddyが合うとき
見出しは自動HTTPSですが、日常の勝ちは動く部品が少ないことです。Caddyは80と443を待ち、HTTPをHTTPSへリダイレクトし、証明書をデータディレクトリに保存します。ドメインがVPSを指している必要は残ります。ACMEを壊さない必要も残ります(ファイアウォールの80/tcp、:80を奪う他プロセスなし)。ワイルドカード証明書にはDNSプロバイダモジュールが必要で、単一ホスト証明書より長い道です。
- 自動のHTTP→HTTPSと証明書更新
- 長いserverブロックではなく読みやすいCaddyfile
- Node、Python、追加設定のPHP-FPM、Dockerバックエンド向けの十分なリバースプロキシ
- 暗号スイートの表なしでHTTP/2と現代的なTLS既定
- 公式パッケージのsystemdユニット
要件
CaddyがACME HTTP-01を証明できる前に、ドメインがこのVPSへ解決する必要があります。DNSがまだ伝播中だと発行に失敗して再試行します。『Caddyが壊れた』ように見えて、実際はDNSだけのことがあります。80/443を持っているなら、先にApacheまたはNginxを止めてください。
- Ubuntu 22.04または24.04
- VPS IPへのドメインAレコード(IPv6を使うならAAAAも)
- ポート80と443が空いており、ファイアウォールで許可済み
- rootまたはsudo
ステップ1:公式リポジトリからCaddyをインストールする
現行のTLSとACMEの挙動が欲しいなら、Ubuntu universeの古いCaddyを無作為に入れないでください。Caddyプロジェクトはaptソースを文書化しています。インストール後、caddyユーザーが存在し、サービスは有効になります。
ssh root@YOUR_VPS_IP
apt update && apt -y install debian-keyring debian-archive-keyring apt-transport-https curl gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt -y install caddy
caddy version
systemctl status caddy --no-pagerステップ2:最初のCaddyfile — 静的サイト
既定のCaddyfileは /etc/caddy/Caddyfile です。自分のドメインに置き換えます。ホスト名がlocalhostでなければ、設定の読み込みと同時に証明書を取りにいきます。サイトファイルはcaddyユーザーが読めるディレクトリへ置きます(www-data風の権限が多いですが、パッケージはユーザーcaddyです)。
mkdir -p /var/www/example
echo '<h1>It works</h1>' > /var/www/example/index.html
chown -R caddy:caddy /var/www/example
cat >/etc/caddy/Caddyfile <<'EOF'
example.com {
root * /var/www/example
file_server
encode gzip
}
EOF
caddy validate --config /etc/caddy/Caddyfile
systemctl reload caddy
# Watch issuance:
journalctl -u caddy -fステップ3:ローカルアプリケーションをリバースプロキシする
Gunicorn、Node、Dockerが 127.0.0.1:8000 で待っているなら、公開プロセスはCaddyだけにすべきです。reverse_proxyディレクティブは、ほとんどのアプリに妥当な形でHostとX-Forwarded-*を転送します。アプリが絶対HTTP URLを生成するなら、アプリ側で信頼できるプロキシ / HTTPSフラグを設定します(DjangoのSECURE_PROXY_SSL_HEADER、Expressのtrust proxyなど)。
app.example.com {
encode gzip
reverse_proxy 127.0.0.1:8000
}
# Several hosts in one file are normal:
# blog.example.com {
# root * /var/www/blog
# file_server
# }ステップ4:ファイアウォールとsystemd
80と443を許可します。Caddyのユニットはcaddy.serviceです。プロセスが健全なら、Caddyfile編集後はreloadで足ります。DNSプラグイン用の環境変数を変えたなら、フル再起動の方が明確です。証明書は既定で /var/lib/caddy/.local/share/caddy/ にあります。再インストール時のレート制限が気になるなら、そのパスをバックアップに含めてください。
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
systemctl enable --now caddy
systemctl reload caddy
# Backup cert storage (path may vary slightly by version):
ls -la /var/lib/caddy/ステップ5:ログ、圧縮、ヘッダー
ボットがパスを叩いたときや404のデバッグにアクセスログが役立ちます。サイトごとにログできます。ブラウザアプリをホストするならセキュリティヘッダーを足します。巨大なヘッダーパックを理解せずコピーしないでください。HSTSは長いmax-ageを一度出すとブラウザが覚えます。
example.com {
root * /var/www/example
file_server
encode gzip
log {
output file /var/log/caddy/example.log
}
header {
X-Content-Type-Options nosniff
Referrer-Policy no-referrer-when-downgrade
-Server
}
}
mkdir -p /var/log/caddy
chown caddy:caddy /var/log/caddy
systemctl reload caddyステップ6:PHPとその他の追加
Caddyはphp_fastcgiディレクティブでPHP-FPMと話せます。多くのWordPressやLaravelホストには足りますが、php-fpmのインストールと一致するソケットパスはまだ必要です。完璧なNginx PHP設定がすでにあるなら、一晩での移行は任意です。Caddyが輝くのはリバースプロキシ + 静的 + 自動TLSです。
example.com {
root * /var/www/example
php_fastcgi unix//run/php/php8.3-fpm.sock
file_server
}
# Confirm FPM is running:
systemctl status php8.3-fpmダウンタイムの驚きなくNginxから移行する
Caddyを始める前にNginxを止めてください。そうしないと80/443を奪い合います。IPも移すなら前日にDNS TTLを下げます。Aレコードを切り替える前に、curl --resolve で新しいVPSに当ててテストします。ロールバックに備え、Nginx設定は1週間gitに残します。
- systemctl stop nginx && systemctl disable nginx
- Caddyを入れ、Caddyfileを検証し、Caddyを起動
- curl -I --resolve example.com:443:NEW_IP https://example.com
- IPが新しい場合のみ、その後にDNSを変更
- 再発行は自動。同じホスト名に対してCertbotも動かさない
トラブルシューティング
ACMEの失敗はほぼ常にDNS、ファイアウォール、またはポート80上の別サービスです。Caddyのログ行はtls.obtainに触れます。HTTPでは動いてHTTPSでは動かないなら、発行が完了していません。証明書が多すぎるならLet's Encryptのレート制限です。テスト中は本番ではなくstaging CAを使います。
- validate失敗: Caddyfileの構文、欠けたブレース
- rootでpermission denied: サイトディレクトリをcaddyにchown
- bind: address already in use — ss -tulpn | grep -E ':80|:443'
- 証明書タイムアウト: VPSから dig +short でドメインを確認、ufw allow 80
- 502 reverse_proxy: バックエンドダウン、またはコンテナネットワークからlocalhostへ誤ってプロキシ
セキュリティ上の注意
CaddyのTLS既定は保守的です。あなたの仕事はアプリケーションセキュリティとSSHです。Caddyの管理APIを公開インターフェースで有効にしないでください。既定の管理エンドポイントはローカルです。そのままにしてください。インターネットのCaddyfileを使うなら、すべてのmatcherを読んでください。/* を内部IPへreverse_proxyするスニペットはオープンプロキシになり得ます。
- 管理APIを公開しない
- パッケージを更新し続ける
- すべてのホスト名でHTTPSが確実に動いてからHSTS
- ACMEレート制限を守るため、テストと本番のホスト名を分ける
ヒント
- caddy fmt --overwrite /etc/caddy/Caddyfile でファイルを読みやすく保つ
- 似たサイトが多いときはimportスニペットを使う
- Dockerでは、マウントしたCaddyfile付きのcaddy:alpineイメージが一般的
- ワイルドカード証明書にはDNSモジュールとAPIトークンが必要 — そのトークンを守る
- ファイル全体を書き直す前に journalctl -u caddy を読む
VPS上のCaddyは、公式パッケージ、短いCaddyfile、開いたポート80/443、すでにサーバーを指すDNS、編集後のsystemd reloadです。静的ファイルサーバーとして、またはGunicorn、Node、Dockerの手前のリバースプロキシとして使います。自動HTTPSは、ACMEがポート80で答えられるときに動きます。発行が失敗したら、Caddyを責める前にDNSとポート衝突を直します。よく再インストールするなら /var/lib/caddy をバックアップしてください。