Det korta svaret
Välj Caddy om du vill ha HTTPS, HTTP/3, websockets och komprimering med minst konfiguration. Den behövde färst rader, och det är den alla våra appguider använder.
Välj Nginx om minne och statiska filer betyder mest, eller om du redan kan den: den använde en åttondel av Caddys minne och serverade en statisk fil mer än dubbelt så snabbt. Välj Traefik om dina containrar körs under en och samma användare eller i Docker och du vill att vägarna följer dem via deras etiketter. Den använde mest minne och kan inte själv servera filer.
Det här testade vi
Alla tre kördes på samma VC-P Alloy (2 vCPU, 8 GB) hos Melonslab med Debian 13, säkrad som i säkerhetsguiden och uppsatt som i guiden om Podman utan root. Alla tre vill ha portarna 80 och 443, så vi körde dem en i taget och stoppade de andra två.
- Caddy 2.11.7, från
docker.io/library/caddy:2, utan root som användarencaddy, precis som i Podman-guiden. - Nginx 1.26.3 från Debian, med certbot 4.0.0 från Debian för certifikaten.
- Traefik 3.7.14, från
docker.io/library/traefik:v3.7, utan root som en egen användare,traefik, uppsatt på samma sätt som Caddy.
Bakom var och en körde samma två appar utan root under en testanvändare: traefik/whoami 1.11.0, en liten webbapp som visar förfrågan den fick, och Uptime Kuma 2, vars gränssnitt går över en websocket. Varje app hade ett eget namn, app.example.com och status.example.com i exemplen, och en egen port på 127.0.0.1.
Varje proxy skulle hämta certifikat från Let's Encrypt för båda namnen, skicka HTTP vidare till HTTPS, tala HTTP/2 och HTTP/3, ge appen besökarens riktiga adress, släppa igenom websockets, lägga till fyra säkerhetsrubriker (Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options och Referrer-Policy) och komprimera svaren. Alla tre klarade allt, med ett undantag: Nginx komprimerade bara med gzip, eftersom Debian saknar en zstd-modul för den.
| Caddy | Nginx | Traefik | |
|---|---|---|---|
| Version | 2.11.7 | 1.26.3, certbot 4.0.0 | 3.7.14 |
| Körs som | egen användare, utan root | root, med arbetsprocesser som www-data | egen användare, utan root |
| Installation | en avbild på 67 MB | 16 paket, 46 MB, 16 sekunder | en avbild på 195 MB |
| Rader konfiguration för testet | 24 | 61 | 55 |
| Minne i vila | 49 MB | 6 MB | 99 MB |
| Minne efter lasttesterna | 67 MB | 10 MB | 129 MB |
| Statisk fil, förfrågningar per sekund | 2 785 | 6 194 | kan inte servera filer |
| Statisk fil, median / 99:e percentilen | 8,8 / 104 ms | 6,8 / 25 ms | – |
| Förfrågan via proxyn, förfrågningar per sekund | 2 137 | 2 794 | 2 093 |
| Förfrågan via proxyn, median / 99:e percentilen | 20 / 70 ms | 16 / 58 ms | 21 / 71 ms |
| Certifikat | inbyggt | certbot, med en timer och en krok för omladdning | inbyggt |
| Ändrad konfiguration utan omstart | kräver ändringar i vår uppsättning (se nedan) | systemctl reload nginx | av sig själv, från filen |
| Förlorade förfrågningar vid omladdning under last | 0 till 9 av ungefär 26 000 | 0 till 1 av ungefär 38 000 | 0 av ungefär 30 000 |
| Nya anslutningar nekade vid omstart | i 0,7 sekunder | en förfrågan misslyckades | i 1,9 sekunder |
Minnet är det proportionella minnet (PSS) för proxyns egna processer, 90 sekunder efter en omstart och en förfrågan till varje webbplats, utan apparna. Raderna är konfigurationsfilerna utan tomma rader och kommentarer.
Lasten kom från oha 1.16.0 på samma server, med 50 anslutningar i 20 sekunder över HTTPS och HTTP/1.1, med gzip. Den statiska filen var en HTML-sida på 10,6 kB som komprimerades vid varje förfrågan; förfrågan via proxyn var whoamis svar på 400 byte. Eftersom lastverktyget delade proxyns två vCPU jämför siffrorna de tre med varandra, inte med vad dina besökare kommer att märka. Över HTTP/2 serverade Caddy och Nginx den statiska filen lika snabbt som över HTTP/1.1, och alla tre var 5 till 20 % långsammare på förfrågan via proxyn.
Installation och konfiguration
Caddy behövde 24 rader för allt. Certifikat, omdirigering till HTTPS, HTTP/2, HTTP/3, websockets och besökarens adress är påslaget från början, så filen anger bara webbplatserna, komprimeringen och rubrikerna:
{
admin off
}
(common) {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
}
}
app.example.com {
import common
handle /static/* {
root * /srv
file_server
}
reverse_proxy 127.0.0.1:8156
}
status.example.com {
import common
reverse_proxy 127.0.0.1:8157
}
Filerna under /static/ kom från en katalog som monterades i containern med ytterligare en Volume=-rad i caddy.container.
Nginx installeras med apt install nginx certbot python3-certbot-nginx, och körs som root från en systemd-tjänst som Debian sätter upp, så portarna 80 och 443 kräver inget mer. Först lade vi ett serverblock för port 80 i /etc/nginx/sites-available/apps, länkade in det i sites-enabled, tog bort webbplatsen default och begärde ett certifikat per namn:
certbot certonly --nginx --cert-name app -d app.example.com
certbot certonly --nginx --cert-name status -d status.example.com
Sedan kom resten: 52 rader i webbplatsens fil och 9 i ett gemensamt utdrag, /etc/nginx/snippets/common.conf:
map $http_upgrade $connection_upgrade {
default upgrade;
'' "";
}
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript image/svg+xml;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
upstream kuma {
server 127.0.0.1:8157;
keepalive 16;
}
upstream whoami {
server 127.0.0.1:8156;
keepalive 16;
}
server {
listen 80;
listen [::]:80;
server_name app.example.com status.example.com;
return 301 https://$host$request_uri;
}
server {
server_name app.example.com;
include snippets/common.conf;
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
ssl_certificate /etc/letsencrypt/live/app/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app/privkey.pem;
location /static/ {
root /var/www;
}
location / {
proxy_pass http://whoami;
}
}
server {
server_name status.example.com;
include snippets/common.conf;
listen 443 quic;
listen [::]:443 quic;
ssl_certificate /etc/letsencrypt/live/status/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/status/privkey.pem;
location / {
proxy_pass http://kuma;
}
}
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400' always;
add_header Strict-Transport-Security "max-age=31536000" always;
add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
proxy_hide_header X-Frame-Options;
De flesta av raderna slår på sådant som Caddy och Traefik gör från början. Nginx dokumentation förklarar var och en:
- Websockets kräver rubrikerna
UpgradeochConnectionoch HTTP/1.1 mot appen; Nginx skickar inte vidare dem av sig själv. - Utan
proxy_set_header Hostfår appen frågor om127.0.0.1:8156i stället för sitt namn. - Nginx komprimerar bara HTML tills du lägger till
gzip_types, och inga av apparnas svar förrän medgzip_proxied any. - Utan blocken
upstreamochkeepaliveöppnar Nginx en ny anslutning till appen för varje förfrågan. I vår första körning utan dem klarade den 1 464 förfrågningar i sekunden via proxyn, långsammare än båda de andra. - HTTP/3 kräver egna rader med
listen ... quic,reuseportpå bara en av dem och rubrikenAlt-Svc, som talar om för webbläsare att den finns. add_headerlägger till en rubrik även när appen skickar sin egen, så Uptime Kumas svar hadeX-Frame-Optionstvå gånger tillsproxy_hide_headertog bort appens. Enlocationmed en egenadd_headertappar dessutom alla rubriker ovanför.
Traefik kördes från en Quadlet-fil som liknar Caddys, med Network=host, konfigurationskatalogen monterad skrivskyddad och en volym för certifikaten. Precis som Caddy får den portarna 80 och 443 som vanlig användare tack vare steg 2 i Podman-guiden. Konfigurationen har två delar: traefik.yml, som läses vid start, med portarna, Let's Encrypt och var vägarna finns (24 rader), och en fil med vägar i dynamic/, som läses varje gång den ändras (31 rader):
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
websecure:
address: ":443"
http3: {}
http:
tls:
certResolver: letsencrypt
middlewares:
- secure@file
- compress@file
certificatesResolvers:
letsencrypt:
acme:
storage: /acme/acme.json
httpChallenge:
entryPoint: web
providers:
file:
directory: /etc/traefik/dynamic
watch: true
http:
routers:
app:
rule: Host(`app.example.com`)
entryPoints: [websecure]
service: app
status:
rule: Host(`status.example.com`)
entryPoints: [websecure]
service: status
services:
app:
loadBalancer:
servers:
- url: http://127.0.0.1:8156
status:
loadBalancer:
servers:
- url: http://127.0.0.1:8157
middlewares:
secure:
headers:
stsSeconds: 31536000
contentTypeNosniff: true
customFrameOptionsValue: SAMEORIGIN
referrerPolicy: strict-origin-when-cross-origin
compress:
compress:
encodings: [zstd, gzip]
Websockets, besökarens adress och HTTP/2 krävde ingenting. Enligt Traefiks dokumentation krävs en e-postadress för Let's Encrypt; version 3.7.14 fick båda certifikaten utan någon. Komprimeringen hoppar över svar under 1 024 byte, så whoamis korta svar gick ut okomprimerat, medan Uptime Kumas skript kom tillbaka med zstd eller gzip.
Traefik kan inte servera filer från disken. För statiska filer kör du en liten webbserver, som Nginx eller Caddy, i en container bakom den. Därför saknar tabellen statiska siffror för Traefik.
Besökarens adress
Alla tre gav appen besökarens riktiga adress i X-Forwarded-For: whoami visade vår egen IPv4-adress när vi anropade den utifrån, och serverns IPv6-adress när vi anropade den från servern. Alla tre kastade också en rubrik X-Forwarded-For: 6.6.6.6 som vi själva skickade, så en besökare kan inte låtsas komma från någon annanstans:
- Caddy och Traefik gör det från början, och behåller bara en sådan rubrik från proxyer du anger (
trusted_proxiesi Caddy,forwardedHeaders.trustedIPsi Traefik). - Nginx gör det för att konfigurationen sätter
X-Forwarded-For $remote_addr. Många exempel använder i stället$proxy_add_x_forwarded_for, som lägger besökarens adress till det besökaren själv skickade.
Har du ett CDN framför måste var och en få veta dess adresser: samma inställningar i Caddy och Traefik, eller set_real_ip_from i Nginx. Det testade vi inte.
Eftersom apparna körs utan root når själva anslutningen dem från serverns egen IPv4-adress, som steg 7 i Podman-guiden förklarar. Det är den adressen en app ska lita på som proxy, vilken av de tre du än använder.
Certifikat och förnyelse
Caddy och Traefik hämtade sina certifikat själva inom några sekunder efter start och sparar dem i en egen volym. Caddy förnyar när ungefär en tredjedel av certifikatets livstid är kvar, eller tidigare om Let's Encrypt säger till via sin förnyelseinformation (ARI), och enligt dokumentationen provar den ZeroSSL om Let's Encrypt inte fungerar. Enligt Traefiks dokumentation förnyas certifikat på 90 dagar 30 dagar innan de går ut, med certificatesDuration för certifikat av andra längder.
Nginx förlitar sig på certbot. Debians paket installerar certbot.timer, som försöker två gånger om dagen och förnyar 30 dagar innan certifikatet går ut. certbot renew --dry-run lyckades för båda namnen, på 105 sekunder. Två saker får du sköta själv:
- Nginx använder ett förnyat certifikat först efter en omladdning. Certbots installationsläge
--nginxgör det åt dig, men vi användecertonlyför att skriva vår egen konfiguration, så vi lade till en krok:echo 'systemctl reload nginx' > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx - En ny webbplats betyder att du kör certbot igen innan dess HTTPS-block fungerar.
certbot --nginx utan certonly skriver själv in certifikatraderna i konfigurationen. nginx.org har också en egen ACME-modul, nginx-module-acme, i sina egna paket men inte i Debians; den testade vi inte.
Omladdningar och omstarter
Vi ändrade en rubrik medan 20 anslutningar fortsatte att hämta sidan via proxyn, och räknade misslyckade förfrågningar:
- Nginx:
systemctl reload nginxförlorade en förfrågan av ungefär 38 000 en gång, och ingen de andra tre gångerna. - Traefik: den märkte själv att filen med vägar ändrats, och ingenting gick förlorat på tre försök. Ändringar i
traefik.ymlkräver en omstart, som nekade nya anslutningar i 1,9 sekunder. - Caddy, som vår Podman-guide sätter upp den, har sitt administrationsgränssnitt avstängt, så ändringar kräver en omstart, som nekade nya anslutningar i 0,7 sekunder. För att ladda om utan omstart gav vi administrationsgränssnittet en socket inne i containern,
admin unix//run/caddy-admin.sock, och kördepodman exec caddy caddy reload --config /etc/caddy/Caddyfile --address unix//run/caddy-admin.sock. Det förlorade 5 till 9 förfrågningar av ungefär 26 000, på anslutningar som Caddy stängde under omladdningen.
En fälla med Caddy: vår Podman-guide monterar Caddyfile som en enskild fil. De flesta redigerare sparar genom att ersätta filen, och då ser containern fortfarande den gamla, så en omladdning ändrar ingenting. Att montera en katalog i stället, Volume=%h/caddy:/etc/caddy:ro, löste det. En omstart läser alltid den nya filen, och därför startar våra guider om Caddy.
Hur de hittar dina appar
Caddy och Nginx läser vanliga filer: varje ny app betyder ett nytt block och en omstart eller omladdning. Våra appguider lägger till ett Caddy-block var.
Traefik kan också läsa containrarnas etiketter. Vi lät den använda användaren traefiks Podman-socket i stället för Dockers, med useBindPortIP: true så att trafiken går till porten containern publicerar på 127.0.0.1, eftersom en container utan root har en egen adress som inte nås utifrån. En whoami-container med tre etiketter fick sin väg och sitt certifikat när den startade, och vägen försvann när den stoppades:
Label=traefik.enable=true
Label=traefik.http.routers.labelled.rule=Host(`app.example.com`)
Label=traefik.http.routers.labelled.entrypoints=websecure
Det ser bara containrarna hos användaren vars socket Traefik har, och den socketen ger Traefik full kontroll över den användarens containrar. Med våra guiders upplägg, en användare per app, passar etiketter inte, och Traefiks fil med vägar gör samma jobb som Caddys block. Etiketter passar där alla containrar körs under en användare eller i Docker, som i Coolify och Dokploy, som båda har Traefik framför.
Resurser och uppdateringar
I vila använde Nginx 6 MB i tre processer, Caddy 49 MB och Traefik 99 MB. Under last steg Caddy till 67 MB, Traefik till 129 MB och Nginx till 10 MB. På en Alloy spelar inget av det någon roll; på en server med 1 GB lämnar Nginx mest över till dina appar.
Caddy och Traefik uppdateras som alla våra containrar, via AutoUpdate=registry och Podmans dagliga timer. Nginx och certbot kommer från Debian, så de automatiska säkerhetsuppdateringarna från säkerhetsguiden täcker dem, och de stannar på versionerna i Debian 13: Nginx 1.26, utan zstd.
När något går fel loggar Caddy och Traefik till sin användares journal, och Nginx till /var/log/nginx/. nginx -t kontrollerar en konfiguration innan du laddar om, och caddy validate gör samma sak för Caddy.
Licens och kostnad
Alla tre är öppen källkod, enligt deras kodförråd på GitHub när vi kontrollerade den 14 oktober 2026:
- Caddy: Apache License 2.0.
- Traefik: MIT. Traefik Labs säljer också kommersiella produkter byggda på den, som Traefik Hub.
- Nginx: BSD-licensen med två klausuler. F5, som äger Nginx, säljer NGINX Plus, en kommersiell version med fler funktioner och support; Nginx i Debian är den med öppen källkod.
Det finns två förgreningar av Nginx: Angie, av tidigare Nginx-utvecklare, under en BSD-liknande licens och med en egen ACME-klient, och freenginx, som Maxim Dounin, länge utvecklare av Nginx, startade i februari 2024. Ingen av dem testade vi.
Ingen av de tre kostar något för det vi gjorde här.
Vilken ska du välja?
- Caddy, för de flesta servrar som kör våra guider. Konfigurationen är kortast, certifikaten sköter sig själva, och varje appguide lägger till sitt block i den. Den sätts upp i Kör containrar utan root med Podman, på en VC-P Alloy som den vi använde. Svagheterna: mer minne än Nginx, långsammast på statiska filer av de två som serverar dem här, och en omstart för varje ändring om du inte sätter upp omladdning som ovan.
- Nginx, när minnet är knappt, du serverar många statiska filer eller redan har konfigurationer för Nginx. Räkna med fler rader, certbot att hålla koll på och fällorna ovan. Den körs som root från Debian; vi testade den också på en Alloy.
- Traefik, när dina containrar körs under en användare eller i Docker och du vill att vägarna följer deras etiketter, eller när program du använder, som Coolify eller Dokploy, tar med den. Den använde mest minne och serverar inga filer själv, och med en användare per app, som i våra guider, blir den en fil med vägar som de andra. En Alloy passar för den.
Bara en av dem kan ha portarna 80 och 443, så välj en per server.