SSD Nodes Learn 🎉 VPS mula $5.50/buwan
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-08-13

Nginx vs Caddy vs Traefik: Alin ang Proxy?

Ihambing ang Nginx, Caddy at Traefik sa TLS certificates, config cost bawat app, websockets at Docker routing para sa isang VPS at public IP.

Nginx vs Caddy vs Traefik: maikling sagot

Pare-pareho ang pangunahing gawain ng Nginx, Caddy, at Traefik bilang reverse proxy: nakikinig sa port 443, binabasa ang hostname sa bawat request, at ipinapasa ito sa tamang service sa iyong VPS. Kahit alin sa tatlo ay maaaring maglagay ng apat na self-hosted app sa likod ng iisang public IP address, at sapat ang bilis ng lahat ng ito kaya ang mga app mo ang malamang na maging mabagal na bahagi. Ang pagkakaiba ay nasa paraan ng pagkuha ng bawat isa ng TLS (transport layer security) certificate at sa dami ng configuration na kailangan para sa bawat karagdagang app. Lumilitaw ang isa pang pagkakaiba kapag kailangan mo na ng feature na hindi saklaw ng mga karaniwang tutorial.

Piliin ang Caddy kung gusto mong awtomatikong pangasiwaan ang HTTPS at mga ordinaryong web app ang iyong mga service. Piliin ang Traefik kung lahat ay tumatakbo sa Docker Compose at nagdaragdag ka ng bagong service bawat ilang linggo. Piliin ang Nginx kung ginagamit mo na ito, o kung kailangan mo ng response caching, client certificates, raw TCP forwarding, o malaki nang existing config na ayaw mong isulat muli.

Paano kumukuha ng TLS certificate ang bawat isa?

Para sa karamihan ng tao, ito ang pangunahing pinagkaiba, kaya dito magsimula. Pare-pareho silang nagtatapos na may parehong certificate mula sa parehong authority. Magkaiba ang proseso para makuha iyon.

Humihingi si Caddy ng certificate dahil nagtakda ka ng hostname. Isulat ang app.example.com bilang site address, at hihingi si Caddy ng certificate gamit ang ACME (automatic certificate management environment) mula sa Let's Encrypt. Kung mabigo ito, gagamit siya ng ZeroSSL. Siya rin ang naghahatid ng HTTP-to-HTTPS redirect sa port 80 at awtomatikong nagre-renew. Walang hiwalay na tool o timer na kailangang i-check. Nasa data directory ng caddy user ang mga certificate—/var/lib/caddy/.local/share/caddy kapag package install—kaya idagdag ang path na iyon sa backups, o tanggapin ang panibagong issuance pagkatapos ng rebuild. Para sa hostname na hindi public, pipirma si tls internal gamit ang sariling local certificate authority ni Caddy. Pareho ang resulta nito sa paglikha ng self-signed certificate sa Ubuntu, ngunit si Caddy na ang bahala sa renewal.

Walang ACME client ang Nginx. Kinukuha ni Certbot ang certificate, at nire-rewrite ng --nginx plugin nito ang server block upang idagdag ang 443 listener at ang redirect. Tumatakbo ang renewal mula sa systemd timer na ini-install ng package, kaya may dalawang bahagi at dalawang bagay na kailangang i-verify: ipinapakita ng systemctl list-timers | grep certbot na umiiral ang timer, at pinatutunayan ng sudo certbot renew --dry-run na gumagana pa ang renewal path. Nasa Certbot sa Ubuntu 24.04 gamit ang Nginx ang sunod-sunod na hakbang. Saklaw din ng parehong tool ang wildcard certificate gamit ang DNS-01 challenge kapag mas marami kang subdomain kaysa sa nais mong isa-isahin.

May sarili nitong ACME client ang Traefik. Mag-configure ka ng isang certificate resolver sa static configuration, at maaari na itong gamitin ng bawat router. Nasa iisang acme.json file ang lahat ng state, kabilang ang account key at mga certificate. Tatanggi ang Traefik na gamitin ang file kung mababasa ito ng sinumang hindi may-ari nito. Ipapaalam din niya ito bago alisin ang 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

