GuiderCaddy, Nginx eller Traefik

Caddy, Nginx eller Traefik framför dina appar

[object Object]

Testad på Uppdaterad 1 januari 1970

Rekommenderad server för guiden

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Minne · 60 GB Lagring

Månadsvis, ingen bindningstid 7 dagars öppet köp

100 kr/mån

Beställ nu
På den här sidan

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ändaren caddy, 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.

CaddyNginxTraefik
Version2.11.71.26.3, certbot 4.0.03.7.14
Körs somegen användare, utan rootroot, med arbetsprocesser som www-dataegen användare, utan root
Installationen avbild på 67 MB16 paket, 46 MB, 16 sekunderen avbild på 195 MB
Rader konfiguration för testet246155
Minne i vila49 MB6 MB99 MB
Minne efter lasttesterna67 MB10 MB129 MB
Statisk fil, förfrågningar per sekund2 7856 194kan inte servera filer
Statisk fil, median / 99:e percentilen8,8 / 104 ms6,8 / 25 ms–
Förfrågan via proxyn, förfrågningar per sekund2 1372 7942 093
Förfrågan via proxyn, median / 99:e percentilen20 / 70 ms16 / 58 ms21 / 71 ms
Certifikatinbyggtcertbot, med en timer och en krok för omladdninginbyggt
Ändrad konfiguration utan omstartkräver ändringar i vår uppsättning (se nedan)systemctl reload nginxav sig själv, från filen
Förlorade förfrågningar vid omladdning under last0 till 9 av ungefär 26 0000 till 1 av ungefär 38 0000 av ungefär 30 000
Nya anslutningar nekade vid omstarti 0,7 sekunderen förfrågan misslyckadesi 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 Upgrade och Connection och HTTP/1.1 mot appen; Nginx skickar inte vidare dem av sig själv.
  • Utan proxy_set_header Host får appen frågor om 127.0.0.1:8156 i 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 med gzip_proxied any.
  • Utan blocken upstream och keepalive ö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, reuseport på bara en av dem och rubriken Alt-Svc, som talar om för webbläsare att den finns.
  • add_header lägger till en rubrik även när appen skickar sin egen, så Uptime Kumas svar hade X-Frame-Options två gånger tills proxy_hide_header tog bort appens. En location med en egen add_header tappar 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_proxies i Caddy, forwardedHeaders.trustedIPs i 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 --nginx gör det åt dig, men vi använde certonly fö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 nginx fö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.yml krä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örde podman 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.

Kör det på din egen server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

100 kr/mån

vCPU
2
Minne
8 GB
Lagring
60 GB
Trafik
10 TB
Performance
SSD · RAID 1
  • Full root-åtkomst
  • Nativt /64 IPv6
  • RAID-skyddad lagring
  • Malmö, Sverige
  • Månadsvis, ingen bindningstid
  • 7 dagars öppet köp
Alla guider