SSD Nodes Learn 🎉 VPS $5.50/月起
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-13

Nginx、Caddy、Traefik 怎麼選?反向代理比較

同一台 VPS、1 個公開 IP,完整比較 Nginx、Caddy 與 Traefik 的 TLS 憑證、每個服務的設定成本、WebSocket 與 Docker 路由差異。

Nginx 與 Caddy、Traefik 的簡短結論

Nginx、Caddy 和 Traefik 都能執行反向代理的相同工作:監聽 443 埠、讀取每個請求中的主機名稱,並將請求轉送到 VPS 上正確的服務。三者都能讓 4 個自架應用程式共用 1 個公開 IP 位址,而且效能都足夠,實際上較慢的通常會是應用程式本身。差異在於各自如何取得 TLS(transport layer security)憑證,以及每增加 1 個應用程式需要多少設定。另一個差異會在之後出現,也就是你需要處理一般教學未涵蓋的情況時。

如果你希望由系統代為處理 HTTPS,且服務都是一般網頁應用程式,請選擇 Caddy。如果所有服務都在 Docker Compose 中執行,而且你每隔幾週就會新增 1 個服務,請選擇 Traefik。如果你已經在使用 Nginx,或需要回應快取、用戶端憑證、原始 TCP 轉送,或有一份不想重寫的大型既有設定,請選擇 Nginx。

每個服務如何取得 TLS 憑證?

這個面向對大多數人而言會直接決定選擇,因此請先從這裡開始。這三者最後都會取得由相同憑證授權單位簽發的相同憑證,但取得憑證所需的工作並不相同。

Caddy 會因為你指定了主機名稱而申請憑證。app.example.com 寫成網站位址後,Caddy 會透過 ACME(自動憑證管理環境)向 Let's Encrypt 申請憑證;若申請失敗,則改用 ZeroSSL,並在 port 80 上提供 HTTP 到 HTTPS 的重新導向,之後自動續期。不需要額外工具,也不需要設定計時器來檢查。套件安裝時,憑證會儲存在 caddy 使用者的資料目錄 /var/lib/caddy/.local/share/caddy,因此請將此路徑納入備份,或接受重建後重新簽發憑證。若主機名稱不是公開名稱,tls internal 會改用 Caddy 自己的本機憑證授權單位簽署。這與在 Ubuntu 上建立自簽憑證的結果相同,但續期會由 Caddy 自動處理。

Nginx 沒有 ACME 用戶端。 Certbot 會取得憑證,其 --nginx 外掛會改寫你的 server block,加入 443 listener 與重新導向設定。續期會由套件安裝的 systemd timer 執行,因此需要處理並驗證兩個元件:systemctl list-timers | grep certbot 可顯示 timer 是否存在,而 sudo certbot renew --dry-run 可確認續期流程仍可正常運作。詳細步驟請參閱在 Ubuntu 24.04 搭配 Nginx 使用 Certbot。如果子網域數量多到不想逐一列出,同一套工具也能透過DNS-01 challenge 取得 wildcard 憑證

Traefik 內建 ACME 用戶端。 你只需在靜態設定中設定一個 certificate resolver,之後每個 router 都能使用它。所有狀態資料,包括帳戶金鑰與憑證,都儲存在單一 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

掛載一個目錄,讓 Traefik 自行建立檔案。先使用 touch 建立檔案;檔案會繼承你的 umask,這也是大多數人遇到該行設定的方式。

三者有一點相同。HTTP-01 challenge 需要讓 port 80 可從網際網路連線,因為憑證授權單位會回連該埠。只開放 443 會導致簽發失敗,而且錯誤訊息看起來像是 DNS 問題。

三種設定中的相同雙應用程式路由工作

工作內容如下: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;
    }
}

接著建立連結、測試設定、重新載入,並加入憑證。

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.com

執行 nginx -t 顯示 syntax is oktest is successful,是每次重新載入前都應執行的檢查。第二個應用程式使用相同區塊,只需變更主機名稱與埠號。proxy_set_header 行不是裝飾:當 proxy_pass 指定位址時,nginx 預設會將 Host: 127.0.0.1:8080 傳送至上游,因此若應用程式根據 Host 標頭建立絕對 URL,就會把使用者導向 localhost。

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-ForX-Forwarded-ProtoX-Forwarded-Host,而且預設會忽略用戶端在這些標頭中傳送的內容,因此請求無法對後端服務謊稱來源位置。憑證、從 80 埠重新導向,以及續期,都會根據這兩個站點位址自動處理。檔案中不需要其他設定。

Traefik

Traefik 在進行任何路由之前,需要先有靜態設定。以下以 Compose 服務為例,使用截至 August 2026 的最新 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 是容器內的埠,不是 published port,因為 Traefik 會透過共用 Docker network 連線至容器。應用程式完全不需要 ports: 行,這才是實際效益:只有 Traefik 會發布連接埠。包含共用 network 與重新導向 middleware 的完整建置方式,請參閱 使用 Traefik 與 Docker Compose 路由多個應用程式