Mag-mount ng directory at hayaang si Traefik ang lumikha ng file. Likhain muna ito gamit ang touch upang mamana nito ang iyong umask. Ito ang karaniwang paraan kung paano natutugunan ng karamihan ang requirement na iyon.

May isang bagay na pare-pareho para sa tatlo. Kailangang reachable mula sa internet ang port 80 para sa HTTP-01 challenge dahil kumokonekta pabalik dito ang certificate authority. Kapag 443 lang ang binuksan mo, mabibigo ang issuance sa paraang parang DNS fault ang problema.

Parehong routing job para sa 2 app sa 3 config

Ang job: ang app.example.com ay mapupunta sa service sa 127.0.0.1:8080, at ang files.example.com ay mapupunta sa service sa 127.0.0.1:8081; parehong HTTPS. Narito ang buong configuration para sa bawat proxy, para makita ang pagkakaiba sa verbosity sa halip na ipahayag lamang ito.

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;
    }
}

Pagkatapos, i-link ito, i-test, i-reload, at idagdag ang certificate.

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

Ang pag-print ng syntax is ok at test is successful gamit ang nginx -t ang check na dapat patakbuhin bago ang bawat reload. Ang pangalawang app ay kaparehong block, ngunit binago ang hostname at port. Hindi dekorasyon ang mga linyang proxy_set_header: kapag ang proxy_pass ay tumutukoy sa isang address, ipinapadala ng nginx ang Host: 127.0.0.1:8080 upstream bilang default. Kaya ang app na bumubuo ng absolute URL mula sa Host header ay magpapadala sa mga user sa 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

Iyon na ang buong file. Awtomatikong sine-set ng reverse_proxy ang X-Forwarded-For, X-Forwarded-Proto, at X-Forwarded-Host mismo. Bilang default, binabalewala nito ang ipinadala ng client sa mga header na iyon, kaya hindi maaaring magsinungaling ang request sa iyong backend tungkol sa pinagmulan nito. Nakukuha na mula sa dalawang site address ang certificates, ang redirect mula port 80, at ang renewal. Walang ibang kailangang idagdag sa file para rito.

Traefik

Kailangan ng Traefik ng static configuration bago ito makapag-route ng kahit ano. Bilang Compose service, gamit ang image tag na kasalukuyan noong 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

Ang bawat application ay may sarili nitong routing sa labels, sa sarili nitong compose file:

    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"

Ang loadbalancer.server.port ay ang port sa loob ng container, hindi isang published port, dahil ina-access ng Traefik ang container sa shared Docker network. Hindi kailangan ng app ng linyang ports:, at ito ang tunay na pakinabang: Traefik lamang ang naka-publish. Makikita ang kumpletong setup, kasama ang shared network at redirect middleware, sa pag-route ng maraming app gamit ang Traefik at Docker Compose.

Magkano ang configuration para sa bawat karagdagang app?

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
  }
]

Batay ito sa mga block sa itaas. Ang Nginx server block ay may 11 non-blank na linya, at isinusulat mo itong muli para sa bawat hostname. Ang Caddy site block ay may 3 linya. Kailangan ng Traefik ng 17 linya ng static configuration bago ito makapagsilbi ng kahit isang request, at pagkatapos ay 5 label para sa bawat app.

Unawain ang trade-off, hindi lamang kung alin ang may pinakamababang bilang. Pinakamalaki ang initial cost ng Traefik bago ang unang app, pero pinakamaliit ang dagdag na cost para sa bawat kasunod na app. Nagkakapantay ang dalawang total sa humigit-kumulang ikatlong site. Kung mas kaunti rito ang site, overhead ang static configuration na hindi mo naman kailangan. Kung mas marami, nangunguna ang mga label at patuloy na lumalaki ang lamang dahil nasa tabi ng service ang routing configuration nito. Kapag dinelete mo ang service, kasama nitong nawawala ang route. Ito ang mahirap gawin sa central config file: maiiwan ang mga lumang server block para sa mga app na ilang buwan nang wala.

