Caddy è un web server e reverse proxy open-source scritto in Go, noto per la sua semplicità di configurazione e per la gestione automatica dei certificati HTTPS tramite Let’s Encrypt. A differenza di altri web server come Nginx Reverse Proxy , Caddy abilita HTTPS di default senza richiedere configurazioni manuali, rendendo la messa in sicurezza dei propri servizi (anche all’interno della propria rete) un processo praticamente trasparente.

Tutta la configurazione avviene in modo dichiarativo tramite un unico file chiamato Caddyfile, con una sintassi semplice e intuitiva che lo rende accessibile anche a chi non ha grande esperienza con i web server. Oltre al reverse proxy, Caddy offre un supporto nativo al load balancing e alla compressione automatica.

Processo di Installazione

Dopo aver installato Docker Engine e Docker Compose, è possibile procedere con l’installazione di Caddy utilizzando un container Docker. La configurazione richiede il file docker-compose.yml per definire il servizio e un Caddyfile per configurare i siti e i reverse proxy.

Installazione Standard

Per una configurazione base, il file docker-compose.yml qui sotto è sufficiente per avviare Caddy. Le porte esposte sono la 80 (HTTP) e la 443 (HTTPS), necessarie per servire il traffico web e gestire i certificati TLS automatici.

yaml
services:

  caddy:
    container_name: Caddy
    image: caddy:latest
    restart: always
    ports:
      - '80:80'
      - '443:443'
      - '443:443/udp'
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

In questa configurazione sono stati creati due volumi: caddy_data per memorizzare i certificati TLS e le informazioni persistenti, e caddy_config per la configurazione runtime di Caddy. La porta 443/udp è necessaria per il supporto al protocollo HTTP/3 (QUIC). Il Caddyfile viene montato direttamente dalla directory corrente.

Installazione con DNS Challenge

Nel caso i servizi fossero dietro un firewall o un NAT, oppure si avesse bisogno di certificati wildcard, è possibile ottenere i certificati Let’s Encrypt tramite una DNS Challenge. Questo metodo verifica (attraverso una chiamata API al provider DNS, come Cloudflare) la proprietà del dominio creando un record TXT nel DNS anziché rispondere a una richiesta HTTP, ed una volta eseguita la verifica il sistema scaricherà ed installerà il certificato.

Questo approccio richiede un plugin DNS per il provider scelto che (al momento della scrittura della guida) non è incluso nell’immagine ufficiale di Caddy. Sarà quindi necessario aggiungerlo andando a creare un Dockerfile dedicato, così da poter compilare Caddy con il modulo caddy-dns/cloudflare tramite xcaddy.

Questo Dockerfile utilizza un multi-stage build, ovvero una costruzione in due fasi separate: nella prima compila Caddy aggiungendo il plugin di Cloudflare, nella seconda copia il binario ottenuto nell’immagine finale leggera.

Dockerfile
FROM caddy:builder AS builder
RUN xcaddy build --with github.com/caddy-dns/cloudflare

FROM caddy:latest
COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Una volta creato il Dockerfile, sarà necessario andare a creare il docker-compose.yaml che è simile a quello precedente, ma utilizza build: . per costruire l’immagine dal Dockerfile appena creato. In più necessita del token API di Cloudflare, passato tramite variabile d’ambiente che può essere definita in un file .env nella stessa directory.

yaml
services:

  caddy:
    build: .
    restart: unless-stopped
    container_name: Caddy
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    environment:
      CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_TOKEN}
    volumes:
      - ./conf:/etc/caddy
      - ./site:/srv
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

Perché Cloudflare?

Cloudflare non è uno sponsor di questa guida. È semplicemente il provider DNS che utilizzo e con cui questa configurazione è stata testata. Lo stesso approccio è applicabile ad altri provider supportati dal progetto caddy-dns .

Configurazione del Caddyfile

Il Caddyfile è il file di configurazione di Caddy, all’interno del quale vengono definiti tutti i domini, i sottodomini e i relativi comportamenti del reverse proxy. La sintassi è totalmente dichiarativa e qui sotto riporto alcuni esempi comuni, ma la configurazione completa è disponibile nella documentazione ufficiale .

Configurazione DNS Challenge (opzionale)

Questo blocco è necessario solo se si utilizza la DNS Challenge per ottenere i certificati Let’s Encrypt (come descritto nella sezione di installazione). Va inserito in testa al Caddyfile e contiene le opzioni globali, tra cui l’email per le notifiche di Let’s Encrypt e le credenziali del provider DNS (inserite nel docker-compose).

