Installare Certbot per nginx su Ubuntu 24.04
Installa Certbot con apt o snap e ottieni un certificato Let's Encrypt con certbot --nginx. Evita due timer di rinnovo e il timeout sulla porta 80.
Installare Certbot: apt o snap
Su Ubuntu 24.04, sudo apt install certbot python3-certbot-nginx installa una versione funzionante di Certbot, in grado di emettere certificati Let's Encrypt validi e attendibili pubblicamente. La documentazione upstream di Certbot indica invece l'uso di snap. La differenza è limitata: snap segue le release upstream, mentre il pacchetto dell'archivio segue la versione distribuita con la LTS e riceve gli aggiornamenti di sicurezza.
Scegli una sola opzione. Due copie di Certbot significano due timer di rinnovo diretti allo stesso albero /etc/letsencrypt. Quella di cui ti sei dimenticato sarà quella che causerà problemi.
Il percorso apt:
sudo apt update
sudo apt install certbot python3-certbot-nginxQuesto installa /usr/bin/certbot, il plugin nginx, una coppia certbot.service + certbot.timer e una voce /etc/cron.d/certbot che non esegue alcuna operazione con systemd.
Il percorso snap:
sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotSnap installa il proprio timer, snap.certbot.renew.timer. Rimuovi il pacchetto apt prima di installare snap.
Dopo l'installazione, entrambe le opzioni funzionano allo stesso modo. L'unica differenza riguarda il momento in cui viene avviato il rinnovo, descritto nella sezione Rinnovo. Per impostazione predefinita, Certbot 2.x usa chiavi ECDSA (P-256). Passa --key-type rsa solo per un client che non supporta ECDSA. Tutto lo stato si trova in /etc/letsencrypt: archive/ contiene i file effettivi della chiave e del certificato, live/ contiene i link simbolici ai file correnti, renewal/ contiene un file di configurazione per ogni certificato e accounts/ contiene la chiave dell'account ACME.
Che cosa fa realmente HTTP-01 e perché la porta 80 è necessaria
La challenge HTTP-01 è una callback. Si richiede a Let's Encrypt un certificato per example.com; il servizio risolve il nome nel DNS pubblico, apre una connessione alla porta 80 dell'indirizzo trovato e richiede http://example.com/.well-known/acme-challenge/<token>. Il server risponde con il contenuto esatto del token che Certbot ha appena scritto su disco. Questo è l'intero meccanismo. Da qui derivano tre conseguenze, che spiegano la maggior parte delle richieste di certificato non riuscite.
- La porta 80 deve essere raggiungibile da Internet, non soltanto dal proprio laptop. Una regola
ufw, un security group del provider cloud o il firewall della console VPS che apre soltanto la porta 443 impedisce il rilascio del certificato e tutti i rinnovi successivi. - Il DNS deve puntare già a questo server. Il server di validazione esegue una propria risoluzione dall'esterno; le voci
/etc/hostse la cache del browser non hanno alcun effetto. - Se viene pubblicato un record AAAA, IPv6 viene provato per primo. Let's Encrypt riprova tramite IPv4 soltanto quando la connessione IPv6 va in timeout. Una connessione IPv6 rifiutata non viene ritentata; inoltre, un record AAAA obsoleto che punta a un host che accetta la connessione e fornisce un contenuto diverso causa un errore definitivo.
I redirect sono consentiti: la validazione segue un redirect HTTP verso HTTPS e non considera un problema il fatto che il certificato dall'altra parte sia assente, scaduto o autofirmato. Tuttavia, la procedura non può iniziare da una porta diversa dalla 80. Certbot non implementa TLS-ALPN-01, quindi "usare direttamente la porta 443" non è una soluzione alternativa.
Scelta dell’autenticatore: --nginx, --webroot, --standalone
--nginx è l’impostazione predefinita corretta quando nginx è già in esecuzione e serve già il dominio. Certbot analizza la configurazione, inserisce una posizione temporanea per la challenge, ricarica nginx, esegue la convalida e quindi scrive le direttive TLS nel blocco server. Non si verifica alcuna interruzione del servizio.
sudo certbot --nginx -d example.com -d www.example.comProcedura tramite script, per un server appena installato:
sudo certbot --nginx \
-d example.com -d www.example.com \
--agree-tos -m ops@example.com --no-eff-email \
--redirect --non-interactive--webroot è indicato quando non vuoi che Certbot modifichi la configurazione di nginx, ad esempio perché la generi da un template, la gestisci in git oppure la distribuisci con Ansible. Certbot scrive solo il file della challenge in una directory che servi già.
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"--standalone è indicato quando nessun servizio è in ascolto sulla porta 80: ad esempio un mail server, un'API che usa soltanto la porta 443 oppure uno script di primo avvio eseguito prima che nginx sia disponibile. Certbot si associa direttamente alla porta 80 per alcuni secondi. Se nginx è in esecuzione, la procedura fallisce; arrestalo prima dell'esecuzione e riavvialo dopo:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"Questi hook vengono registrati nella configurazione di rinnovo del certificato, quindi lo stesso arresto e riavvio vengono eseguiti automaticamente durante il rinnovo.
Un blocco server che funziona prima e dopo la disponibilità del certificato
Il problema dell’ordine delle operazioni è il seguente: nginx rifiuta di avviarsi con ssl_certificate che punta a un file inesistente, mentre Certbot non può eseguire la convalida se nginx è arrestato. Pubblicare prima il sito sulla porta 80.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}Eseguire sudo nginx -t && sudo systemctl reload nginx, verificare che curl -I http://example.com/ risponda dall’esterno del server, quindi emettere il certificato. In seguito:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}Il prefisso ACME più lungo ha già precedenza su location /, quindi return 301 non riceve mai una richiesta di challenge. Il modificatore ^~ è utile in un altro caso: quando quel prefisso corrisponde, nginx ignora completamente le location basate su espressioni regolari. Di conseguenza, una regola aggiunta in seguito, come location ~ /\. { deny all; }, non può bloccare la challenge. Mantenere questa location sulla porta 80 consente ai rinnovi di continuare a funzionare anche dopo aver imposto HTTPS sul resto del sito.
Entrambi i blocchi precedenti servono file dal disco. Se nginx inoltra invece le richieste a un'applicazione, location / diventa un blocco proxy_pass e il blocco server del reverse proxy, riga per riga descrive gli header richiesti dall'applicazione. La location ACME e le direttive TLS rimangono esattamente come sono.
La sintassi di HTTP/2 dipende dalla versione di nginx. Ubuntu 24.04 include nginx 1.24, che richiede la sintassi inline, listen 443 ssl http2;, e non si avvia se si specifica la direttiva separata http2 on;. Debian 13 include nginx 1.26, che richiede http2 on; e segnala soltanto che la forma inline è deprecata. Controllare prima nginx -v.
Configurare nginx perché punti a live/, mai a archive/. I collegamenti simbolici live/ vengono aggiornati a ogni rinnovo. Un percorso fisso all'interno di archive/ vincola invece nginx a un certificato che scade senza essere sostituito.
I wildcard richiedono DNS-01 e DNS-01 richiede un plugin
Un certificato wildcard (*.example.com) non può essere convalidato tramite HTTP-01, perché non esiste un singolo hostname da cui recuperare un file. DNS-01 è l'unica soluzione: dimostri di controllare il dominio pubblicando un record TXT _acme-challenge.example.com. Per farlo senza intervento manuale, Certbot richiede le credenziali API del provider DNS. A questo servono i plugin del provider. La guida completa ai certificati wildcard descrive il funzionamento dei record TXT e il problema dei rinnovi nella modalità manuale; segue la versione breve per Cloudflare.
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflareNel percorso apt, invece, il valore è sudo apt install python3-certbot-dns-cloudflare. Inserisci le credenziali in un file accessibile soltanto a root:
# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_hereLimita il token ai permessi di modifica DNS per quella sola zona. È una chiave per il tuo DNS: trattala come tale.
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'Racchiudi il wildcard tra virgolette per impedire al shell di espanderlo. DNS-01 risolve anche il limite di HTTP-01: consente di creare certificati per host senza la porta pubblica 80, per un servizio interno, per un server raggiungibile soltanto tramite una VPN WireGuard self-hosted su un VPS o per un pannello di amministrazione su un'interfaccia privata.
Rinnovo: i 90 giorni, il timer e il deploy hook
I certificati Let's Encrypt sono validi per 90 giorni. Il pacchetto apt (Certbot 2.9.0) avvia il rinnovo quando restano meno di 30 giorni. snap segue la versione corrente di Certbot, che dalla versione 4.1 chiede inoltre a Let's Encrypt un tempo di rinnovo suggerito tramite ACME Renewal Info (ARI) e può quindi rinnovare prima; per un certificato Let's Encrypt questo significa in genere dopo circa due terzi della sua durata. In entrambi i casi hai una finestra di circa 30 giorni, durante la quale un rinnovo non riuscito è un problema risolvibile e non un'interruzione del servizio. Dal prossimo anno i rinnovi saranno più frequenti: il profilo predefinito di Let's Encrypt passerà ai certificati di 64 giorni il 10 febbraio 2027 e ai certificati di 45 giorni il 16 febbraio 2028. Let's Encrypt non invia più email di avviso sulla scadenza. Nessuno ti avviserà direttamente, quindi ora devi occuparti tu del monitoraggio.
Controlla il timer installato dal tuo pacchetto:
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renew esamina ogni configurazione in /etc/letsencrypt/renewal/, ignora quelle per cui il rinnovo non è ancora dovuto e rinnova le altre usando le opzioni salvate nella relativa configurazione. Sono i flag del comando eseguito inizialmente, a meno che tu non li abbia modificati in seguito con certbot reconfigure. Per questo la prima esecuzione è importante: è quella che viene registrata.
Il rinnovo del file sul disco, da solo, non cambia nulla: nginx continua a usare il vecchio certificato presente in memoria finché non riceve il comando di ricaricare la configurazione. Configura una volta sola un deploy hook:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shQualsiasi file eseguibile in renewal-hooks/deploy/ viene eseguito dopo ogni rinnovo riuscito. Il flag --deploy-hook svolge la stessa funzione per un singolo certificato e salva renew_hook = ... nella relativa configurazione di rinnovo. certbot --nginx esegue il reload automaticamente; le configurazioni --webroot e --standalone non lo fanno. L'assenza di un hook è esattamente il motivo per cui un sito può servire un certificato scaduto mentre certbot certificates segnala correttamente che ne è stato rinnovato uno nuovo. Qualsiasi altro software che legge il certificato all'avvio richiede lo stesso hook. Un'applicazione containerizzata, come una installazione di Nextcloud VPS con Docker, TLS e backup, richiede anche il proprio riavvio o reload, che deve essere configurato qui.
Test reale del rinnovo
sudo certbot renew --dry-runQuesto esegue la challenge completa nell'ambiente di staging di Let's Encrypt: stesso percorso di esecuzione, stesso firewall, stesso DNS, senza consumare i limiti di frequenza dell'ambiente di produzione (lo staging ha limiti propri, molto più generosi) e senza salvare certificati su disco. Se oggi il test ha esito positivo, anche il rinnovo automatico tra 60 giorni avrà esito positivo, a condizione che nel frattempo non cambi nulla sul server.
Una simulazione non dimostra che l'hook di ricaricamento venga eseguito; il comportamento varia in base alla versione di Certbot. Verifica manualmente questa parte: esegui direttamente lo script dell'hook, verifica che systemctl reload nginx abbia esito positivo e controlla sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.
Gli errori che si verificano più spesso
Could not bind to IPv4 or IPv6., --standalone mentre nginx utilizza già la porta 80. Usare --nginx o --webroot, oppure arrestare nginx durante l'esecuzione. Verificare quale processo utilizza la porta con sudo ss -lntp | grep ':80'.
Timeout during connect (likely firewall problem), Let's Encrypt non ha potuto raggiungere la porta 80. Procedere dall'esterno verso il server: controllare sudo ufw status (aprire la porta con sudo ufw allow 'Nginx Full'), quindi il firewall del provider VPS e infine il DNS. Eseguire il test da un sistema diverso dal server: curl -sSv http://example.com/.well-known/acme-challenge/test. Un record AAAA obsoleto produce lo stesso messaggio.
unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, la porta 80 è raggiungibile, ma il token non viene pubblicato. La richiesta è stata gestita da un blocco server diverso (verificare quale blocco gestisce default_server), oppure la directory passata a -w non è quella pubblicata da nginx. Creare un file in /var/www/example.com/.well-known/acme-challenge/test e recuperarlo dall'esterno; se la richiesta restituisce 404, il certificato non era la causa del problema.
DNS problem: NXDOMAIN looking up A for example.com, il nome non viene risolto pubblicamente. Potrebbero trattarsi di record nuovi non ancora propagati oppure di un record appartenente a una zona che il registrar non sta pubblicando.
too many certificates (5) already issued ..., un limite di frequenza, in particolare quello che si raggiunge più facilmente durante il troubleshooting ripetuto. Il messaggio indica il limite e termina con un intervallo di tempo retry after. Let's Encrypt limita a cinque per settimana i certificati duplicati, cioè con lo stesso identico insieme di nomi, e per questo limite non offre alcun override; solo il trascorrere del tempo consente di procedere. Separatamente, consente 50 nuovi certificati per dominio registrato a settimana; questo limite può essere aumentato richiedendo un override, una richiesta per lo stesso identico insieme di nomi di un certificato esistente viene considerata un rinnovo ed è esente da questo limite, mentre i rinnovi coordinati tramite ARI sono esenti da tutti i limiti. Eseguire il debug sull'ambiente staging con --dry-run.
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, nginx è configurato per un certificato che non è mai stato emesso oppure che è stato rimosso con certbot delete. Commentare il blocco server TLS, avviare nginx, emettere il certificato e ripristinare il blocco.
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, quel file viene installato insieme al pacchetto del plugin nginx. Su un sistema certonly senza python3-certbot-nginx, aggiungere il plugin oppure sostituire la riga include con le proprie impostazioni ssl_protocols e ssl_ciphers.
Gestire l'infrastruttura su larga scala
Un certificato può contenere fino a 100 nomi e un singolo certbot --nginx -d a.example.com -d b.example.com ... può sembrare una scelta conveniente, finché un record DNS obsoleto non supera la convalida e rende inutilizzabili anche tutti gli altri nomi presenti nello stesso certificato. Certificati separati per ogni sito falliscono in modo indipendente, come è preferibile su un server che ospita più di un paio di servizi. Quando i siti diventano numerosi, un endpoint di accesso compatibile con ACME diventa utile: un reverse proxy Traefik che esegue più applicazioni con Docker Compose richiede e rinnova autonomamente i certificati, eliminando del tutto Certbot. La scelta del proxy da usare come endpoint di accesso è una decisione distinta; il confronto tra Nginx, Caddy e Traefik dipende soprattutto da quanta parte della gestione dei certificati e della configurazione per applicazione si vuole affidare al proxy.
Esegui il backup completo di /etc/letsencrypt, mantenendo intatti sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt e i symlink. Quella struttura contiene accounts/, la chiave dell'account ACME, che non può essere rigenerata in modo identico. Il passaggio a un nuovo VPS si riduce quindi a questo: sincronizzare la struttura con -a, installare Certbot, aggiornare la destinazione DNS ed eseguire certbot renew --dry-run prima di spostare il traffico.
Se ricrei il server o passi a una nuova release LTS, il timer dei rinnovi non viene trasferito. Dopo una migrazione, il ripristino di uno snapshot o un aggiornamento della distribuzione, esegui systemctl list-timers 'certbot*' e un --dry-run. Saltare questo controllo può rendere un sito irraggiungibile 89 giorni dopo, alle 3 di notte, perché tutti davano per scontato che il certificato si rinnovasse automaticamente.
I percorsi HTTP-01 descritti sopra presuppongono una macchina sotto il tuo controllo, con un IP pubblico e la porta 80 aperta verso Internet: in altre parole, un VPS; DNS-01 non richiede nessuna di queste condizioni. La procedura è identica in entrambi i casi.
Gli stessi passaggi per i certificati si applicano ad Apache invece di nginx e, quando un certificato pubblico non è un'opzione, un certificato autofirmato su Ubuntu è adatto ai servizi interni.
FAQ
Devo lasciare aperta la porta 80 se il mio sito serve solo HTTPS?
Sì, per la challenge HTTP-01. Let's Encrypt avvia sempre la richiesta di validazione sulla porta 80 e Certbot non implementa TLS-ALPN-01. Di conseguenza, un firewall che apre solo la porta 443 blocca sia il primo rilascio sia ogni rinnovo non presidiato successivo. Un redirect dalla porta 80 a HTTPS è corretto: la validazione lo segue. L'unico modo per evitare completamente la porta 80 è usare DNS-01, tramite un plugin del provider oppure con --manual --preferred-challenges dns. In questo caso aggiungi manualmente il record TXT e il rinnovo non è più automatico, a meno che tu non fornisca uno script --manual-auth-hook.
apt o snap: quale Certbot devo installare per nginx su Ubuntu 24.04?
Usa apt. sudo apt install certbot python3-certbot-nginx fornisce Certbot 2.9.0 su Ubuntu 24.04, una versione sufficientemente aggiornata per tutto ciò che viene descritto in questa guida. Riceve le patch di sicurezza tramite unattended-upgrades e non richiede snapd. Scegli snap solo se ti serve subito la versione più recente oppure un plugin DNS distribuito esclusivamente come snap. In ogni caso, installane esattamente uno: due installazioni significano due timer di rinnovo che puntano allo stesso albero /etc/letsencrypt, e quella dimenticata è la causa dei problemi.
Certbot può rilasciare un certificato wildcard per nginx?
Solo tramite DNS-01. Un wildcard come *.example.com non ha un singolo hostname dal quale recuperare un file di challenge. Di conseguenza, --nginx, --webroot e --standalone non sono utilizzabili. Installa il plugin per il tuo provider DNS, inserisci un token API con ambito limitato in un file di credenziali accessibile solo a root ed esegui certbot certonly --dns-cloudflare -d example.com -d '*.example.com', racchiudendo il wildcard tra virgolette per impedire l'espansione dei caratteri jolly da parte della shell.
Perché nginx continua a servire il vecchio certificato dopo un rinnovo riuscito?
nginx mantiene il certificato in memoria e non rileva il nuovo file sul disco finché non viene ricaricato. certbot --nginx esegue il reload automaticamente, ma le esecuzioni di --webroot e --standalone non lo fanno. Di conseguenza, il rinnovo può riuscire mentre il browser continua a visualizzare un certificato in scadenza. Inserisci uno script eseguibile in /etc/letsencrypt/renewal-hooks/deploy/ che esegua nginx -t && systemctl reload nginx: verrà eseguito dopo ogni rinnovo riuscito.
certbot renew --dry-run dimostra che il rinnovo funzionerà?
Nella maggior parte dei casi. Esegue la challenge reale nell'ambiente di staging, usando lo stesso firewall, lo stesso DNS e lo stesso percorso di esecuzione, senza influire sui limiti di frequenza dell'ambiente di produzione e senza salvare certificati sul disco. Un esito positivo dimostra quindi che la parte di rete funziona. Tuttavia, non dimostra in modo affidabile che l'hook di deploy venga eseguito. Verificalo separatamente: esegui manualmente lo script dell'hook e controlla sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.