Pinapaboran din ng line count ang Nginx. Kailangan ng bawat block ng symlink, isang nginx -t, reload, at certbot run. Isang reload lang ang kailangan para sa Caddy edit, habang walang command na kailangan para sa Traefik edit. Lahat ng tatlo ay nagre-reload nang hindi tinatapos ang mga aktibong connection. Ang pagkakaiba ay kung ilang magkakahiwalay na hakbang ang kailangan mong tandaan nang alas-1 ng madaling-araw.

Alin ang nakakaalam tungkol sa iyong mga container?

Minomonitor ng Traefik ang Docker socket at bumubuo ito ng mga router mula sa container labels habang nagsisimula at humihinto ang mga container. Wala nang iba rito ang gumagawa nito. Kailangang i-edit ang configuration at i-reload ang Nginx at Caddy kapag may bagong container, at kailangan nila ng address na maaabot nila: maaaring port na naka-publish sa loopback, o shared Docker network na kinabitan din ng proxy.

May kapalit ang feature na ito, kaya dapat itong sabihin nang malinaw. Binabasa ng Traefik ang /var/run/docker.sock. Sinumang makakakonekta sa socket na ito ay maaaring magsimula ng container na may naka-mount na host filesystem sa loob nito, na katumbas ng root access sa host. Binabawasan ng read-only na pag-mount ang panganib pero hindi nito inaalis. Kung mahalaga ito sa iyong threat model, maglagay ng socket proxy sa pagitan na naglalantad lamang ng mga container list endpoint na kailangan ng Traefik.

Maaaring gumamit ang Caddy ng label-based discovery sa pamamagitan ng community plugin, pero naka-compile sa Caddy ang mga plugin. Kaya kailangan mong mag-build ng custom binary o custom image gamit ang xcaddy, at ikaw ang mamamahala sa build na iyon at sa mga update nito. Para sa tatlo o apat na serbisyo, mas kaunting trabaho ang pag-edit ng Caddyfile.

WebSocket at streaming: ano ang nasisira, at bakit

Si Nginx ang kailangang i-configure. Nagsisimula ang koneksyon ng WebSocket bilang HTTP request na may dalang Upgrade: websocket, at hindi ipinapasa ng nginx sa upstream ang hop-by-hop headers maliban kung tahasan mo itong iko-configure.

# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Pagkatapos, sa loob ng location block, dapat naroon ang tatlong linyang ito:

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

Kapag inalis ang mga ito, ipapakita ng browser console ang WebSocket connection to 'wss://app.example.com/ws' failed, habang ordinaryong GET ang makikita sa backend log. Kailangan ang map dahil ang hardcoded na Connection: upgrade ay ipapadala sa bawat request, kabilang ang mga ordinaryong request na dapat may close.

May dalawa pang default ng Nginx na maaaring magdulot ng problema. Ang proxy_read_timeout ay 60 segundo at nalalapat ito sa tunnel pagkatapos ng upgrade. Dahil dito, isinasara ng proxy ang WebSocket na walang traffic sa loob ng isang minuto. Nahuhuli o dumarating nang burst ang server-sent events hanggang itakda mo ang proxy_buffering off; sa location na iyon, dahil hinahawakan ng nginx ang response sa buffer habang hinihintay ito ng page.

Awtomatikong ginagawa ng Caddy ang upgrade at inililipat ang koneksyon sa two-way tunnel nang walang anumang directive. Nagfa-flush din ito agad kapag ang response ay text/event-stream o walang kilalang haba, kaya gumagana ang streaming nang walang karagdagang configuration. Ipinapasa ng Traefik ang mga upgrade at hindi nito bina-buffer ang mga response maliban kung ikaw mismo ang magdagdag ng buffering middleware. Kung may chat, web terminal, log tail, o live dashboard ang iyong mga serbisyo, mahalaga ang pagkakaibang ito sa dami ng configuration na kailangan mong isulat at i-debug.

Ang buong Nginx server block, kasama ang WebSocket at 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;
    }
}