text
{
	# Email per notifiche Let's Encrypt
	email mario.rossi@dominio.xx

	# DNS Challenge globale via Cloudflare
	acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}

Configurazione Reverse Proxy

Questo è l’esempio più comune: instradare il traffico da un dominio (o sottodominio) verso un altro servizio, che può trovarsi sulla stessa macchina o in un altro container. Con questa configurazione Caddy otterrà automaticamente un certificato SSL per il dominio specificato e instraderà tutto il traffico verso il servizio in ascolto sulla porta 8080.

text
servizio.dominio.xx {
    reverse_proxy localhost:8080
}

Configurazione Reverse Proxy con Cache

Oltre al reverse proxy, è possibile definire le regole di cache specificando quali file il browser deve tenere in memoria (e per quanto tempo) e quali invece devono essere ricaricati ad ogni visita. Nell’esempio qui sotto, i file statici come immagini, CSS e JavaScript vengono mantenuti in cache per una settimana, mentre le pagine HTML vengono sempre richieste al server.

text
servizio.dominio.xx {
	reverse_proxy 80.81.82.83:1234

	@static path *.js *.css *.png *.jpg *.jpeg *.gif *.svg *.ico *.woff *.woff2 *.ttf *.eot
	header @static Cache-Control "public, max-age=604800, immutable"

  @html path *.html /
	header @html Cache-Control "no-cache"
}

Configurazione di un Redirect

In alcuni casi può essere necessario reindirizzare un dominio verso un altro, ad esempio per ridirigere la versione con www verso quella senza, oppure per spostare il traffico da un vecchio dominio a uno nuovo. In questo esempio, tutte le richieste verso www.dominio.xx vengono reindirizzate con un redirect permanente (HTTP 301) verso dominio.xx, mantenendo il percorso originale grazie a {uri}.

text
www.dominio.xx {
	redir https://dominio.xx{uri} permanent
}

Configurazione di Load Balancing

Caddy supporta nativamente il load balancing semplicemente specificando più destinazioni nella direttiva reverse_proxy. Il traffico verrà distribuito automaticamente tra i vari backend in modalità round-robin.

text
servizio.dominio.xx {
	reverse_proxy 10.0.0.1:8080 10.0.0.2:8080 10.0.0.3:8080
}

Volendo è possibile specificare una politica di distribuzione diversa, come ad esempio least_conn che seleziona il backend con il minor numero di connessioni attive, oppure random.

text
servizio.dominio.xx {
	reverse_proxy 10.0.0.1:8080 10.0.0.2:8080 10.0.0.3:8080 {
		lb_policy least_conn
	}
}

Primo Accesso ed Aggiornamento

Una volta completata l’installazione e avviato il container, Caddy inizierà immediatamente a servire i siti seguendo quanto dichiarato nel Caddyfile. Se i domini puntano correttamente al server, Caddy provvederà automaticamente ad ottenere e gestire i certificati HTTPS tramite Let’s Encrypt. Per verificare che tutto funzioni correttamente è possibile controllare i log con docker logs Caddy, dove si dovrebbe vedere il messaggio di ottenimento del certificato SSL.

Quando si modifica il Caddyfile, le modifiche non vengono applicate automaticamente al salvataggio del file. Per attivarle è necessario eseguire un reload esplicito all’interno del container:

bash
docker exec Caddy caddy reload --config /etc/caddy/Caddyfile

Questo approccio è un punto di forza di Caddy: il reload avviene in modo “graceful”, ovvero la nuova configurazione viene caricata senza interrompere le connessioni attive, garantendo virtualmente zero downtime. In questo modo è possibile aggiornare le regole di reverse proxy, aggiungere nuovi domini o modificare i comportamenti della cache con la certezza che il servizio non subirà interruzioni.

Troubleshooting & Consigli

Di seguito alcuni consigli utili per risolvere i problemi di configurazione più comuni:

  • Se Caddy non riesce ad ottenere i certificati SSL, verificare che le porte 80 e 443 siano raggiungibili dall’esterno e che il dominio punti correttamente all’indirizzo IP del server. Controllare i log con docker logs Caddy per dettagli.
  • Se il reverse proxy non funziona, assicurarsi che il servizio di destinazione sia raggiungibile dal container Caddy. Se il servizio è in un altro container Docker, utilizzare il nome del container o una rete Docker condivisa al posto di localhost.
  • Per utilizzare Caddy come reverse proxy verso altri container Docker, è consigliato creare una rete Docker dedicata e collegarvi sia Caddy che i servizi da esporre, in modo che possano comunicare tramite il nome del container.