Installare Certbot per nginx su Ubuntu 24.04
Scopri i comandi apt e snap, scegli una sola installazione e risolvi il timeout sulla porta 80 durante il rinnovo dei certificati Let's Encrypt.
Installare Certbot: apt o snap
Su Ubuntu 24.04, sudo apt install certbot python3-certbot-nginx installa una versione funzionante di Certbot che emette certificati Let's Encrypt reali e riconosciuti pubblicamente. La documentazione upstream di Certbot indica invece snap; la differenza è limitata: snap segue le release upstream, mentre il pacchetto dell'archivio segue la versione inclusa nell'LTS e riceve gli aggiornamenti di sicurezza.
Scegli una sola opzione. Due copie di Certbot significano due timer di rinnovo che operano sullo stesso albero /etc/letsencrypt. Quello che hai dimenticato è proprio quello che può creare problemi.
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 quando viene usato systemd.
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 include un proprio timer, snap.certbot.renew.timer. Rimuovi il pacchetto apt prima di installare snap.
Dopo l'installazione, entrambe le opzioni funzionano nello stesso modo. 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 delle chiavi e dei certificati, live/ contiene i collegamenti simbolici a quelli correnti, renewal/ contiene un file di configurazione per ogni certificato e accounts/ contiene la chiave dell'account ACME.
Cosa esegue realmente HTTP-01 e perché la porta 80 è obbligatoria
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. Ne derivano tre conseguenze, che spiegano la maggior parte delle emissioni non riuscite.
- La porta 80 deve essere raggiungibile da Internet, non soltanto dal laptop. Una regola
ufw, un security group del provider cloud o un firewall della console VPS che apre soltanto la porta 443 impedisce l'emissione e tutti i rinnovi successivi. - Il DNS deve puntare già a questo server. Il server di validazione esegue la propria risoluzione dall'esterno; le voci
/etc/hostse la cache del browser non hanno alcun effetto. - Se pubblichi un record AAAA, IPv6 viene provato per primo. Let's Encrypt riprova tramite IPv4 quando la connessione IPv6 fallisce immediatamente, ma un record AAAA obsoleto che punta a un host che accetta la connessione e pubblica un servizio diverso causa un errore definitivo.
I redirect sono consentiti: la validazione segue un redirect HTTP verso HTTPS e non considera se il certificato dell'altra estremità è assente, scaduto o autofirmato. Tuttavia, la procedura non inizia mai da una porta diversa dalla 80. Certbot non implementa TLS-ALPN-01, quindi «usare semplicemente la 443» non è una soluzione alternativa.
Scelta dell’autenticatore: --nginx, --webroot, --standalone
--nginx è l’opzione predefinita corretta quando nginx è già in esecuzione e pubblica già il dominio. Certbot analizza la configurazione, inserisce temporaneamente una location per la challenge, ricarica nginx, esegue la convalida e quindi scrive le direttive TLS nel blocco server. Non si verifica alcun downtime.
sudo certbot --nginx -d example.com -d www.example.comAdatto agli 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 mantieni in git o la distribuisci con Ansible. Certbot scrive solo il file della challenge in una directory che stai già pubblicando.
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 server di posta, un'API che comunica soltanto tramite 443 oppure uno script di primo avvio eseguito prima che nginx sia disponibile. Certbot acquisisce direttamente la porta 80 per alcuni secondi. Se nginx è in esecuzione, il comando fallisce. Arrestalo prima dell'esecuzione e riavvialo al termine:
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 iniziale è questo: nginx non si avvia se ssl_certificate punta a un file inesistente e Certbot non può eseguire la convalida mentre nginx è fermo. 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 ^~ sulla location ACME è necessario: impedisce al blocco return 301 di intercettare la richiesta di challenge. Mantenere questa location sulla porta 80 consente ai rinnovi di continuare a funzionare anche dopo avere configurato il resto del sito per usare esclusivamente HTTPS.
Entrambi i blocchi precedenti servono file dal disco; se nginx fa invece da proxy per un’applicazione, location / diventa un blocco proxy_pass. Il blocco server del reverse proxy, riga per riga descrive gli header richiesti dall’applicazione, mentre la location ACME e le direttive TLS restano esattamente invariate.
La sintassi di HTTP/2 dipende dalla versione di nginx. Usare la forma errata causa un errore all’avvio. Ubuntu 24.04 include nginx 1.24, che richiede la direttiva inline, listen 443 ssl http2;. Debian 13 include una versione più recente di nginx, che richiede la direttiva http2 on; separata. Verificare prima nginx -v.
Configurare nginx in modo che punti a live/, mai a archive/. I symlink live/ vengono riassegnati a ogni rinnovo; un percorso fisso all’interno di archive/ vincola il server a un certificato che prima o poi scade.
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 scaricare un file. DNS-01 è l'unico metodo disponibile: dimostri il controllo pubblicando un record TXT _acme-challenge.example.com. Per farlo senza intervento, Certbot richiede le credenziali API del provider DNS. I plugin del provider servono proprio a questo scopo. La guida completa ai certificati wildcard descrive il funzionamento dei record TXT e il problema dei rinnovi in 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 pacchetto è sudo apt install python3-certbot-dns-cloudflare. Le credenziali devono essere inserite in un file accessibile solo 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 singola 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 alla shell di espanderlo. DNS-01 risolve anche i casi che HTTP-01 non può gestire: certificati per host senza una porta pubblica 80, per un servizio interno, per un server raggiungibile soltanto tramite una VPN WireGuard self-hosted su un VPS oppure per un pannello di amministrazione su un'interfaccia privata.
Rinnovo: 90 giorni, il timer e il deploy hook
I certificati Let's Encrypt sono validi per 90 giorni. Certbot esegue il rinnovo quando restano meno di 30 giorni. Questo offre una finestra di 30 giorni in cui un rinnovo non riuscito è un problema risolvibile, non un'interruzione del servizio. Let's Encrypt non invia più email di avviso sulla scadenza. Nessuno ti avviserà manualmente. Il monitoraggio spetta a te.
Controlla il timer installato:
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renew esamina ogni configurazione in /etc/letsencrypt/renewal/, ignora quelle al di fuori della finestra di 30 giorni e rinnova le altre usando esattamente i flag dell'esecuzione originale. Per questo la prima esecuzione è importante: è quella i cui parametri vengono registrati.
Rinnovare il file sul disco non modifica nulla da solo. nginx continua a usare il vecchio certificato già caricato in memoria finché non riceve un comando di reload. Configura una volta il 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 elemento 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ò pubblicare un certificato scaduto mentre certbot certificates segnala correttamente che il rinnovo è riuscito. Qualsiasi altro componente che legge il certificato all'avvio richiede lo stesso hook. Un'applicazione containerizzata, come un installazione di Nextcloud VPS con Docker, TLS e backup, richiede inoltre un'operazione dedicata di riavvio o reload, che deve essere configurata qui.
Test reale del rinnovo
sudo certbot renew --dry-runQuesto esegue la verifica completa con l'ambiente di staging di Let's Encrypt: stesso percorso del codice, stesso firewall, stesso DNS, senza consumare limiti di frequenza e senza scrivere dati su disco. Se oggi la verifica ha esito positivo, anche il rinnovo automatico tra 60 giorni avrà esito positivo, a condizione che nel frattempo non cambi nulla sul server.
Un'esecuzione di prova 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, conferma che systemctl reload nginx abbia esito positivo e controlla sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.
The errors you will actually hit
Could not bind to IPv4 or IPv6., --standalone while nginx already holds port 80. Use --nginx or --webroot, or stop nginx around the run. Confirm the holder with sudo ss -lntp | grep ':80'.
Timeout during connect (likely firewall problem), Let's Encrypt could not reach port 80. Walk outward: sudo ufw status (open it with sudo ufw allow 'Nginx Full'), then the VPS provider's own firewall, then DNS. Test from somewhere that is not your server: curl -sSv http://example.com/.well-known/acme-challenge/test. A stale AAAA record produces this same message.
unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, port 80 is reachable, but the token is not being served. The request landed in a different server block (check which one owns default_server), or the directory passed to -w is not the one nginx serves. Drop a file at /var/www/example.com/.well-known/acme-challenge/test and fetch it externally; if that 404s, the certificate was never the problem.
DNS problem: NXDOMAIN looking up A for example.com, the name does not resolve publicly. New records that have not propagated, or a record in a zone your registrar is not serving.
too many certificates already issued for: example.com, a rate limit, and the one people hit while debugging in a loop. Let's Encrypt caps duplicate certificates, the same exact set of names, at five per week, and separately allows 50 new certificates per registered domain per week; nothing unblocks either except time. Debug against staging with --dry-run.
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, nginx is configured for a certificate never issued, or one removed with certbot delete. Comment out the TLS server block, start nginx, issue, restore the block.
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, that file arrives with the nginx plugin package. On a certonly box without python3-certbot-nginx, either add the plugin or replace the include line with your own ssl_protocols and ssl_ciphers settings.
Gestire questa configurazione 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 pratica, finché un record DNS obsoleto non supera la convalida e non rende inutilizzabili anche tutti gli altri nomi associati a quel certificato. Certificati separati per ogni sito hanno problemi indipendenti, come è preferibile su un server che ospita più di un paio di servizi. Oltre un numero limitato di siti, un punto di accesso frontale 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 la necessità di Certbot. La scelta del proxy da usare come punto di accesso frontale è una decisione separata; confrontare Nginx, Caddy e Traefik significa soprattutto valutare quanta parte della gestione dei certificati e della configurazione per applicazione si vuole affidare al proxy.
Esegui il backup completo di /etc/letsencrypt, mantenendo sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt e i link simbolici. Questa struttura contiene accounts/, la chiave dell'account ACME, che non è possibile rigenerare in modo identico. Il passaggio a un nuovo VPS si riduce quindi a: sincronizzare la struttura con -a, installare Certbot, modificare la configurazione DNS ed eseguire certbot renew --dry-run prima del passaggio definitivo.
Se ricrei il server o passi a una nuova release LTS, il timer per il rinnovo non viene trasferito. Dopo qualsiasi migrazione, ripristino da snapshot o 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, a causa di un certificato che tutti ritenevano rinnovato automaticamente.
Tutto questo presuppone una macchina sotto il tuo controllo, con un indirizzo IP pubblico e la porta 80 aperta a Internet: in altre parole, un VPS. Le procedure descritte sono identiche su tutte queste macchine.
Gli stessi passaggi per i certificati si applicano con Apache invece di nginx e, quando non è possibile usare un certificato pubblico, 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; un firewall che apre solo la porta 443 blocca quindi sia la prima emissione sia ogni rinnovo automatico successivo. Un redirect dalla porta 80 a HTTPS è sufficiente: la validazione lo segue. L’unico modo per evitare completamente la porta 80 è usare DNS-01 con un plugin del provider.
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 abbastanza recente 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 uno solo: due installazioni significano due timer di rinnovo che puntano allo stesso albero /etc/letsencrypt, e quella dimenticata è l’installazione che causa il problema.
Certbot può emettere un certificato wildcard per nginx?
Solo tramite DNS-01. Un wildcard come *.example.com non ha un singolo hostname da cui recuperare un file di challenge, quindi --nginx, --webroot e --standalone non sono disponibili. Installa il plugin per il tuo provider DNS, inserisci un token API con autorizzazioni limitate in un file delle 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 glob 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; il rinnovo può quindi 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à?
Per lo più. Esegue la challenge reale nell’ambiente di staging, usando lo stesso firewall, lo stesso DNS e lo stesso percorso di codice, senza consumare il limite di richieste e senza scrivere dati su disco. Un esito positivo conferma quindi che la parte di rete funziona. Tuttavia, non dimostra in modo affidabile che l’hook di deploy venga eseguito. Verifica questo aspetto separatamente: esegui manualmente lo script dell’hook e controlla sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.