Traefik vs Caddy vs Nginx: reverse proxy Docker a confronto
Tre stack Docker Compose funzionanti per Traefik, Caddy e Nginx come reverse proxy su un VPS. Stesso backend, benchmark reali e un framework decisionale per scegliere quello giusto.
Hai dei container Docker in esecuzione su un VPS. Ti servono HTTPS, routing per nome di dominio e un singolo punto di ingresso. Traefik, Caddy e Nginx risolvono tutti questo problema. Lo risolvono in modo diverso.
Questo articolo ti fornisce tre stack Docker Compose funzionanti che deployano lo stesso backend dietro ogni proxy. Copia quello che si adatta alla tua situazione. La tabella comparativa e il framework decisionale alla fine ti aiutano a scegliere.
Tutti gli esempi usano una rete proxy dedicata, redirect da HTTP a HTTPS e impostazioni predefinite adatte alla produzione. Gli stack sono pensati per Ubuntu 24.04 su un VPS Virtua Cloud.
Cosa fa un reverse proxy per i container Docker su un VPS?
Un reverse proxy si posiziona tra internet e i tuoi container Docker. Termina il TLS (HTTPS), instrada le richieste al container corretto in base all'hostname ed espone una sola coppia di porte (80/443) invece di una porta per servizio. I tuoi container non gestiscono mai certificati e non si legano mai direttamente a porte pubbliche.
Senza un reverse proxy, ogni container avrebbe bisogno della propria porta pubblica. I visitatori accederebbero a example.com:3000 per un servizio e example.com:8080 per un altro. Un reverse proxy permette di usare app.example.com e api.example.com sulle porte standard.
Tutti e tre gli stack presuppongono:
- Docker e Docker Compose sono installati Docker in produzione su un VPS: cosa si rompe e come risolvere
- Un record DNS A punta il tuo dominio all'IP del VPS
- Le porte 80 e 443 sono aperte nel firewall Docker bypassa UFW: 4 soluzioni testate per il tuo VPS
- Hai accesso SSH come utente non-root con sudo
Ogni esempio deploya lo stesso backend: l'immagine traefik/whoami, che restituisce header HTTP e informazioni sul container. Sostituiscila con la tua applicazione reale in seguito.
Come si configura Traefik come reverse proxy Docker con HTTPS automatico?
Traefik scopre i container automaticamente leggendo le label Docker. Aggiungi le regole di routing come label su ogni servizio. Quando un container si avvia, Traefik lo rileva, richiede un certificato Let's Encrypt e inizia a instradare il traffico. Nessun ricaricamento della configurazione necessario.
Crea la directory del progetto:
mkdir -p ~/traefik-proxy && cd ~/traefik-proxy
Crea la rete Docker che tutti i servizi con proxy condivideranno:
docker network create proxy
Crea docker-compose.yml:
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
Prima di avviare, crea il file di archiviazione certificati con permessi limitati:
touch acme.json && chmod 600 acme.json
Traefik si rifiuta di partire se acme.json ha permessi troppo aperti. Il 600 garantisce che solo il proprietario possa leggere le chiavi private memorizzate all'interno.
Avvia lo stack:
docker compose up -d
Verifica che entrambi i container siano in esecuzione:
docker compose ps
Sia traefik che whoami devono mostrare lo stato Up. Testa dal tuo computer locale (non dal server):
curl https://app.example.com
La risposta contiene l'output di whoami con gli header della richiesta. L'header X-Forwarded-For nella risposta indica che Traefik sta facendo proxy del traffico e terminando il TLS.
Cosa fanno le label:
traefik.enable=trueattiva questo container (dato cheexposedbydefault=falseè impostato)traefik.http.routers.whoami.rule=Host(...)filtra le richieste per hostnametraefik.http.routers.whoami.tls.certresolver=letsencryptindica a Traefik di ottenere un certificato per questo dominio
Per aggiungere un altro servizio, aggiungilo in qualsiasi file Compose sulla stessa rete proxy con le label appropriate. Traefik lo rileva automaticamente.
È sicuro montare il socket Docker in Traefik?
Montare /var/run/docker.sock dà a Traefik accesso completo all'API Docker. Se un attaccante compromette Traefik, può creare container, leggere variabili d'ambiente (inclusi i secret) e scalare a root sull'host. Il flag :ro impedisce solo le scritture a livello di filesystem. Non limita le chiamate all'API Docker.
In produzione, usa un proxy per il socket Docker. Si posiziona tra Traefik e il daemon Docker, filtrando le chiamate API per consentire solo operazioni di lettura sui metadati dei container.
Aggiungi questo al tuo 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 non monta più il socket Docker direttamente. La rete socket-proxy è internal: true, il che significa che non ha accesso a internet in uscita. Il socket proxy consente solo richieste GET agli endpoint containers e networks.
Come si configura Caddy come reverse proxy Docker con HTTPS automatico?
Caddy gestisce HTTPS automaticamente senza nessuna configurazione oltre al nome di dominio. Punta un dominio al tuo server, inseriscilo nel Caddyfile e Caddy ottiene e rinnova i certificati da Let's Encrypt. Nessuna configurazione del resolver, nessun parametro ACME. È il percorso più breve verso HTTPS per un reverse proxy Docker.
Crea la directory del progetto:
mkdir -p ~/caddy-proxy && cd ~/caddy-proxy
Crea la rete proxy condivisa (salta se l'hai già creata per Traefik):
docker network create proxy
Crea il Caddyfile:
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"
}
}
Questa è l'intera configurazione del proxy. Caddy legge il nome di dominio, richiede un certificato e fa proxy verso il container whoami sulla porta 80. Nessun resolver di certificati, nessuna email ACME (Caddy usa quella predefinita della macchina, oppure puoi impostarla globalmente), nessun percorso di archiviazione da gestire.
Crea docker-compose.yml:
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:
La porta 443:443/udp abilita HTTP/3 (QUIC), che Caddy supporta nativamente. cap_drop: ALL con cap_add: NET_BIND_SERVICE rimuove tutte le capability Linux tranne quella necessaria per il binding su porte inferiori a 1024.
Avvia lo stack:
docker compose up -d
Controlla lo stato dei container:
docker compose ps
Entrambi i container devono mostrare Up. Testa dal tuo computer locale con output dettagliato:
curl -v https://app.example.com
Cerca HTTP/2 200 nell'output. Dovresti anche vedere gli header di sicurezza dal Caddyfile (Strict-Transport-Security, X-Content-Type-Options, ecc.).
Per aggiungere un altro servizio, aggiungi un nuovo blocco nel Caddyfile con il dominio e la direttiva reverse_proxy, poi ricarica:
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
Non serve riavviare il container. Caddy non ha bisogno del socket Docker. Non scopre automaticamente i container. Gestisci il routing nel Caddyfile.
Come si configura Nginx come reverse proxy Docker con Let's Encrypt?
Nginx ti dà il controllo completo su ogni direttiva di proxy, header, dimensione del buffer e regola di cache. Il compromesso è la configurazione manuale. Nginx non ottiene certificati TLS da solo. Lo abbini a Certbot, che gestisce le challenge ACME e il rinnovo dei certificati.
Crea la directory del progetto:
mkdir -p ~/nginx-proxy && cd ~/nginx-proxy
Crea la rete proxy condivisa:
docker network create proxy
Crea la configurazione Nginx 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; nasconde la versione di Nginx dagli header di risposta. La divulgazione della versione aiuta gli attaccanti a prendere di mira vulnerabilità note.
Crea docker-compose.yml:
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 richiede che i file dei certificati esistano prima dell'avvio. La configurazione sopra fa riferimento a /etc/letsencrypt/live/app.example.com/fullchain.pem, che non esiste ancora. Per il certificato iniziale, sostituisci temporaneamente app.conf con una versione solo HTTP:
server {
listen 80;
server_name app.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
Avvia Nginx e il backend:
docker compose up -d nginx whoami
Richiedi il certificato iniziale:
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
Una volta ottenuto il certificato, ripristina l'app.conf completo (la versione con il blocco server SSL mostrato sopra), poi avvia lo stack completo:
docker compose up -d
Verifica che tutti i container siano in esecuzione:
docker compose ps
Testa dal tuo computer locale:
curl -v https://app.example.com
L'header di risposta server: dovrebbe mostrare nginx senza numero di versione, a conferma che server_tokens off è attivo.
Per aggiungere un altro servizio, crea un nuovo file .conf in nginx/conf.d/, poi ricarica:
docker compose exec nginx nginx -s reload
Per il rinnovo dei certificati, il container Certbot esegue certbot renew ogni 12 ore. Dopo il rinnovo, ricarica Nginx per applicare i nuovi certificati. Automatizza questo processo con un cron job o uno script che controlla le date di modifica dei certificati. Per un approfondimento sulla configurazione di Nginx come reverse proxy, vedi Come configurare Nginx come reverse proxy.
Come si confrontano Traefik, Caddy e Nginx per il reverse proxying Docker?
Traefik vince sull'auto-discovery. Caddy vince sulla semplicità. Nginx vince sul controllo. La tabella seguente analizza i compromessi che contano quando si eseguono container Docker su un VPS.
| Caratteristica | Traefik v3 | Caddy 2.11 | Nginx 1.28 |
|---|---|---|---|
| Auto-discovery | Sì (label Docker) | No (Caddyfile manuale) | No (file conf manuali) |
| Automazione TLS | ACME integrato | ACME integrato | Richiede sidecar Certbot |
| Metodo di configurazione | Label Docker + YAML/CLI statico | Caddyfile o API JSON | File nginx.conf |
| Ricaricamento config | Automatico su eventi container | caddy reload (zero downtime) |
nginx -s reload (zero downtime) |
| Socket Docker richiesto | Sì (o socket proxy) | No | No |
| HTTP/3 (QUIC) | Sperimentale | Sì (predefinito) | Tramite modulo di terze parti |
| Middleware/plugin | Integrati (rate limit, auth, header) | Integrati + plugin Go | Tramite direttive di config |
| Community/documentazione | Grande, attiva, buona documentazione | Più piccola, documentazione eccellente | La più grande, documentazione estesa |
| Curva di apprendimento | Media (label + config statica) | Bassa (Caddyfile intuitivo) | Alta (molte direttive) |
Quale reverse proxy usa meno memoria?
Il consumo di memoria a riposo conta su un VPS dove ogni megabyte è prezioso. Questi numeri provengono da docker stats --no-stream su un VPS Virtua Cloud con 4 vCPU / 8 GB di RAM con Ubuntu 24.04. Ogni proxy era a riposo senza traffico prima della misurazione.
| Proxy | RAM a riposo | Dimensione immagine |
|---|---|---|
| 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 consuma di gran lunga meno memoria. Caddy si posiziona nel mezzo. Il consumo più alto di Traefik deriva dal mantenere in memoria lo stato del provider Docker e la tabella di routing. Tutti e tre usano le immagini predefinite (basate su Debian/Alpine). Le varianti Alpine ridurrebbero le dimensioni delle immagini, ma con possibili problemi di compatibilità con alcune estensioni.
Sotto carico leggero (100 richieste concorrenti tramite wrk), tutti e tre gestiscono il traffico senza aumenti significativi di CPU o memoria su questa dimensione di VPS. Le differenze contano solo su larga scala o sui piani VPS più piccoli.
Come scegliere il reverse proxy giusto per la tua configurazione Docker?
La scelta giusta dipende da quanti servizi esegui, quanto spesso cambiano e cosa conosci già.
Scegli Traefik quando:
- Esegui molti container che cambiano frequentemente (aggiunta/rimozione di servizi settimanalmente)
- Vuoi routing zero-touch: deploya un container con le label ed è online
- Usi Docker Swarm o hai bisogno di service discovery su più nodi
- Accetti l'esposizione del socket Docker (con un socket proxy in produzione)
Scegli Caddy quando:
- Esegui pochi servizi che cambiano raramente
- Vuoi il percorso più semplice verso HTTPS automatico
- Non vuoi montare il socket Docker
- Apprezzi un'immagine piccola e un basso consumo di memoria
- Vuoi il supporto HTTP/3 senza configurazione aggiuntiva
Scegli Nginx quando:
- Conosci già la configurazione di Nginx
- Hai bisogno di controllo granulare sul comportamento del proxy (buffer, cache, header personalizzati per location)
- Vuoi il minor consumo di memoria possibile
- Il tuo team infrastruttura ha strumenti e monitoring Nginx esistenti
- Non ti pesa gestire Certbot separatamente
Albero decisionale:
- Esegui più di 5 servizi Docker che cambiano regolarmente? Sì -> Traefik
- Hai bisogno di tuning fine del proxy o usi già Nginx? Sì -> Nginx
- Vuoi il minor numero di componenti e il setup più veloce? Sì -> Caddy
Per la maggior parte degli indie hacker che deployano uno o due progetti personali, Caddy è il miglior punto di partenza. Per i team DevOps che gestiscono una flotta di container, l'auto-discovery di Traefik ripaga l'investimento. Per i team che già usano Nginx altrove, restare su Nginx mantiene lo stack coerente Reti Docker su un VPS: bridge, host e macvlan spiegati.
Hardening della sicurezza per tutti e tre i proxy
Qualunque proxy tu scelga, applica queste pratiche di sicurezza di base.
Header di sicurezza. Tutti e tre gli esempi sopra includono HSTS, X-Content-Type-Options, X-Frame-Options e Referrer-Policy. Per Traefik, aggiungili come label middleware:
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 ha un middleware di rate limiting integrato. Caddy ha una direttiva rate_limit disponibile come plugin. Nginx usa limit_req_zone nella configurazione. Il rate limiting protegge il backend da attacchi brute-force e abusi.
Isolamento rete Docker. Ogni esempio usa una rete proxy esterna. I servizi backend non devono stare sulla rete bridge predefinita. Solo i container che necessitano di proxy si uniscono alla rete proxy. I container database e i servizi interni restano su reti separate e interne Sicurezza Docker: Rootless Mode, Seccomp e AppArmor su VPS.
Firewall. Solo le porte 80 e 443 devono essere accessibili pubblicamente. Docker manipola iptables direttamente, il che può aggirare le regole UFW. Vedi Docker bypassa UFW: 4 soluzioni testate per il tuo VPS per la soluzione.
Log. Controlla i log del proxy quando qualcosa non funziona:
# Traefik
docker logs traefik -f
# Caddy
docker logs caddy -f
# Nginx
docker logs nginx -f
Per Traefik, imposta --log.level=DEBUG temporaneamente per diagnosticare problemi di routing o certificati. Per Caddy, attiva l'opzione globale debug nel Caddyfile. Per Nginx, controlla error.log all'interno del container in /var/log/nginx/error.log.
Qualcosa non funziona?
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| Certificato non emesso | Record DNS A non punta all'IP del VPS | Verifica con dig app.example.com |
| Traefik 404 su tutte le rotte | Container non nella rete proxy |
Controlla docker network inspect proxy |
| Caddy "permission denied" sulla porta 80 | Capability NET_BIND_SERVICE mancante |
Aggiungi cap_add: NET_BIND_SERVICE |
| Nginx "no such file" per il certificato | Certbot non è stato ancora eseguito | Esegui prima certbot certonly |
ERR_CONNECTION_REFUSED |
Firewall che blocca 80/443 | Controlla ufw status o iptables -L |
Errore permessi acme.json in Traefik |
Permessi del file troppo aperti | Esegui chmod 600 acme.json |
| Il proxy funziona sul server, fallisce dall'esterno | Test solo su localhost | Testa con curl dal tuo computer locale |
Per l'hardening in produzione oltre al reverse proxying, vedi Limiti di risorse, healthcheck e politiche di riavvio in Docker Compose per limiti di risorse e health check sui tuoi stack Compose.
Copyright 2026 Virtua.Cloud. Tutti i diritti riservati. Questo contenuto è un'opera originale del team Virtua.Cloud. La riproduzione, ripubblicazione o redistribuzione senza autorizzazione scritta è vietata.
Pronto a provare?
Distribuisci il tuo server in pochi secondi. Linux, Windows o FreeBSD.
Vedi piani VPS