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(automatic certificate management environment)経由で Let's Encrypt に証明書を要求します。失敗した場合は ZeroSSL にフォールバックします。port 80 で HTTP から HTTPS へのリダイレクトを提供し、証明書を自動更新します。別のツールや、確認用の timer は必要ありません。証明書は caddy ユーザーのデータディレクトリに保存されます。パッケージでインストールした場合は /var/lib/caddy/.local/share/caddy です。このパスをバックアップ対象に追加するか、再構築後に新しい証明書を発行する運用にします。公開されていないホスト名の場合、tls internal により Caddy 自身のローカル認証局で署名します。これは Ubuntu で自己署名証明書を作成する場合と同じ結果になりますが、更新は自動で処理されます。
Nginx には ACME クライアントがありません。 Certbot が証明書を取得し、--nginx プラグインが server block を書き換えて、443 の listener とリダイレクトを追加します。更新は、パッケージがインストールする systemd timer から実行されます。そのため、確認すべき要素が 2 つあります。systemctl list-timers | grep certbot で timer が存在することを確認し、sudo certbot renew --dry-run で更新処理が引き続き動作することを確認します。手順の詳細は Ubuntu 24.04 で Nginx と Certbot を使用するにあります。列挙したくないサブドメインが多い場合は、同じツールで DNS-01 challenge によるワイルドカード証明書も取得できます。
Traefik には ACME クライアントが組み込まれています。 static configuration で certificate resolver を 1 つ設定すると、すべての router から利用できます。account key や証明書を含むすべての状態情報は、単一の acme.json ファイルに保存されます。Traefik は、そのファイルを所有者以外が読み取れる場合、使用を拒否します。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 600ディレクトリを mount し、Traefik 自身にファイルを作成させます。touch で先に作成すると、現在の umask が適用されます。多くのユーザーがこの権限エラーに遭遇するのはこのためです。
3 つすべてに共通する点があります。HTTP-01 challenge では、インターネットから port 80 に到達できる必要があります。認証局がその port に接続して確認するためです。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 ブロックをディレクティブごとに解説した手順で説明しています。
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 からのリダイレクト、更新は、2 つのサイトアドレスから自動的に設定されます。ファイル内で、それらを個別に指定する必要はありません。
Traefik
Traefik でルーティングするには、先に静的設定が必要です。2026 年 8 月時点で現行の image tag を使う Compose service は次のとおりです。
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 file の 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 で複数のアプリケーションをルーティングする手順にあります。
追加するアプリごとに必要な設定量
The data behind this chart
[
{
"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 の背後では安定して動作します。