Ang map ay dapat nasa http context, hindi sa loob ng server. Ilagay ito sa sarili nitong file sa ilalim ng /etc/nginx/conf.d/. I-off ang proxy_buffering sa mga location lamang na nagse-stream, dahil ang buffering ang nagbibigay-daan sa nginx na agad palayain ang backend worker para sa mga ordinaryong response. Binabago ng Certbot ang block na ito kapag pinatakbo mo ito, kaya basahin muli ang file pagkatapos.

Ano ang nangyayari kapag may kailangan kang hindi pangkaraniwan?

Dito nagiging kapaki-pakinabang ang dagdag na configuration lines ng Nginx.

  • Client certificates, na tinatawag ding mTLS (mutual TLS), kung saan kailangan ding magpakita ng certificate ang client. Kailangan ng Nginx ang ssl_client_certificate /etc/ssl/ca.pem; at ssl_verify_client on; sa server block. Kailangan naman ng Caddy ng client_auth block sa loob ng tls. Hindi ito kayang i-express ng Traefik labels: mag-define ka ng TLS option sa isang file provider at ituro rito ang router gamit ang traefik.http.routers.app.tls.options=mtls@file. Nagkakaroon ng exception ang modelong lahat ay nasa labels sa unang pagkakataong kailangan mo ito.
  • Malalaking upload. Nililimitahan ng Nginx sa 1 MB ang request body bilang default. Ang mas malaking upload ay nagbabalik ng 413 Request Entity Too Large, at sinasabi ng error log ang client intended to send too large body. Itaas ang client_max_body_size. Walang body limit ang Caddy at Traefik bilang default, kaya nakararating ang request sa app at ang sariling limit ng app ang nagpapasya.
  • Response caching. May proxy_cache ang Nginx, at mature na ito. Kailangan ng Caddy ng plugin na naka-compile sa build. Walang HTTP cache ang open source build ng Traefik, kaya nagugulat ang mga taong ipinapalagay na lahat ng proxy ay nagca-cache.
  • Raw TCP o UDP, para sa database port o game server. May stream module ang Nginx. May sariling TCP at UDP routers ang Traefik sa kani-kanilang entrypoints. Kailangan ng Caddy ng isa pang plugin, kaya kailangan na naman ng custom build.
  • May web server na nasa likod na ng proxy. Kung classic PHP application ang service, kasama na ng LAMP stack sa Ubuntu 24.04 ang Apache, at kapag naglagay ka ng proxy sa harap nito, magkakaroon ka ng dalawang lugar na nagse-set ng headers at dalawang lugar na maaaring mag-rewrite ng URL. Magpasya kung alin ang magte-terminate ng TLS, pagkatapos ay panatilihin ang isa sa plain HTTP na naka-bind sa loopback.

Ang firewall trap na kasunod ng pagpipiliang ito

Ang layunin ng reverse proxy ay 80 at 443 lamang ang bukas. Tahimik itong ina-override ng Docker. Kapag nag-publish ka ng port gamit ang -p 8080:80, nagsusulat ito ng DNAT rule sa nat table. Sinusuri ang rule na iyon bago ang INPUT rules na mina-manage ng ufw. Kaya hindi ito bina-block ng ufw deny 8080, at napupunta ang app mo sa public internet katabi ng proxy na maingat mong na-configure. I-bind ang mga published port sa loopback gamit ang 127.0.0.1:8080:80, o tuluyang alisin ang ports: at hayaan ang proxy na maabot ang container sa pamamagitan ng Docker network. Ito ang ginagawa ng halimbawa ng Traefik sa itaas. Nasa kung bakit nalalampasan ng Docker published ports ang ufw ang mekanismo at solusyon.

Subukan ito mula sa machine na hindi ang VPS, dahil palaging magtatagumpay ang check na pinapatakbo mismo sa server:

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

Ang Connection refused o timeout ang resultang gusto mo. Kapag may HTTP response, naaabot ang app na iyon nang hindi dumadaan sa proxy mo. Kung gayon, dekorasyon lamang ang lahat ng na-configure mo sa itaas.

Aling proxy ang dapat piliin?

