SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

Nginx, Caddy o Traefik: quale reverse proxy scegliere

Confronta Nginx, Caddy e Traefik su HTTPS automatico, costo di configurazione per app, WebSocket e routing Docker su un VPS con un solo IP pubblico.

Nginx vs Caddy vs Traefik: la risposta breve

Nginx, Caddy e Traefik svolgono tutti lo stesso compito come reverse proxy: restano in ascolto sulla porta 443, leggono il nome host in ogni richiesta e la inoltrano al servizio corretto sul tuo VPS. Ognuno dei tre consente di pubblicare quattro applicazioni self-hosted dietro un unico indirizzo IP pubblico. Tutti hanno prestazioni sufficienti, tanto che il collo di bottiglia sarà costituito dalle applicazioni. La differenza riguarda il modo in cui ciascuno ottiene un certificato TLS (transport layer security) e il livello di configurazione richiesto da ogni applicazione aggiuntiva. L’altra differenza emerge in seguito, quando serve una funzione che i tutorial più comuni non trattano.

Scegli Caddy se vuoi che HTTPS venga gestito automaticamente e i tuoi servizi sono normali applicazioni web. Scegli Traefik se tutto funziona in Docker Compose e aggiungi un nuovo servizio ogni poche settimane. Scegli Nginx se lo usi già oppure se ti servono il caching delle risposte, i certificati client, l’inoltro TCP trasparente o una configurazione esistente complessa che preferisci non riscrivere.

Come ottiene ciascuno un certificato TLS?

Questo criterio è decisivo per la maggior parte delle persone, quindi partiamo da qui. Alla fine, tutti e tre usano lo stesso certificato rilasciato dalla stessa autorità. Il percorso per ottenerlo, però, è diverso.

Caddy richiede il certificato perché hai specificato un hostname. Inserisci app.example.com come indirizzo del sito e Caddy richiede un certificato tramite ACME (automatic certificate management environment) a Let's Encrypt; se la richiesta fallisce, usa ZeroSSL, serve il redirect da HTTP a HTTPS sulla porta 80 e rinnova automaticamente il certificato. Non servono altri strumenti né un timer da controllare. I certificati vengono salvati nella directory dati dell'utente caddy, /var/lib/caddy/.local/share/caddy quando installi il pacchetto. Aggiungi quindi questo percorso ai backup oppure accetta una nuova emissione dopo una ricostruzione del server. Per un hostname non pubblico, tls internal firma il certificato con la propria autorità di certificazione locale. Il risultato è equivalente a creare un certificato autofirmato su Ubuntu, ma il rinnovo viene gestito automaticamente.

Nginx non include un client ACME. Certbot ottiene il certificato e il suo plugin --nginx modifica il server block per aggiungere l'ascolto sulla porta 443 e il redirect. Il rinnovo viene eseguito da un timer systemd installato dal pacchetto. Ci sono quindi due componenti da gestire e due aspetti da verificare: systemctl list-timers | grep certbot mostra che il timer esiste, mentre sudo certbot renew --dry-run dimostra che il processo di rinnovo funziona ancora. La procedura dettagliata è descritta in Certbot su Ubuntu 24.04 con Nginx. Lo stesso strumento gestisce anche un certificato wildcard tramite la challenge DNS-01 quando hai più sottodomini di quanti tu voglia elencare.

Traefik include un proprio client ACME. Configuri un certificate resolver nella configurazione statica e ogni router può quindi utilizzarlo. Tutto lo stato, inclusi la chiave dell'account e i certificati, viene salvato in un singolo file acme.json. Traefik rifiuta di usare questo file se è leggibile da utenti diversi dal proprietario e lo segnala prima di disattivare il 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

Monta una directory e lascia che sia Traefik a creare il file. Crealo prima con touch: in questo modo eredita il tuo umask, che è il motivo per cui la maggior parte degli utenti incontra quel messaggio.

Una cosa vale per tutti e tre. La challenge HTTP-01 richiede che la porta 80 sia raggiungibile da Internet, perché l'autorità di certificazione deve connettersi a quella porta. Se apri soltanto la porta 443, l'emissione fallisce con un errore che sembra indicare un problema DNS.

Lo stesso instradamento di due applicazioni in tre configurazioni

Il compito è questo: app.example.com deve essere inoltrata a un servizio su 127.0.0.1:8080, mentre files.example.com deve essere inoltrata a un servizio su 127.0.0.1:8081; entrambe le connessioni devono usare HTTPS. Di seguito è riportata la configurazione completa per ciascun proxy, così la differenza di verbosità è evidente e non viene soltanto dichiarata.

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

Collegate quindi il file, verificatene la configurazione, ricaricate il servizio e aggiungete il certificato.

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