每新增一個應用程式需要多少設定?

ChartNon-blank config lines for the same two-app routing job
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 在處理第一個請求前,需要先設定 17 行靜態設定,之後每個應用程式再加入 5 個 label。

請比較取捨,不要只看誰勝出。Traefik 在加入第一個應用程式前的成本最高,但之後每個應用程式的新增成本最低;兩者的總成本約在第三個網站時相等。少於這個數量時,靜態設定只是額外負擔。超過這個數量後,label 會逐漸取得優勢,因為路由設定與所路由的服務放在一起。刪除服務時,對應的路由也會一併刪除。這正是集中式設定檔難以處理的問題:已在數個月前停止存在的應用程式,仍留下過時的 server block。

行數也會讓 Nginx 看起來比較簡單。每個區塊都需要建立 symlink、執行 nginx -t、重新載入設定並執行 certbot;Caddy 只需重新載入一次,Traefik 則完全不需要執行任何命令。三者重新載入時都不會中斷現有連線。差別在於凌晨 1 點時,必須記住多少個獨立步驟。

瞭解容器狀態的元件

Traefik 會監看 Docker socket,並在容器啟動與停止時,依據容器 labels 建立 routers。這裡的其他元件都不會這樣做。Nginx 和 Caddy 都需要在新容器出現時修改設定並重新載入,且需要可連線到的位址:可以是發佈在 loopback 上的連接埠,也可以是 proxy 已連接的共用 Docker network。

這項功能有其代價,必須明確說明。Traefik 會讀取 /var/run/docker.sock。任何能與該 socket 通訊的人,都可以啟動一個將主機檔案系統掛載其中的容器,而這等同於取得主機上的 root 權限。以唯讀方式掛載可降低風險,但無法消除風險。如果這符合你的威脅模型,請在中間加入 socket proxy,只公開 Traefik 所需的容器清單 endpoints。

Caddy 可透過社群 plugin 進行依 labels 的探索,但 Caddy plugins 會編譯進執行檔,因此你必須使用 xcaddy 建立自訂 binary 或自訂 image,之後也必須自行負責該建置版本及其更新。若只有 3 或 4 個服務,直接編輯 Caddyfile 的工作量較少。

WebSocket 與串流:哪些設定會失效,以及原因

需要額外設定的是 Nginx。WebSocket 連線一開始是攜帶 Upgrade: websocket 的 HTTP 請求;除非明確指定,nginx 不會將逐跳標頭轉送給上游。

# /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 秒,且套用於升級後的通道,因此 WebSocket 若 1 分鐘沒有流量,代理就會關閉連線。伺服器發送事件在設定該位置的 proxy_buffering off; 之前,會延遲到達或一次成批到達,因為 nginx 會將回應暫存在緩衝區中,導致頁面必須等待。

Caddy 不需要任何指令,就會執行升級並將連線切換為雙向通道。當回應為 text/event-stream 或長度未知時,Caddy 也會立即排放資料,因此串流無須額外調整即可運作。Traefik 會轉送升級,也不會緩衝回應,除非你自行加入 buffering middleware。如果服務包含聊天、Web 終端機、即時日誌或即時儀表板,這會實際影響你需要撰寫及除錯的設定量。

包含 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 context,而不是 server 內,因此請將它存放在 /etc/nginx/conf.d/ 下的獨立檔案中。只有在需要串流的位置才關閉 proxy_buffering,因為在一般回應中,緩衝能讓 nginx 及早釋放後端 worker。執行 Certbot 時,它會改寫此區塊,因此完成後請重新讀取檔案。

遇到特殊需求時會怎麼樣?

這正是 Nginx 顯示其額外設定價值的地方。

  • 用戶端憑證,也稱為 mTLS(雙向 TLS),表示用戶端也必須提供憑證。Nginx 需要在 server block 中設定 ssl_client_certificate /etc/ssl/ca.pem;ssl_verify_client on;。Caddy 則需要在 tls 內加入 client_auth block。Traefik 的 labels 完全無法表達這項設定:您必須在 file provider 中定義 TLS option,再使用 traefik.http.routers.app.tls.options=mtls@file 將 router 指向該設定。第一次需要這項功能時,所有設定都放在 labels 中的模式就必須例外處理。
  • 大型上傳檔案。 Nginx 預設將 request body 限制為 1 MB。較大的上傳會回傳 413 Request Entity Too Large,而 error log 會記錄 client intended to send too large body。請調高 client_max_body_size。Caddy 和 Traefik 預設不限制 body 大小,因此請求會到達您的應用程式,最後由應用程式本身的限制決定。
  • 回應快取。 Nginx 提供 proxy_cache,而且功能已相當成熟。Caddy 需要編譯包含 plugin 的版本。Traefik 的開源版本完全沒有 HTTP cache;以為每個代理都會快取的人,通常會對此感到意外。
  • 原始 TCP 或 UDP,例如資料庫埠或遊戲伺服器。Nginx 提供 stream module。Traefik 可在各自的 entrypoint 上使用 TCP 和 UDP router。Caddy 則需要另一個 plugin,因此也需要自訂建置版本。
  • 代理後方已經有 Web server。 如果服務是傳統 PHP 應用程式,Ubuntu 24.04 上的 LAMP stack 已包含 Apache;在前方再堆疊一層代理,會有兩個地方設定標頭,也有兩個地方可以重寫 URL。請決定由哪一層執行 TLS termination,然後讓另一層使用繫結至 loopback 的純 HTTP。

