Nginx・Caddy・Traefikの比較と選び方
1つのVPSとパブリックIPで4つのアプリを公開するなら、証明書、設定量、WebSocket、Dockerルーティングを比較して最適なプロキシを選べます。
Nginx と Caddy と Traefik の比較: 要点
Nginx、Caddy、Traefik は、いずれもリバースプロキシとして同じ役割を果たします。443 番ポートで待ち受け、各リクエストのホスト名を読み取り、VPS 上の正しいサービスへ転送します。3 つのいずれを使っても、4 つのセルフホストアプリを 1 つのパブリック IP アドレスの背後で運用できます。いずれも十分に高速なため、遅くなるとすればアプリ側です。違いは、TLS (transport layer security) 証明書の取得方法と、アプリを追加するたびに必要となる設定量です。もう 1 つの違いは、一般的なチュートリアルでは扱われない要件が発生したときに表面化します。
HTTPS の処理を任せたい、かつサービスが一般的な Web アプリであれば Caddy を選びます。すべてを Docker Compose で実行し、数週間ごとに新しいサービスを追加するなら Traefik を選びます。すでに Nginx を運用している場合、またはレスポンスキャッシュ、クライアント証明書、raw TCP 転送、大規模な既存設定を必要とし、書き直したくない場合は Nginx を選びます。
各方式では TLS 証明書をどのように取得しますか?
多くの人にとって、この軸で選択が決まるため、ここから確認してください。3 つの方式はいずれも、同じ認証局から同じ証明書を取得します。ただし、そこに至るまでの作業は異なります。
Caddy はホスト名を指定すると証明書を要求します。 app.example.com をサイトアドレスとして記述すると、Caddy は ACME(自動証明書管理環境)を使用して Let's Encrypt に証明書を要求します。失敗した場合は ZeroSSL にフォールバックします。また、port 80 で HTTP から HTTPS へのリダイレクトを提供し、証明書を自動更新します。別のツールや、確認用の timer は必要ありません。証明書は caddy ユーザーのデータディレクトリに保存されます。パッケージでインストールした場合は /var/lib/caddy/.local/share/caddy です。このパスをバックアップ対象に追加するか、再構築後に新しく証明書を発行する運用を受け入れてください。公開されていないホスト名の場合、tls internal で Caddy 独自のローカル認証局を使用して署名します。これは Ubuntu で自己署名証明書を作成する場合と同じ構成ですが、更新は Caddy が自動で処理します。
Nginx には ACME クライアントを追加する必要があります。 一般的なのは Certbot です。Certbot が証明書を取得し、--nginx plugin が server block を書き換えて、443 listener とリダイレクトを追加します。更新はパッケージがインストールする systemd timer から実行されるため、確認すべき要素が 2 つあります。systemctl list-timers | grep certbot で timer の存在を確認し、sudo certbot renew --dry-run で更新処理が現在も機能することを検証します。手順の詳細は Nginx を使用した Ubuntu 24.04 の Certbotにあります。サブドメインをすべて列挙したくない場合は、同じツールで DNS-01 challenge による wildcard certificateにも対応できます。
2025 年 8 月以降は、Certbot を使わない公式の方法もあります。ngx_http_acme_module は dynamic module で、nginx にコンパイルして組み込むのではなく、nginx.org 独自の repository から nginx-module-acme package としてインストールします。acme_issuer block で認証局を指定し、server block 内の acme_certificate line で証明書を要求します。その後、nginx が証明書を自動更新します。2026 年 10 月時点では、この module は HTTP-01 challenge と TLS-ALPN-01 challenge に対応していますが、wildcard name には対応していません。そのため、wildcard certificate には引き続き Certbot と DNS-01 が必要です。
Traefik は独自の ACME クライアントを内蔵しています。 static configuration で certificate resolver を 1 つ設定すると、すべての router で使用できます。account key と証明書を含むすべての状態は、1 つの acme.json file に保存されます。Traefik は、所有者以外がその file を読み取れる場合、その file を使用しません。resolver を無効にする前に、そのことを通知します。
The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600directory を mount し、Traefik 自身に file を作成させてください。最初に touch で directory を作成すると、自分の umask が適用されます。多くの人がこのエラーに遭遇する理由はこれです。
3 つすべてに共通する点が 1 つあります。HTTP-01 challenge では、インターネットから port 80 に到達できる必要があります。認証局が port 80 に接続して検証するためです。443 だけを開放すると、DNS の問題のように見える形で証明書の発行に失敗します。
3 つの設定で同じ 2 アプリケーションのルーティングを行う
目的は次のとおりです。app.example.com は 127.0.0.1:8080 上のサービスへ、files.example.com は 127.0.0.1:8081 上のサービスへ転送し、どちらも HTTPS を使用します。各プロキシで全体を示し、説明ではなく設定の記述量の違いが分かるようにします。
Nginx
# /etc/nginx/sites-available/app.example.com
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}次にリンクを作成し、構文を確認してから reload し、証明書を追加します。
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.comnginx -t で syntax is ok と test is successful を出力することが、reload の前に毎回実行する確認です。2 つ目のアプリケーションも同じブロックを使い、ホスト名とポートだけを変更します。proxy_set_header の行は飾りではありません。proxy_pass がアドレスを指定すると、nginx はデフォルトで Host: 127.0.0.1:8080 を upstream に送信します。そのため、Host ヘッダーから絶対 URL を生成するアプリケーションでは、ユーザーが localhost に誘導されます。これら 4 つのヘッダーの用途と、proxy_pass の末尾にスラッシュを付けるとアプリケーションが受け取るパスが意図せず変わる理由については、nginx の server block の詳細な解説でディレクティブごとに説明しています。
Caddy
app.example.com {
reverse_proxy 127.0.0.1:8080
}
files.example.com {
reverse_proxy 127.0.0.1:8081
}sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddyファイル全体はこれだけです。reverse_proxy が X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host を自動的に設定します。また、デフォルトではクライアントがこれらのヘッダーで送信した値を無視するため、リクエストでバックエンドに送信元を偽装することはできません。証明書、ポート 80 からの redirect、更新は、2 つのサイトアドレスから自動的に設定されます。ファイル内で追加の設定を指定する必要はありません。
Traefik
Traefik でルーティングするには、最初に静的設定が必要です。Compose のサービスとして、2026 年 10 月時点で最新の image tag を使う場合は次のようになります。
services:
traefik:
image: traefik:v3.7
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.le.acme.email=you@example.com"
- "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt:/letsencrypt次に、各アプリケーションのルーティングを、それぞれの compose ファイル内で labels に指定します。
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`app.example.com`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=le"
- "traefik.http.services.app.loadbalancer.server.port=8080"loadbalancer.server.port はコンテナ内部のポートであり、公開ポートではありません。Traefik は共有 Docker network 経由でコンテナに接続するためです。アプリケーション側には ports: の行も必要ありません。これがこの構成の本当の利点です。公開するのは Traefik だけです。共有 network と redirect middleware を含む完全な構成は、Traefik と Docker Compose で複数のアプリケーションをルーティングする方法で説明しています。
追加するアプリごとに必要な設定量
このグラフのデータ
[
{
"tool": "Nginx",
"proxy_setup_lines": 0,
"lines_per_app": 11
},
{
"tool": "Caddy",
"proxy_setup_lines": 0,
"lines_per_app": 3
},
{
"tool": "Traefik",
"proxy_setup_lines": 17,
"lines_per_app": 5
}
]上記のブロックを基準に数えています。Nginx の server block は空白でない行が 11 行あり、ホスト名ごとに同じ内容を記述します。Caddy の site block は 3 行です。Traefik は、1 件のリクエストを処理する前に静的設定を 17 行記述し、その後はアプリごとに 5 個のラベルを設定します。
単純な勝敗ではなく、トレードオフを確認してください。Traefik は最初のアプリを追加するまでのコストが最も高く、2 つ目以降のアプリごとのコストが最も低くなります。2 つの合計は、およそ 3 つ目のサイトで並びます。それより少ない場合、静的設定は不要なオーバーヘッドです。それより多い場合は、ラベル方式が先行し、その差が広がり続けます。ルーティング設定が、ルーティング対象のサービスの近くに置かれるためです。サービスを削除すると、そのルートも一緒に削除されます。これは、数か月前に存在しなくなったアプリの古い server block が残りやすい中央設定ファイルにはない利点です。
行数だけでは Nginx が有利に見えます。しかし、各ブロックには symlink の作成、nginx -t、reload、certbot の実行が必要です。一方、Caddy の編集では reload が 1 回必要なだけで、Traefik の編集ではコマンド自体が不要です。3 つとも、稼働中の接続を切断せずに reload できます。違いは、午前 1 時に覚えておく必要がある個別の手順数です。
コンテナを把握できるのはどれですか?
Traefik は Docker socket を監視し、コンテナの起動と停止に応じて、コンテナの labels から routers を構築します。ここで同じことができるものは他にありません。Nginx と Caddy は、新しいコンテナが現れるたびに設定を編集して reload する必要があります。また、到達可能なアドレスも必要です。loopback で公開された port、または proxy を接続した共有 Docker network のいずれかを用意します。
この機能には代償があります。明確に理解しておく必要があります。Traefik は /var/run/docker.sock を読み取ります。この socket と通信できるユーザーは、ホストの filesystem をコンテナ内に mount した状態でコンテナを起動できます。これにより、ホスト上の root 権限を取得できます。read only で mount すればリスクは下がりますが、なくなるわけではありません。脅威モデル上これが問題になる場合は、socket proxy を間に配置し、Traefik に必要な container list endpoints だけを公開します。
Caddy は community plugin によって label ベースの discovery を利用できます。ただし、Caddy plugins はバイナリに組み込んでコンパイルする必要があります。そのため、xcaddy を含む custom binary または custom image を作成し、その build と更新を自分で管理することになります。サービスが 3 つか 4 つであれば、Caddyfile を編集するほうが手間は少なくて済みます。
WebSocket とストリーミングで壊れるもの、その理由
対応が必要なのは Nginx です。WebSocket 接続は Upgrade: websocket を含む HTTP リクエストとして開始されますが、指示しない限り nginx は hop-by-hop ヘッダーを upstream に渡しません。
# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}次に、location ブロック内に、必ずすべて記述する 3 行があります。
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;これらを省略すると、ブラウザーのコンソールには WebSocket connection to 'wss://app.example.com/ws' failed と表示され、バックエンドのログには通常の GET と記録されます。map が必要なのは、固定した Connection: upgrade を指定すると、通常のリクエストにも毎回送信されてしまうためです。通常のリクエストでは close とする必要があります。
Nginx のデフォルト設定には、もう 2 つ注意点があります。proxy_read_timeout は 60 秒で、アップグレード後のトンネルにも適用されます。そのため、1 分間トラフィックがない WebSocket はプロキシによって切断されます。また、SSE は、その location に proxy_buffering off; を設定するまで、遅れて届いたり、まとめて届いたりします。nginx がレスポンスをバッファーに保持するため、ページ側は待たされます。
Caddy は追加のディレクティブなしでアップグレードを実行し、接続を双方向トンネルに切り替えます。また、レスポンスが text/event-stream であるか、既知の長さを持たない場合は直ちにフラッシュするため、そのままストリーミングを利用できます。Traefik はアップグレードを通過させ、独自の buffering middleware を追加しない限りレスポンスをバッファーしません。サービスにチャット、Web ターミナル、ログの tail、ライブダッシュボードが含まれる場合、記述してデバッグする設定量に明確な差が生じます。
WebSocket と SSE を含む完全な Nginx server block
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name app.example.com;
client_max_body_size 64m;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_buffering off;
}
}map は http コンテキストに置くもので、server の内部には置きません。そのため、/etc/nginx/conf.d/ 配下の専用ファイルに記述してください。proxy_buffering を無効にするのはストリーミングを行う location だけにしてください。通常のレスポンスでは、バッファリングによって nginx がバックエンドの worker を早く解放できるためです。Certbot は実行時にこのブロックを書き換えるため、実行後にファイルをもう一度確認してください。
特別な要件がある場合
このような場面で、Nginx の設定行数の多さが役立ちます。
- クライアント証明書。mTLS(相互 TLS)とも呼ばれ、クライアントも証明書を提示する必要がある構成です。Nginx では server ブロックに
ssl_client_certificate /etc/ssl/ca.pem;とssl_verify_client on;を指定します。Caddy ではtls内にclient_authブロックを配置します。Traefik のラベルだけでは表現できません。file provider で TLS オプションを定義し、traefik.http.routers.app.tls.options=mtls@fileで router に割り当てます。ラベルだけですべてを設定するモデルでも、この要件が初めて必要になった時点で例外が生じます。 - 大容量アップロード。Nginx はデフォルトでリクエストボディを 1 MB に制限します。上限を超えるアップロードでは
413 Request Entity Too Largeが返され、エラーログにはclient intended to send too large bodyと記録されます。client_max_body_sizeを増やしてください。Caddy と Traefik にはデフォルトのボディサイズ制限がないため、リクエストはアプリに到達し、アプリ側の制限が適用されます。 - レスポンスキャッシュ。Nginx には
proxy_cacheがあり、成熟した機能です。Caddy では plugin を組み込んでコンパイルする必要があります。Traefik の open source build には HTTP キャッシュがまったくありません。すべての proxy がキャッシュすると思っていると、意外に感じる点です。 - raw TCP または UDP。データベース用ポートやゲームサーバーで使用します。Nginx には
streammodule があります。Traefik では、それぞれの entrypoint に TCP router と UDP router を設定できます。Caddy では別の plugin が必要なため、別の custom build も必要です。 - proxy の背後にすでに Web server がある場合。サービスが従来型の PHP アプリケーションであれば、Ubuntu 24.04 上の LAMP stack にはすでに Apache が含まれています。その前段に proxy を追加すると、header を設定する場所と URL を書き換える場所がそれぞれ 2 つになります。どちらで TLS 終端を担うかを決め、もう一方は loopback に bind した plain HTTP のままにしてください。
この選択に続くファイアウォールの落とし穴
リバースプロキシを使う目的は、80 と 443 だけを開放することです。Docker はこれを気付かないうちに無効にします。-p 8080:80 でポートを公開すると、nat テーブルに DNAT ルールが書き込まれます。このルールは ufw が管理する INPUT ルールより先に評価されるため、ufw deny 8080 では遮断できません。その結果、アプリケーションは、慎重に設定したプロキシと並んでパブリックインターネット上に公開されます。127.0.0.1:8080:80 を使って公開ポートを loopback にバインドしてください。または ports: を完全に削除し、Docker ネットワーク経由でプロキシからコンテナへ接続させます。上記の Traefik の例では、この方法を使っています。仕組みと対処方法については、Docker の公開ポートが ufw を迂回する理由を参照してください。
VPS 以外のマシンからテストしてください。VPS 自身で実行したチェックは、常に成功するためです。
curl --max-time 5 http://your.server.address:8080Connection refused またはタイムアウトになれば、期待どおりの結果です。HTTP レスポンスが返る場合、そのアプリケーションにはプロキシを経由せずに到達できます。その場合、上記で行った設定はすべて意味を持ちません。
どのプロキシを選ぶべきか
主に静的サイトを運用し、アプリケーションが 1 つか 2 つある場合: Caddy。 自動 HTTPS により、繰り返し発生する作業の中でも最大の負担をなくせます。設定は 1 画面で読めるほど短く、静的サイトは同じサイトブロック内の root 行と file_server 行だけで構成できます。その代わり、問題が発生したときにそのまま使える解決策は少なくなります。
追加し続ける docker-compose の homelab: Traefik。 3 つ目のサービスを超えると、中央ファイルを編集するよりラベルを使うほうが手間が少なくなります。サービスを削除すれば、そのルートも削除されます。最初の設定には半日ほど見込んでください。entrypoint、router、service、middleware という新しい用語を覚える必要があるためです。ラベルのタイプミスは通常、起動失敗ではなく Traefik からの 404 として現れます。アプリケーションが壊れていると判断する前に、docker logs traefik を読んで parse error を確認してください。
既存の Nginx 設定を使う場合、または上記の一覧にある要件がある場合: Nginx。 レスポンスキャッシュとクライアント証明書には、すでに対応方法があります。ほぼすべてのサードパーティー製ガイドも Nginx を前提にしています。その代わり、証明書と websocket のサポートは自動的に利用できる機能ではなく、自分で設定する必要があります。
どれを選んでも、1 つのルールは変わりません。公開インターフェースで待ち受けるプロセスは必ず 1 つだけにします。それ以外は loopback またはプライベートな Docker ネットワークで待ち受けさせます。
FAQ
少数の Docker アプリを 1 台の VPS で運用する場合、どのリバースプロキシが最適ですか?
追加するサービスが 3 つか 4 つで、追加頻度も高くない場合は、Traefik が適しています。各アプリが独自のルーティングラベルを持つため、中央のファイルを編集する必要がありません。サービス構成が安定していて、HTTPS の管理を主な負担にしたくない場合は、Caddy のほうが習得する内容も、壊す可能性も少なくなります。すでに Nginx に慣れている場合や、レスポンスキャッシュやプレーン TCP リスナーなど、他の 2 つにない機能が必要な場合は Nginx を選びます。
Caddy では本当に証明書の設定が不要ですか?
通常の構成であれば、不要です。サイトアドレスとして公開ホスト名を指定するだけで設定は完了します。Caddy は ACME 経由で証明書を要求し、80 番ポートからのリダイレクトを提供し、有効期限の前に更新します。ただし、2 つの条件があります。HTTP-01 チャレンジのために 80 番ポートへインターネットから到達できる必要があります。また、証明書認証局がホスト名を名前解決して VPS に接続できるよう、ホスト名の DNS A または AAAA レコードがあらかじめ VPS を指している必要があります。
同じ VPS で Nginx と Traefik を実行できますか?
同じポートでは実行できません。後から起動したほうが bind に失敗し、nginx は bind() to 0.0.0.0:443 failed (98: Address already in use) と表示します。一方、Traefik は同様の bind エラーをログに記録して終了します。80 番ポートと 443 番ポートでは 1 つのプロキシだけを実行し、その他のサービスはその背後に配置してください。移行中であれば、ホスト名を 1 つずつ移行します。最後のサイトを移行するまで、前段のプロキシからループバックポート上の旧プロキシへ転送します。
Nginx の背後で WebSocket が 60 秒後に切断されるのはなぜですか?
proxy_read_timeout のデフォルト値は 60 秒です。アップグレード完了後のトンネルにも適用されるため、1 分間トラフィックがない接続はアプリではなくプロキシによって閉じられます。その location で proxy_read_timeout 3600s; を設定して値を増やすか、アプリケーションから 30 秒ごとに ping フレームを送信してください。Caddy と Traefik は、1 分間のタイマーでアイドル状態のアップグレード済み接続を閉じません。そのため、同じアプリでも Nginx の背後では不安定に見え、Caddy や Traefik の背後では安定して動作します。