Il comando nginx -t, che stampa syntax is ok e test is successful, è il controllo da eseguire prima di ogni reload. La seconda applicazione usa lo stesso blocco, modificando hostname e porta. Le righe proxy_set_header non sono decorative: quando proxy_pass specifica un indirizzo, nginx invia per impostazione predefinita Host: 127.0.0.1:8080 al servizio upstream. Di conseguenza, un'applicazione che costruisce URL assoluti usando l'header Host invierà gli utenti a localhost. La funzione di ciascuno di questi quattro header e il motivo per cui una barra finale in proxy_pass modifica silenziosamente il percorso ricevuto dall'applicazione sono illustrati direttiva per direttiva in questa guida a un blocco server nginx.

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

Questo è l'intero file. reverse_proxy imposta direttamente X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host e, per impostazione predefinita, ignora i valori inviati dal client in questi header. Una richiesta non può quindi comunicare al backend un'origine contraffatta. Certificati, redirect dalla porta 80 e rinnovo derivano tutti dai due indirizzi dei siti. Nel file non serve configurare altro.

Traefik

Traefik richiede una configurazione statica prima di poter instradare le richieste. Ecco un servizio Compose con il tag dell'immagine aggiornato ad agosto 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

Ogni applicazione definisce quindi il proprio instradamento tramite label, nel relativo file Compose:

    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"

loadbalancer.server.port è la porta interna al container, non una porta pubblicata, perché Traefik raggiunge il container tramite una rete Docker condivisa. L'applicazione non richiede alcuna riga ports:, ed è questo il vantaggio principale: viene pubblicato soltanto Traefik. La configurazione completa, compresa la rete condivisa e il middleware di redirect, è disponibile in instradare più applicazioni con Traefik e Docker Compose.

Quanto costa in configurazione ogni applicazione aggiuntiva?

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

Conteggiando i blocchi precedenti, il blocco server di Nginx contiene 11 righe non vuote e va riscritto per ogni hostname. Il blocco site di Caddy contiene 3 righe. Traefik richiede 17 righe di configurazione statica prima di poter gestire una sola richiesta, quindi 5 label per ogni applicazione.

Valuta il compromesso, non soltanto il vincitore. Traefik richiede la configurazione iniziale più estesa prima della prima applicazione, ma il costo per ogni applicazione successiva è il più basso. I due totali si equivalgono intorno al terzo sito. Al di sotto di questa soglia, la configurazione statica è un sovraccarico non necessario. Al di sopra, le label diventano più convenienti e il vantaggio aumenta, perché il routing si trova accanto al servizio a cui si riferisce. Se elimini il servizio, elimini anche la relativa route. Questo è il punto debole di un file di configurazione centralizzato: lascia blocchi server obsoleti per applicazioni che non esistono più da mesi.

Il conteggio delle righe favorisce inoltre Nginx. Ognuno di questi blocchi richiede un symlink, un nginx -t, un reload e un'esecuzione di certbot, mentre la modifica in Caddy richiede un solo reload e quella in Traefik non richiede alcun comando. Tutti e tre eseguono il reload senza interrompere le connessioni attive. La differenza sta nel numero di passaggi separati che devi ricordare all'una di notte.

Quale soluzione conosce i tuoi container?

Traefik monitora il socket Docker e crea i router leggendo le label dei container quando questi vengono avviati o arrestati. Nessun'altra soluzione qui descritta lo fa. Nginx e Caddy richiedono entrambi una modifica della configurazione e un reload quando compare un nuovo container. Inoltre, devono poter raggiungere l'indirizzo del servizio: una porta pubblicata su loopback oppure una rete Docker condivisa a cui sia collegato anche il proxy.

Questa funzionalità ha un costo, che va dichiarato con chiarezza. Traefik legge /var/run/docker.sock. Chiunque possa comunicare con quel socket può avviare un container con il filesystem dell'host montato al suo interno, ottenendo così l'accesso root sull'host. Il montaggio in sola lettura riduce il rischio, ma non lo elimina. Se questo aspetto è rilevante per il tuo modello di minaccia, inserisci un socket proxy che esponga soltanto gli endpoint per l'elenco dei container necessari a Traefik.

Caddy può eseguire il rilevamento basato sulle label tramite un plugin della community. Tuttavia, i plugin di Caddy vengono compilati nel binario. Devi quindi creare un binario personalizzato o un'immagine personalizzata con xcaddy, assumendoti la responsabilità della relativa build e dei suoi aggiornamenti. Per tre o quattro servizi, modificare un Caddyfile richiede meno lavoro.

WebSocket e streaming: cosa si rompe e perché