Pangunahing static sites, kasama ang isa o dalawang app: Caddy. Awtomatikong inaalis ng HTTPS ang pinakamalaking paulit-ulit na gawain. Sapat na maikli ang configuration para mabasa sa isang screen, at ang isang static site ay isang root line at isang file_server line sa loob ng parehong site block. Ang kapalit nito ay mas kaunting mapagkukunan ng copy-paste na sagot kapag may hindi pangkaraniwang problema.

Docker Compose homelab na patuloy mong dinaragdagan: Traefik. Kapag lampas na sa ikatlong service, mas kaunting trabaho ang labels kaysa sa pag-edit ng central file, at kasama ng na-delete na service ang route nito. Maglaan ng isang hapon para sa unang setup, dahil bago sa iyo ang vocabulary na entrypoints, routers, services, at middlewares. Karaniwang lumalabas ang typo sa label bilang 404 mula sa Traefik sa halip na bilang failure sa pagsisimula, kaya basahin ang docker logs traefik para sa parse error bago ipalagay na may sira ang app.

May existing na Nginx config, o anumang requirement mula sa listahan sa itaas: Nginx. Mayroon na itong solusyon para sa response caching at client certificates, at ipinapalagay ito ng halos lahat ng third-party guide. Ang kapalit nito ay kailangan mong i-configure ang certificates at websocket support sa halip na awtomatikong makuha ang mga ito.

Iisa ang sinusunod na rule kahit alin ang piliin mo. Eksaktong isang process ang nakikinig sa public interface, at lahat ng iba pa ay nakikinig sa loopback o sa private Docker network.

FAQ

Aling reverse proxy ang pinakamainam para sa ilang Docker app sa isang VPS?

Para sa tatlo o apat na service na paminsan-minsan mong dinaragdagan, sulit ang Traefik dahil may sarili nang routing label ang bawat app at hindi kailangang mag-edit ng central file. Kung stable ang mga service at ang pangunahing gusto mo ay hindi na problemahin ang HTTPS, mas kaunti ang kailangang aralin at mas kaunti ang maaaring masira sa Caddy. Piliin ang Nginx kung pamilyar ka na rito o kung kailangan mo ng feature na wala sa dalawa, gaya ng response caching o plain TCP listener.

Kailangan ba talaga ng Caddy ng configuration para sa certificate?

Sa karaniwang setup, hindi. Ang pagtukoy sa public hostname bilang site address ang buong configuration: hinihingi ng Caddy ang certificate gamit ang ACME, sine-serve ang redirect mula sa port 80, at nire-renew ito bago mag-expire. May dalawang requirement pa rin. Dapat reachable mula sa internet ang port 80 para sa HTTP-01 challenge, at dapat nakaturo na sa VPS ang DNS A o AAAA record ng hostname dahil nireresolba ng certificate authority ang pangalan at kumokonekta pabalik dito.

Maaari ko bang patakbuhin ang Nginx at Traefik sa iisang VPS?

Hindi sa parehong port. Magfa-fail ang mauunang magsimula sa pangalawang proxy kapag nag-bind ito sa port, at magpapakita ang nginx ng bind() to 0.0.0.0:443 failed (98: Address already in use) habang magla-log naman ang Traefik ng katulad na bind error at lalabas. Magpatakbo ng isang proxy sa ports 80 at 443, at ilagay rito ang lahat ng iba pang service. Kung nagmi-migrate ka, ilipat ang mga hostname nang paisa-isa: ipasa muna ng front proxy ang traffic sa lumang proxy gamit ang loopback port hanggang mailipat ang huling site.

Bakit nawawala ang koneksyon ng websockets ko pagkalipas ng 60 segundo sa likod ng Nginx?

Ang proxy_read_timeout ay may default na 60 segundo at nalalapat ito sa tunnel kapag kumpleto na ang upgrade. Kaya isinasara ng proxy ang koneksyong walang traffic sa loob ng isang minuto, hindi ng app mo. Taasan ito sa location na iyon gamit ang proxy_read_timeout 3600s;, o magpadala ang application ng ping frame kada 30 segundo. Hindi isinasara ng Caddy at Traefik ang mga idle na upgraded connection gamit ang isang minutong timer. Dahil dito, maaaring stable ang parehong app sa likod ng mga ito ngunit unstable sa likod ng Nginx.