Traefik vs Caddy vs Nginx: Docker reverse proxy vergeleken
Drie werkende Docker Compose-stacks voor Traefik, Caddy en Nginx als reverse proxy op een VPS. Dezelfde backend, echte benchmarks en een besliskader om de juiste te kiezen.
Je hebt Docker-containers draaien op een VPS. Je hebt HTTPS nodig, routing op domeinnaam en één enkel ingangspunt. Traefik, Caddy en Nginx lossen dit probleem allemaal op. Ze lossen het anders op.
Dit artikel geeft je drie werkende Docker Compose-stacks die dezelfde backend deployen achter elke proxy. Kopieer degene die bij jouw situatie past. De vergelijkingstabel en het besliskader aan het eind helpen je kiezen.
Alle voorbeelden gebruiken een apart proxy-netwerk, HTTP-naar-HTTPS-redirect en productiewaardige standaardinstellingen. De stacks zijn gericht op Ubuntu 24.04 op een Virtua Cloud VPS.
Wat doet een reverse proxy voor Docker-containers op een VPS?
Een reverse proxy zit tussen het internet en je Docker-containers. Het termineert TLS (HTTPS), routeert verzoeken naar de juiste container op basis van de hostnaam en stelt één poortpaar (80/443) beschikbaar in plaats van één poort per service. Je containers hoeven zich nooit met certificaten bezig te houden en binden niet rechtstreeks aan publieke poorten.
Zonder reverse proxy zou elke container zijn eigen publieke poort nodig hebben. Bezoekers zouden example.com:3000 voor de ene service en example.com:8080 voor de andere gebruiken. Een reverse proxy maakt het mogelijk om app.example.com en api.example.com op standaardpoorten te gebruiken.
Alle drie de stacks hieronder gaan ervan uit dat:
- Docker en Docker Compose zijn geïnstalleerd Docker in productie op een VPS: wat er misgaat en hoe je het oplost
- Een DNS A-record naar het VPS-IP wijst
- Poorten 80 en 443 open zijn in je firewall Docker omzeilt UFW: 4 geteste oplossingen voor je VPS
- Je SSH-toegang hebt als niet-root gebruiker met sudo
Elk voorbeeld deployt dezelfde backend: de traefik/whoami-image, die HTTP-headers en containerinfo teruggeeft. Vervang deze later door je echte applicatie.
Hoe stel je Traefik in als Docker reverse proxy met automatisch HTTPS?
Traefik ontdekt containers automatisch door Docker-labels te lezen. Je voegt routingregels toe als labels op elke service. Wanneer een container start, detecteert Traefik hem, vraagt een Let's Encrypt-certificaat aan en begint verkeer te routeren. Geen configuratie-herlaadactie nodig.
Maak de projectdirectory aan:
mkdir -p ~/traefik-proxy && cd ~/traefik-proxy
Maak het Docker-netwerk aan dat alle geproxyde services delen:
docker network create proxy
Maak docker-compose.yml aan:
services:
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=WARN"
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./acme.json:/acme.json
networks:
- proxy
security_opt:
- no-new-privileges:true
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.rule=Host(`app.example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
networks:
- proxy
networks:
proxy:
external: true
Maak voor het starten het certificaatopslagbestand aan met beperkte rechten:
touch acme.json && chmod 600 acme.json
Traefik weigert te starten als acme.json te open permissies heeft. 600 zorgt ervoor dat alleen de eigenaar de opgeslagen privésleutels kan lezen.
Start de stack:
docker compose up -d
Controleer of beide containers draaien:
docker compose ps
Zowel traefik als whoami moeten de status Up tonen. Test nu vanaf je lokale machine (niet de server):
curl https://app.example.com
Het antwoord bevat de whoami-output met de request-headers. De X-Forwarded-For-header in het antwoord geeft aan dat Traefik het verkeer proxyt en TLS termineert.
Wat de labels doen:
traefik.enable=trueactiveert deze container (aangezienexposedbydefault=falseis ingesteld)traefik.http.routers.whoami.rule=Host(...)matcht verzoeken op hostnaamtraefik.http.routers.whoami.tls.certresolver=letsencryptinstrueert Traefik om een certificaat voor dit domein te verkrijgen
Om een andere service toe te voegen, voeg deze toe aan een willekeurig Compose-bestand op hetzelfde proxy-netwerk met de juiste labels. Traefik pikt het automatisch op.
Is het veilig om het Docker-socket te mounten in Traefik?
Het mounten van /var/run/docker.sock geeft Traefik volledige toegang tot de Docker-API. Als een aanvaller Traefik compromitteert, kan die containers aanmaken, omgevingsvariabelen lezen (inclusief secrets) en naar root escaleren op de host. De :ro-vlag voorkomt alleen schrijfacties op bestandssysteemniveau. Het beperkt geen Docker-API-aanroepen.
Gebruik voor productie een Docker-socket-proxy. Deze zit tussen Traefik en de Docker-daemon en filtert API-aanroepen om alleen leesoperaties op containermetadata toe te staan.
Voeg dit toe aan je docker-compose.yml:
services:
socket-proxy:
image: tecnativa/docker-socket-proxy:0.4
container_name: socket-proxy
restart: unless-stopped
environment:
CONTAINERS: 1
NETWORKS: 1
SERVICES: 0
TASKS: 0
POST: 0
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket-proxy
security_opt:
- no-new-privileges:true
traefik:
image: traefik:v3.6
container_name: traefik
restart: unless-stopped
depends_on:
- socket-proxy
command:
- "--providers.docker.endpoint=tcp://socket-proxy:2375"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.network=proxy"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--certificatesresolvers.letsencrypt.acme.email=you@example.com"
- "--certificatesresolvers.letsencrypt.acme.storage=/acme.json"
- "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
- "--log.level=WARN"
ports:
- "80:80"
- "443:443"
volumes:
- ./acme.json:/acme.json
networks:
- proxy
- socket-proxy
security_opt:
- no-new-privileges:true
networks:
proxy:
external: true
socket-proxy:
driver: bridge
internal: true
Traefik mount het Docker-socket niet meer rechtstreeks. Het socket-proxy-netwerk is internal: true, wat betekent dat het geen uitgaand internettoegang heeft. De socket-proxy staat alleen GET-verzoeken naar de containers- en networks-endpoints toe.
Hoe stel je Caddy in als Docker reverse proxy met automatisch HTTPS?
Caddy handelt HTTPS automatisch af zonder enige configuratie behalve de domeinnaam. Wijs een domein naar je server, zet het in het Caddyfile en Caddy verkrijgt en vernieuwt certificaten van Let's Encrypt. Geen resolver-configuratie, geen ACME-instellingen. Het is het kortste pad naar HTTPS voor een Docker reverse proxy.
Maak de projectdirectory aan:
mkdir -p ~/caddy-proxy && cd ~/caddy-proxy
Maak het gedeelde proxy-netwerk aan (sla over als je het al voor Traefik hebt aangemaakt):
docker network create proxy
Maak het Caddyfile aan:
app.example.com {
reverse_proxy whoami:80
encode gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Dat is de volledige proxyconfiguratie. Caddy leest de domeinnaam, vraagt een certificaat aan en proxyt naar de whoami-container op poort 80. Geen certificaatresolver, geen ACME-e-mail (Caddy gebruikt de standaard van je machine, of je kunt het globaal instellen), geen opslagpaden om te beheren.
Maak docker-compose.yml aan:
services:
caddy:
image: caddy:2.11
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- proxy
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
networks:
proxy:
external: true
volumes:
caddy_data:
caddy_config:
De poort 443:443/udp schakelt HTTP/3 (QUIC) in, dat Caddy standaard ondersteunt. cap_drop: ALL met cap_add: NET_BIND_SERVICE verwijdert alle Linux-capabilities behalve degene die nodig is om te binden aan poorten onder 1024.
Start de stack:
docker compose up -d
Controleer de containerstatus:
docker compose ps
Beide containers moeten Up tonen. Test vanaf je lokale machine met uitgebreide output:
curl -v https://app.example.com
Zoek naar HTTP/2 200 in de output. Je zou ook de beveiligingsheaders uit het Caddyfile moeten zien (Strict-Transport-Security, X-Content-Type-Options, enz.).
Om een andere service toe te voegen, voeg een nieuw blok toe in het Caddyfile met het domein en de reverse_proxy-directive, en herlaad:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
Geen container-herstart nodig. Caddy heeft het Docker-socket niet nodig. Het ontdekt containers niet automatisch. Je beheert routing in het Caddyfile.
Hoe stel je Nginx in als Docker reverse proxy met Let's Encrypt?
Nginx geeft je volledige controle over elke proxy-directive, header, buffergrootte en cacheregel. De afweging is handmatige configuratie. Nginx verkrijgt geen TLS-certificaten zelfstandig. Je combineert het met Certbot, die ACME-challenges en certificaatvernieuwing afhandelt.
Maak de projectdirectory aan:
mkdir -p ~/nginx-proxy && cd ~/nginx-proxy
Maak het gedeelde proxy-netwerk aan:
docker network create proxy
Maak de Nginx-configuratie aan in nginx/conf.d/app.conf:
mkdir -p nginx/conf.d
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;
server_tokens off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
proxy_pass http://whoami:80;
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;
}
}
server_tokens off; verbergt het Nginx-versienummer in responsheaders. Versie-informatie helpt aanvallers bij het richten op bekende kwetsbaarheden.
Maak docker-compose.yml aan:
services:
nginx:
image: nginx:1.28
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- certbot_webroot:/var/www/certbot:ro
- certbot_certs:/etc/letsencrypt:ro
networks:
- proxy
depends_on:
- whoami
certbot:
image: certbot/certbot
container_name: certbot
restart: unless-stopped
volumes:
- certbot_webroot:/var/www/certbot
- certbot_certs:/etc/letsencrypt
entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- proxy
networks:
proxy:
external: true
volumes:
certbot_webroot:
certbot_certs:
Nginx vereist dat de certificaatbestanden bestaan voordat het start. De configuratie hierboven verwijst naar /etc/letsencrypt/live/app.example.com/fullchain.pem, dat nog niet bestaat. Vervang voor het eerste certificaat app.conf tijdelijk door een HTTP-only versie:
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
Start Nginx en de backend:
docker compose up -d nginx whoami
Vraag het eerste certificaat aan:
docker compose run --rm certbot certonly \
--webroot \
--webroot-path=/var/www/certbot \
-d app.example.com \
--email you@example.com \
--agree-tos \
--no-eff-email
Zodra het certificaat is verkregen, herstel het volledige app.conf (de versie met het SSL-serverblok hierboven), en start de volledige stack:
docker compose up -d
Controleer of alle containers draaien:
docker compose ps
Test vanaf je lokale machine:
curl -v https://app.example.com
De server:-responsheader zou nginx moeten tonen zonder versienummer, wat bevestigt dat server_tokens off actief is.
Om een andere service toe te voegen, maak een nieuw .conf-bestand aan in nginx/conf.d/ en herlaad:
docker compose exec nginx nginx -s reload
Voor certificaatvernieuwing voert de Certbot-container elke 12 uur certbot renew uit. Na vernieuwing herlaad je Nginx om de nieuwe certificaten te gebruiken. Automatiseer dit met een cronjob of een script dat de wijzigingstijden van certificaten controleert. Zie Nginx configureren als reverse proxy voor een diepere blik op Nginx reverse proxy-configuratie.
Hoe verhouden Traefik, Caddy en Nginx zich voor Docker reverse proxying?
Traefik wint op auto-discovery. Caddy wint op eenvoud. Nginx wint op controle. De tabel hieronder zet de afwegingen uiteen die ertoe doen bij het draaien van Docker-containers op een VPS.
| Eigenschap | Traefik v3 | Caddy 2.11 | Nginx 1.28 |
|---|---|---|---|
| Auto-discovery | Ja (Docker-labels) | Nee (handmatig Caddyfile) | Nee (handmatige conf-bestanden) |
| TLS-automatisering | Ingebouwd ACME | Ingebouwd ACME | Vereist Certbot-sidecar |
| Configuratiemethode | Docker-labels + statisch YAML/CLI | Caddyfile of JSON-API | nginx.conf-bestanden |
| Config herladen | Automatisch bij container-events | caddy reload (zero downtime) |
nginx -s reload (zero downtime) |
| Docker-socket vereist | Ja (of socket-proxy) | Nee | Nee |
| HTTP/3 (QUIC) | Experimenteel | Ja (standaard) | Via module van derden |
| Middleware/plugins | Ingebouwd (rate limit, auth, headers) | Ingebouwd + Go-plugins | Via config-directives |
| Community/docs | Groot, actief, goede docs | Kleiner, uitstekende docs | Grootst, uitgebreide docs |
| Leercurve | Gemiddeld (labels + statische config) | Laag (Caddyfile is intuïtief) | Hoog (veel directives) |
Welke reverse proxy gebruikt het minste geheugen?
Geheugengebruik in rust telt op een VPS waar elke megabyte kostbaar is. Deze cijfers komen van docker stats --no-stream op een Virtua Cloud VPS met 4 vCPU / 8 GB RAM met Ubuntu 24.04. Elke proxy draaide in rust zonder verkeer vóór de meting.
| Proxy | RAM in rust | Image-grootte |
|---|---|---|
| Traefik v3.6 | ~17 MB | ~242 MB |
| Caddy 2.11 | ~14 MB | ~88 MB |
| Nginx 1.28 | ~5 MB | ~240 MB |
| Nginx + Certbot | ~5 MB + ~25 MB | ~240 MB + ~298 MB |
Nginx gebruikt veruit het minste geheugen. Caddy zit in het midden. Het hogere geheugengebruik van Traefik komt doordat het de Docker-providerstaat en routingtabel in het geheugen bijhoudt. Alle drie gebruiken de standaard (Debian/Alpine-gebaseerde) images. Alpine-varianten zouden de image-groottes verkleinen, maar mogelijk ten koste van compatibiliteit met bepaalde extensies.
Onder lichte belasting (100 gelijktijdige verzoeken via wrk) verwerken alle drie het verkeer zonder noemenswaardige CPU- of geheugentoename op deze VPS-grootte. De verschillen doen er alleen toe op grote schaal of bij de kleinste VPS-plannen.
Hoe kies je de juiste reverse proxy voor je Docker-setup?
De juiste keuze hangt af van hoeveel services je draait, hoe vaak ze veranderen en wat je al kent.
Kies Traefik wanneer:
- Je veel containers draait die vaak veranderen (wekelijks services toevoegen/verwijderen)
- Je zero-touch routing wilt: deploy een container met labels en hij is live
- Je Docker Swarm gebruikt of service discovery over meerdere nodes nodig hebt
- Je de Docker-socket-blootstelling accepteert (met een socket-proxy voor productie)
Kies Caddy wanneer:
- Je een paar services draait die zelden veranderen
- Je het eenvoudigste pad naar automatisch HTTPS wilt
- Je het Docker-socket niet wilt mounten
- Je een kleine image-grootte en laag geheugengebruik waardeert
- Je HTTP/3-ondersteuning wilt zonder extra configuratie
Kies Nginx wanneer:
- Je Nginx-configuratie al kent
- Je fijnmazige controle nodig hebt over proxygedrag (buffers, caching, aangepaste headers per location)
- Je het laagst mogelijke geheugengebruik wilt
- Je infrastructuurteam bestaande Nginx-tooling en monitoring heeft
- Het apart beheren van Certbot je niet stoort
Beslisboom:
- Draai je meer dan 5 Docker-services die regelmatig veranderen? Ja -> Traefik
- Heb je fijne proxy-tuning nodig of gebruik je al Nginx? Ja -> Nginx
- Wil je de minste bewegende onderdelen en de snelste setup? Ja -> Caddy
Voor de meeste indie hackers die een of twee zijprojecten deployen, is Caddy het beste startpunt. Voor DevOps-teams die een vloot containers beheren, verdient Traefiks auto-discovery zichzelf terug. Voor teams die Nginx al elders gebruiken, houdt bij Nginx blijven je stack consistent Docker-netwerken op een VPS: bridge, host en macvlan uitgelegd.
Beveiligingshardening voor alle drie de proxies
Welke proxy je ook kiest, pas deze basis beveiligingspraktijken toe.
Beveiligingsheaders. Alle drie de voorbeelden hierboven bevatten HSTS, X-Content-Type-Options, X-Frame-Options en Referrer-Policy. Voor Traefik voeg je ze toe als middleware-labels:
labels:
- "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000"
- "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true"
- "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true"
- "traefik.http.middlewares.security-headers.headers.frameDeny=true"
- "traefik.http.routers.whoami.middlewares=security-headers"
Rate limiting. Traefik heeft ingebouwde rate limiting-middleware. Caddy heeft een rate_limit-directive beschikbaar als plugin. Nginx gebruikt limit_req_zone in de configuratie. Rate limiting beschermt je backend tegen brute-force-aanvallen en misbruik.
Docker-netwerkisolatie. Elk voorbeeld gebruikt een extern proxy-netwerk. Backend-services mogen niet op het standaard bridge-netwerk staan. Alleen containers die geproxyd moeten worden, joinen het proxy-netwerk. Database-containers en interne services blijven op aparte, interne netwerken Docker Security Hardening: Rootless Mode, Seccomp, AppArmor op een VPS.
Firewall. Alleen poorten 80 en 443 moeten publiek bereikbaar zijn. Docker manipuleert iptables rechtstreeks, wat UFW-regels kan omzeilen. Zie Docker omzeilt UFW: 4 geteste oplossingen voor je VPS voor de oplossing.
Logs. Bekijk de proxy-logs wanneer iets misgaat:
# Traefik
docker logs traefik -f
# Caddy
docker logs caddy -f
# Nginx
docker logs nginx -f
Voor Traefik stel je --log.level=DEBUG tijdelijk in om routing- of certificaatproblemen te diagnosticeren. Voor Caddy schakel je de globale debug-optie in het Caddyfile in. Voor Nginx controleer je error.log in de container op /var/log/nginx/error.log.
Iets werkt niet?
| Symptoom | Waarschijnlijke oorzaak | Oplossing |
|---|---|---|
| Certificaat niet uitgegeven | DNS A-record wijst niet naar VPS-IP | Verifieer met dig app.example.com |
| Traefik 404 op alle routes | Container niet op het proxy-netwerk |
Controleer docker network inspect proxy |
| Caddy "permission denied" op poort 80 | Ontbrekende NET_BIND_SERVICE-capability |
Voeg cap_add: NET_BIND_SERVICE toe |
| Nginx "no such file" voor certificaat | Certbot is nog niet uitgevoerd | Voer eerst certbot certonly uit |
ERR_CONNECTION_REFUSED |
Firewall blokkeert 80/443 | Controleer ufw status of iptables -L |
Traefik acme.json-permissiefout |
Bestandsrechten te open | Voer chmod 600 acme.json uit |
| Proxy werkt op server, faalt extern | Alleen op localhost testen | Test met curl vanaf je lokale machine |
Voor productiehardening voorbij reverse proxying, zie Docker Compose resourcelimieten, healthchecks en herstartbeleid voor resource-limieten en health checks op je Compose-stacks.
Copyright 2026 Virtua.Cloud. Alle rechten voorbehouden. Deze inhoud is een origineel werk van het Virtua.Cloud-team. Reproductie, herpublicatie of herdistributie zonder schriftelijke toestemming is verboden.
Klaar om het zelf te proberen?
Deploy uw eigen server in seconden. Linux, Windows of FreeBSD.
Bekijk VPS-aanbod