È nginx ad avere bisogno di una configurazione aggiuntiva. Una connessione WebSocket inizia come una richiesta HTTP che contiene Upgrade: websocket, ma nginx non inoltra a monte gli header hop-by-hop se non glielo si indica esplicitamente.

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

All'interno del blocco location devono quindi essere presenti tutte e tre le righe:

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

Se ne manca una, la console del browser mostra WebSocket connection to 'wss://app.example.com/ws' failed, mentre il log del backend registra una normale richiesta GET. map è necessario perché un Connection: upgrade codificato direttamente verrebbe inviato a ogni richiesta, comprese quelle HTTP normali che dovrebbero indicare close.

Anche due impostazioni predefinite di Nginx possono causare problemi. proxy_read_timeout è impostato a 60 secondi e si applica al tunnel dopo l'upgrade. Di conseguenza, il proxy chiude una connessione WebSocket che non trasferisce dati per un minuto. Inoltre, gli eventi server-sent arrivano in ritardo o a raffiche finché non si imposta proxy_buffering off; nella relativa location, perché nginx mantiene la risposta nel buffer mentre la pagina attende i dati.

Caddy esegue l'upgrade e converte la connessione in un tunnel bidirezionale senza richiedere direttive. Inoltre, esegue il flush immediato quando la risposta è text/event-stream o quando la sua lunghezza non è nota, quindi lo streaming funziona senza modifiche. Traefik inoltra gli upgrade e non esegue il buffering delle risposte, a meno che non si aggiunga manualmente il middleware buffering. Se i servizi includono chat, un terminale web, il monitoraggio continuo dei log o dashboard aggiornate in tempo reale, questa differenza incide concretamente sulla quantità di configurazione da scrivere e sottoporre a troubleshooting.

Il blocco server completo di Nginx, con WebSocket e SSE inclusi
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;
    }
}

map appartiene al contesto http, non a server. Deve quindi essere inserito in un file separato sotto /etc/nginx/conf.d/. Disattivare proxy_buffering soltanto nelle location che eseguono lo streaming, perché il buffering consente a nginx di liberare rapidamente il worker del backend nelle risposte normali. Certbot riscrive questo blocco quando viene eseguito; dopo l'esecuzione, leggere nuovamente il file.

Cosa succede quando serve qualcosa di insolito?

È in questi casi che Nginx giustifica le righe di configurazione aggiuntive.

  • Certificati client, chiamati anche mTLS (TLS reciproco), in cui anche il client deve presentare un certificato. Nginx richiede ssl_client_certificate /etc/ssl/ca.pem; e ssl_verify_client on; nel blocco server. Caddy richiede un blocco client_auth all'interno di tls. Le label di Traefik non possono esprimere questa configurazione: si definisce un'opzione TLS in un file provider e si collega il router tramite traefik.http.routers.app.tls.options=mtls@file. Il modello basato interamente sulle label prevede un'eccezione già al primo caso di questo tipo.
  • Upload di grandi dimensioni. Per impostazione predefinita, Nginx limita i corpi delle richieste a 1 MB. Un upload più grande restituisce 413 Request Entity Too Large e il log degli errori riporta client intended to send too large body. Aumentare il valore con client_max_body_size. Caddy e Traefik non impostano alcun limite sul corpo della richiesta per impostazione predefinita, quindi la richiesta raggiunge l'applicazione e viene applicato il limite definito dall'applicazione stessa.
  • Caching delle risposte. Nginx include proxy_cache, una funzionalità stabile e collaudata. Caddy richiede un plugin compilato nell'installazione. La build open source di Traefik non include alcuna cache HTTP, un aspetto che sorprende chi presume che ogni proxy esegua il caching.
  • TCP o UDP non elaborati, ad esempio per la porta di un database o per un game server. Nginx include il modulo stream. Traefik supporta router TCP e UDP sui propri entrypoint. Caddy richiede un altro plugin e quindi un'altra build personalizzata.
  • Un web server già dietro il proxy. Se il servizio è una classica applicazione PHP, uno stack LAMP su Ubuntu 24.04 include già Apache e aggiungere un proxy davanti crea due punti in cui vengono impostati gli header e due punti in cui può essere riscritto un URL. Decidere quale componente termina TLS, quindi mantenere l'altro su HTTP semplice associato all'interfaccia di loopback.

La trappola del firewall che segue questa scelta

