SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-30

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

比較 Nginx、Caddy 與 Traefik 在 TLS 憑證、每個應用程式的設定成本、WebSocket,以及 Docker 路由上的差異,協助你為單一 VPS 與公開 IP 選擇代理。

Nginx 與 Caddy、Traefik 的簡短結論

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

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

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

這個面向通常會決定多數人的選擇,因此先從這裡開始。三者最後都會取得同一個憑證授權單位簽發的憑證,但取得憑證的操作方式不同。

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

Nginx 沒有 ACME client。 Certbot 會取得憑證,其 --nginx plugin 會改寫 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 client。 你可以在靜態設定中設定一個 certificate resolver,之後每個 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

掛載一個目錄,讓 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 ok 和 test is successful,這是每次重新載入前都應執行的檢查。第二個應用程式使用相同的區塊,只需修改主機名稱和連接埠。proxy_set_header 行並非裝飾:當 proxy_pass 指定位址時,nginx 預設會將 Host: 127.0.0.1:8080 傳送至上游,因此若應用程式根據 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 的重新導向與續期,均由這 2 個網站位址自動處理。檔案中不需要其他設定。

Traefik

Traefik 必須先有靜態設定,才能開始進行路由。以下是 Compose 服務設定,映像標籤截至 August 2026:

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 和重新導向 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 個標籤。

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

行數也讓 Nginx 看起來較有優勢。每個區塊都需要 symlink、nginx -t、reload 和一次 certbot 執行;Caddy 的修改只需要一次 reload,Traefik 的修改則完全不需要執行命令。三者 reload 時都不會中斷現有連線。差別在於,你凌晨 1 點需要記住多少個分開的步驟。

哪一個元件能辨識你的容器?

Traefik 會監看 Docker socket,並在容器啟動與停止時,根據容器標籤建立路由器。這裡的其他元件都不會自動執行這項工作。Nginx 和 Caddy 都需要在新容器出現時修改設定並重新載入,且必須取得可連線的位址:可以是發佈在 loopback 上的連接埠,也可以是代理已連接的共用 Docker network。

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

Caddy 可透過社群 plugin 使用以標籤為基礎的服務探索,但 Caddy plugin 會編譯進程式,因此你必須使用 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 秒,且適用於升級後的通道,因此沒有流量達 1 分鐘的 WebSocket 連線會被代理伺服器關閉。伺服器傳送事件在設定該位置的 proxy_buffering off; 前,會延遲送達或成批出現,因為 nginx 會將回應保留在緩衝區中,而頁面正在等待資料。

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

完整的 Nginx server block,包含 WebSocket 與 SSE
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 limit,因此請求會到達應用程式,再由應用程式自身的限制決定是否接受。
  • 回應快取。 Nginx 提供 proxy_cache,而且功能已相當成熟。Caddy 需要編譯包含 plugin 的版本。Traefik 的 open source build 完全沒有 HTTP cache;以為每個 proxy 都會快取的人,通常會對此感到意外。
  • 原始 TCP 或 UDP,例如資料庫連接埠或遊戲伺服器。Nginx 提供 stream module。Traefik 可在各自的 entrypoints 上使用 TCP 與 UDP routers。Caddy 則需要另一個 plugin,因此也需要自訂 build。
  • proxy 後方已經有 web server。 如果服務是傳統 PHP 應用程式,Ubuntu 24.04 上的 LAMP stack 已經包含 Apache;在前方再疊加一層 proxy,會有兩個位置設定 headers,也有兩個位置可以重寫 URL。請決定由哪一層執行 TLS termination,然後讓另一層使用綁定至 loopback 的純 HTTP。

這項選擇引發的防火牆陷阱

反向代理的目的,是只開放 80 和 443。Docker 會在不明顯的情況下繞過這項限制。使用 -p 8080:80 發布連接埠時,Docker 會在 nat table 中寫入 DNAT 規則。該規則會先於 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 或 timeout。若收到 HTTP 回應,表示該應用程式可以不經過代理直接連線。這代表你上方設定的所有內容都只是裝飾。

應該選哪個代理?

以靜態網站為主,另有一兩個應用程式:Caddy。 自動 HTTPS 能免除最主要的例行工作。設定檔短到能在單一畫面中讀完,靜態網站在同一個 site block 中只需一行 root 和一行 file_server。代價是遇到異常時,可直接複製貼上的解法較少。

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

已有 Nginx 設定,或有上述清單中的任何需求:Nginx。 它已經支援回應快取與用戶端憑證,而且幾乎所有第三方指南都以它為前提。代價是,憑證與 websocket 支援需要自行設定,並非安裝後即可使用。

無論選擇哪一個,都應遵守同一項規則。公開介面上只能有一個程序監聽,其他服務都應監聽 loopback 或私有 Docker network。

FAQ

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

如果只有三或四個服務,而且偶爾才新增服務,Traefik 很值得採用,因為每個應用程式都帶有自己的路由標籤,不需要編輯集中式設定檔。如果服務已經穩定,而你主要是不想再處理 HTTPS,Caddy 較容易學習,也較不容易設定錯誤。如果你已經熟悉 Nginx,或需要其他兩者沒有的功能,例如回應快取或純 TCP 監聽器,請選擇 Nginx。

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

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

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

不能使用相同的連接埠。後啟動的代理會無法繫結連接埠,nginx 會顯示 bind() to 0.0.0.0:443 failed (98: Address already in use),而 Traefik 會記錄類似的繫結錯誤後結束執行。讓其中一個代理使用 80 和 443,並將其他所有服務置於其後方。如果你正在進行遷移,請一次移動一個主機名稱:在最後一個網站完成移轉前,先讓前端代理將請求轉送到舊代理的 loopback 連接埠。

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

proxy_read_timeout 的預設值為 60 秒,而且升級完成後會套用至該通道,因此沒有流量持續一分鐘的連線會由代理關閉,而不是由應用程式關閉。請在該 location 上使用 proxy_read_timeout 3600s; 調高逾時時間,或讓應用程式每 30 秒傳送一次 ping frame。Caddy 和 Traefik 不會以一分鐘計時器關閉閒置的升級連線,因此相同的應用程式在這兩者後方可能看起來很穩定,在 Nginx 後方卻不穩定。