這項選擇後續會造成的防火牆陷阱

反向代理的目的,是只開放 80 和 443。Docker 會在不明顯的情況下破壞這項設計。使用 -p 8080:80 發布連接埠時,Docker 會將 DNAT 規則寫入 nat table。該規則會在 ufw 管理的 INPUT 規則之前進行評估,因此 ufw deny 8080 無法封鎖這項流量。如此一來,您的應用程式就會與您仔細設定的代理並列暴露在公用網際網路上。請使用 127.0.0.1:8080:80 將發布的連接埠繫結至 loopback,或完全省略 ports:,讓代理透過 Docker network 連線至容器;上方的 Traefik 範例採用的就是這種方式。相關機制與修正方式請參閱 Docker 發布的連接埠為何會繞過 ufw

請從 VPS 以外的機器進行測試,因為在該主機本身執行的檢查一定會成功:

curl --max-time 5 http://your.server.address:8080

您希望得到的結果是 Connection refused 或逾時。若收到 HTTP 回應,表示該應用程式不經過代理即可連線,而您在上方完成的所有設定都只是裝飾。

應該選哪個 proxy?

主要是靜態網站,另外搭配一兩個應用程式:Caddy。 自動 HTTPS 可免除最繁瑣的例行工作。設定檔短小,單一畫面即可讀完;靜態網站只需在同一個 site block 中加入一行 root 和一行 file_server。代價是遇到異常時,可直接複製貼上的解決方案較少。

持續新增服務的 docker-compose homelab:Traefik。 超過第 3 個服務後,使用 labels 比編輯集中式設定檔省事,而且刪除服務時,其路由也會一併移除。首次設定應預留一個下午,因為 entrypoints、routers、services 和 middlewares 都是新的術語。label 拼字錯誤通常會讓 Traefik 回傳 404,而不是導致服務啟動失敗;因此,先閱讀 docker logs traefik 所顯示的 parse error,再判斷應用程式是否故障。

已有 Nginx 設定,或有上述清單中的任何需求:Nginx。 Nginx 已提供 response caching 和 client certificates 的處理方式,而且幾乎所有第三方指南都以它為前提。代價是憑證和 websocket 支援需要自行設定,並非預設即可使用。

無論選擇哪一個,都應遵守同一項規則。對外部介面只能有一個 process 監聽,其他服務則監聽 loopback 或 private Docker network。

FAQ

單一 VPS 上執行幾個 Docker 應用程式時,哪個反向代理最好?

如果只有 3 或 4 個服務,而且偶爾才新增服務,Traefik 很適合,因為每個應用程式都能使用自己的路由 labels,不必修改中央設定檔。如果服務已經穩定,主要是不想再處理 HTTPS,Caddy 較容易學習,也較不容易設定錯誤。如果你已經熟悉 Nginx,或需要另外兩者沒有的功能,例如回應快取或純 TCP listener,則選擇 Nginx。

Caddy 真的不需要設定憑證嗎?

一般情況下,是的。將公開 hostname 設為 site address 就是完整設定:Caddy 會透過 ACME 申請憑證,從 port 80 提供重新導向,並在憑證到期前續期。仍有兩項條件必須成立。Port 80 必須能從網際網路連入,才能完成 HTTP-01 challenge;hostname 的 DNS A 或 AAAA record 也必須已指向 VPS,因為憑證授權單位會解析該名稱並連回該主機。

可以在同一台 VPS 上執行 Nginx 和 Traefik 嗎?

不能使用相同的 ports。後啟動的服務會無法 bind;nginx 會顯示 bind() to 0.0.0.0:443 failed (98: Address already in use),而 Traefik 會記錄類似的 bind 錯誤後結束。讓一個 proxy 使用 80 和 443,其他所有服務都放在它後方。如果正在進行遷移,請一次移動一個 hostname:在最後一個網站完成遷移前,讓前端 proxy 先透過 loopback port 將請求轉送到舊 proxy。

為什麼我的 websockets 經過 Nginx 後會在 60 秒斷線?

proxy_read_timeout 的預設值是 60 秒。升級完成後,它會套用到該 tunnel,因此連續 1 分鐘沒有 traffic 的 connection 會由 proxy 關閉,而不是由應用程式關閉。請在該 location 上調高此值,使用 proxy_read_timeout 3600s;;或讓應用程式每 30 秒傳送一次 ping frame。Caddy 和 Traefik 不會以 1 分鐘計時器關閉閒置的 upgraded connections,因此相同的應用程式在這兩者後方可能看起來穩定,但在 Nginx 後方卻不穩定。