Lo scopo di un reverse proxy è lasciare aperte soltanto le porte 80 e 443. Docker annulla silenziosamente questo isolamento. La pubblicazione di una porta con -p 8080:80 scrive una regola DNAT nella tabella nat. Questa regola viene valutata prima delle regole INPUT gestite da ufw. Di conseguenza, ufw deny 8080 non blocca la porta e l'applicazione viene esposta su Internet accanto al proxy configurato con tanta attenzione. Associa le porte pubblicate all'interfaccia loopback con 127.0.0.1:8080:80, oppure rimuovi completamente ports: e consenti al proxy di raggiungere il container tramite una rete Docker, come nell'esempio con Traefik riportato sopra. Il meccanismo e la soluzione sono descritti in perché le porte pubblicate da Docker bypassano ufw.

Esegui il test da una macchina diversa dal VPS, perché un controllo eseguito direttamente sul server ha sempre esito positivo:

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

Il risultato atteso è Connection refused oppure un timeout. Una risposta HTTP indica che l'applicazione è raggiungibile senza passare dal proxy. In tal caso, tutta la configurazione applicata sopra è inefficace.

Quale proxy scegliere?

Soprattutto siti statici, con una o due applicazioni: Caddy. HTTPS automatico elimina l'attività ricorrente più impegnativa. La configurazione resta abbastanza breve da poter essere letta in una sola schermata, e un sito statico richiede una riga root e una riga file_server all'interno dello stesso blocco del sito. Il compromesso è una disponibilità minore di soluzioni pronte da copiare e incollare quando si verifica un problema insolito.

Un homelab basato su docker-compose che continua a crescere: Traefik. Dopo il terzo servizio, usare le label richiede meno lavoro che modificare un file centrale, e quando elimini un servizio elimini anche la relativa route. Dedica un pomeriggio alla prima configurazione, perché entrypoint, router, servizi e middleware sono termini nuovi. Un errore di battitura in una label di solito produce un 404 da Traefik invece di impedire l'avvio, quindi leggi docker logs traefik per individuare l'errore di analisi prima di concludere che l'applicazione non funziona.

Una configurazione Nginx esistente o uno dei requisiti elencati sopra: Nginx. Offre già una soluzione per il caching delle risposte e per i certificati client, e quasi tutte le guide di terze parti lo presuppongono. Il compromesso è che i certificati e il supporto WebSocket devono essere configurati manualmente.

Indipendentemente dalla scelta, vale una regola. Esattamente un processo resta in ascolto sull'interfaccia pubblica; tutti gli altri restano in ascolto su loopback o su una rete Docker privata.

FAQ

Quale reverse proxy è più adatto per alcune applicazioni Docker su un singolo VPS?

Per tre o quattro servizi che aggiungi solo occasionalmente, Traefik ripaga il tempo investito, perché ogni applicazione contiene le proprie label di routing e non richiede modifiche a un file centrale. Se i servizi sono stabili e vuoi soprattutto smettere di gestire HTTPS, Caddy richiede meno apprendimento e offre meno possibilità di configurazione errata. Scegli Nginx se lo conosci già oppure se ti serve una funzione che gli altri due non offrono, come il caching delle risposte o un listener TCP semplice.

Caddy non richiede davvero alcuna configurazione dei certificati?

Nel caso normale, sì. Impostare un hostname pubblico come indirizzo del sito costituisce l'intera configurazione: Caddy richiede il certificato tramite ACME, gestisce il redirect dalla porta 80 e rinnova il certificato prima della scadenza. Devono comunque essere vere due condizioni. La porta 80 deve essere raggiungibile da Internet per la challenge HTTP-01 e il record DNS A o AAAA dell'hostname deve puntare già al VPS, perché la certification authority risolve il nome e si connette nuovamente al server.

Posso eseguire Nginx e Traefik sullo stesso VPS?

Non sulle stesse porte. Quello che viene avviato per secondo non riesce a eseguire il bind e nginx restituisce bind() to 0.0.0.0:443 failed (98: Address already in use), mentre Traefik registra un errore di bind simile e termina. Esegui un solo proxy sulle porte 80 e 443 e instrada tutti gli altri servizi tramite quel proxy. Se stai effettuando una migrazione, sposta gli hostname uno alla volta: fai inoltrare dal proxy front-end le richieste al proxy precedente su una porta di loopback finché non hai trasferito l'ultimo sito.

Perché le connessioni WebSocket si interrompono dopo 60 secondi dietro Nginx?

proxy_read_timeout ha un valore predefinito di 60 secondi e si applica al tunnel dopo il completamento dell'upgrade. Di conseguenza, una connessione senza traffico per un minuto viene chiusa dal proxy e non dall'applicazione. Aumenta il valore per quella location con proxy_read_timeout 3600s; oppure configura l'applicazione per inviare un frame ping ogni 30 secondi. Caddy e Traefik non chiudono le connessioni con upgrade inattive tramite un timer di un minuto. Per questo la stessa applicazione può risultare stabile dietro questi proxy e instabile dietro Nginx.