SSD Nodes Learn
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-07-24

Installare Certbot su Nginx Ubuntu 24.04

Guida all'installazione con apt o snap su Ubuntu 24.04. Evita conflitti tra i timer di rinnovo e scopri come configurare correttamente il plugin nginx.

Install Certbot: apt o snap

Su Ubuntu 24.04, sudo apt install certbot python3-certbot-nginx fornisce un Certbot funzionante che emette certificati Let's Encrypt reali e pubblicamente fidati. La documentazione ufficiale di Certbot consiglia l'uso di snap; la differenza è minima: il pacchetto snap segue le versioni ufficiali, mentre il pacchetto degli archivi segue la versione inclusa nella LTS e riceve solo aggiornamenti di sicurezza.

Scegli un metodo. Avere due copie di Certbot significa avere due timer di rinnovo che puntano allo stesso albero /etc/letsencrypt; la versione dimenticata causerà errori imprevisti.

Il percorso apt:

sudo apt update
sudo apt install certbot python3-certbot-nginx

Questo installa /usr/bin/certbot, il plugin nginx, una coppia certbot.service + certbot.timer e una voce /etc/cron.d/certbot che non ha effetto sotto 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/certbot

Il pacchetto snap include il proprio timer, snap.certbot.renew.timer. Rimuovi il pacchetto apt prima di installare il pacchetto snap.

Dopo l'installazione, entrambi i metodi si comportano allo stesso modo. Certbot 2.x utilizza ECDSA (P-256) come impostazione predefinita; usa --key-type rsa solo se il client non supporta ECDSA. Tutti i dati risiedono in /etc/letsencrypt: archive/ contiene i file della chiave e del certificato reali, live/ contiene i symlink ai certificati attuali, renewal/ contiene un file di configurazione per ogni certificato, accounts/ contiene la chiave dell'account ACME.

Cosa fa effettivamente HTTP-01 e perché la porta 80 non è opzionale

La challenge HTTP-01 è una callback. Richiedi a Let's Encrypt un certificato per example.com; il server risolve il nome nel DNS pubblico, apre una connessione sulla porta 80 all'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 questo derivano tre conseguenze che causano la maggior parte dei fallimenti di emissione.

  • La porta 80 deve essere raggiungibile da internet, non solo dal tuo laptop. Una regola ufw, un security group di un cloud provider o un firewall della console VPS che apre solo la 443 blocca l'emissione e ogni futuro rinnovo.
  • Il DNS deve già puntare a questo server. Il server di validazione esegue la propria ricerca dall'esterno; le tue voci /etc/hosts e la cache del browser non hanno alcun effetto su di esso.
  • Se pubblichi un record AAAA, viene tentato prima l'IPv6. Let's Encrypt riprova via IPv4 se la connessione IPv6 fallisce completamente; tuttavia, un record AAAA obsoleto che punta a un host che accetta la connessione ma serve contenuti diversi causa un errore definitivo.

I redirect sono consentiti: la validazione segue un redirect HTTP verso HTTPS e non richiede che il certificato sul destinatario sia valido, presente o non scaduto. Tuttavia, la validazione non può iniziare su una porta diversa dalla 80. Certbot non implementa TLS-ALPN-01, quindi l'opzione "usa solo la 443" non è una soluzione valida.

Scelta dell'authenticator: --nginx, --webroot, --standalone

--nginx è l'opzione predefinita corretta se nginx è già in esecuzione e serve già il dominio. Certbot analizza la configurazione, inserisce una posizione temporanea per la challenge, ricarica nginx, effettua la validazione e scrive le direttive TLS nel server block. Nessun downtime.

sudo certbot --nginx -d example.com -d www.example.com

Scriptato, per un sistema nuovo:

sudo certbot --nginx \
  -d example.com -d www.example.com \
  --agree-tos -m ops@example.com --no-eff-email \
  --redirect --non-interactive

--webroot è l'opzione corretta se non si desidera che Certbot modifichi la configurazione di nginx — ad esempio se la configurazione è generata da un template, conservata in git o distribuita tramite Ansible. Certbot scrive solo il file di challenge in una directory già servita dal server.

sudo certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com \
  --deploy-hook "systemctl reload nginx"

--standalone è l'opzione corretta se nessun servizio è in ascolto sulla porta 80: un server mail, un'API che utilizza solo la porta 443 o uno script di avvio che viene eseguito prima della creazione di nginx. Certbot occupa la porta 80 per alcuni secondi. Se nginx è in esecuzione, l'operazione fallisce — arrestare il servizio prima dell'esecuzione:

sudo certbot certonly --standalone -d mail.example.com \
  --pre-hook "systemctl stop nginx" \
  --post-hook "systemctl start nginx"

Questi hook vengono salvati nella configurazione di rinnovo del certificato; pertanto, l'operazione di stop/start avviene automaticamente durante il rinnovo.

Un server block funzionante prima e dopo la creazione del certificato

Il problema dell'uovo o della gallina: nginx non si avvia se ssl_certificate punta a un file inesistente, ma Certbot non può effettuare la validazione se nginx è spento. Avvia il sito sulla porta 80 per primo.

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

Esegui sudo nginx -t && sudo systemctl reload nginx, conferma le risposte di curl -I http://example.com/ da un sistema esterno, quindi emetti il certificato. Successivamente:

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 ^~ nella location ACME è fondamentale: impedisce al blocco return 301 di intercettare la richiesta di challenge. Mantenere quella location sulla porta 80 garantisce il funzionamento dei rinnovi dopo che il resto del sito sarà passato esclusivamente a HTTPS.

La sintassi di HTTP/2 dipende dalla versione di nginx in uso; utilizzare le due forme in modo errato causa un errore di avvio. Ubuntu 24.04 include nginx 1.24, che richiede la configurazione inline — listen 443 ssl http2;. Debian 13 include una versione di nginx più recente, che richiede la direttiva separata http2 on;. Verifica prima nginx -v.

Configura nginx verso live/, mai verso archive/. I symlink in live/ vengono aggiornati a ogni rinnovo; utilizzare un percorso assoluto in archive/ blocca il sistema su un certificato in scadenza.

I wildcard richiedono DNS-01, e DNS-01 richiede un plugin

Un certificato wildcard (*.example.com) non può essere validato tramite HTTP-01: non esiste un singolo hostname da cui scaricare il file. DNS-01 è l'unica opzione: la proprietà viene dimostrata pubblicando un record TXT _acme-challenge.example.com. Certbot richiede le credenziali API del provider DNS per eseguire l'operazione in modo automatico; i plugin del provider servono a questo scopo. Guida completa al certificato wildcard spiega il funzionamento del record TXT e il problema del rinnovo in modalità manuale; segue la versione breve per Cloudflare.

sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

Sui sistemi apt, il percorso è invece sudo apt install python3-certbot-dns-cloudflare. Le credenziali vanno inserite in un file accessibile solo all'utente root:

# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_here

Limita il token ai soli permessi di modifica DNS per quella specifica 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'

Inserisci il wildcard tra virgolette per evitare che la shell esegua il globbing. DNS-01 risolve anche ciò che HTTP-01 non può fare: certificati per host senza porta 80 pubblica — un servizio interno, un server raggiungibile solo tramite un self-hosted WireGuard VPN su un VPS, o un pannello di amministrazione su un'interfaccia privata.

Rinnovo: i 90 giorni, il timer, il deploy hook

I certificati Let's Encrypt sono validi per 90 giorni. Certbot effettua il rinnovo quando mancano meno di 30 giorni alla scadenza. Questo garantisce una finestra di 30 giorni in cui un eventuale errore di rinnovo è un problema risolvibile e non un outage. Let's Encrypt non invia più email di avviso per la scadenza: il monitoraggio è ora a carico dell'utente.

Verifica il timer incluso nella tua installazione:

systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificates

certbot renew analizza ogni configurazione in /etc/letsencrypt/renewal/, salta gli elementi al di fuori della finestra di 30 giorni e rinnova i restanti utilizzando esattamente i flag della sessione originale. Per questo motivo la prima esecuzione è fondamentale: è l'unica che viene registrata.

Il rinnovo del file su disco non produce effetti immediati: nginx continua a servire il vecchio certificato dalla memoria finché non riceve un comando di reload. Configura 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.sh

Qualsiasi file eseguibile in renewal-hooks/deploy/ viene avviato dopo ogni rinnovo riuscito. Il flag --deploy-hook svolge la stessa funzione per un singolo certificato, memorizzando renew_hook = ... nella sua configurazione di rinnovo. certbot --nginx esegue il reload automaticamente; le configurazioni --webroot e --standalone no. La mancanza di un hook è la causa per cui un sito serve un certificato scaduto mentre certbot certificates segnala correttamente un certificato valido. Qualsiasi altro servizio che legge il certificato all'avvio richiede lo stesso hook: un'applicazione containerizzata come una installazione Nextcloud VPS con Docker, TLS e backup richiede un proprio passaggio di restart o reload configurato qui.

Test di rinnovo reale

sudo certbot renew --dry-run

Questa operazione esegue il test completo contro l'ambiente di staging di Let's Encrypt: lo stesso percorso del codice, lo stesso firewall, lo stesso DNS, senza costi di rate-limit e senza scrivere nulla su disco. Se il test ha successo oggi, avrà successo anche il rinnovo automatico tra 60 giorni, a condizione che non vengano apportate modifiche alla configurazione del sistema.

Un dry run non prova che il reload hook venga eseguito: il comportamento varia in base alla versione di Certbot. Verifica questa parte manualmente: esegui direttamente lo script del hook, conferma che systemctl reload nginx abbia successo e controlla sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.

Gli errori che incontrerai effettivamente

Could not bind to IPv4 or IPv6.--standalone perché nginx utilizza già la porta 80. Usa --nginx o --webroot, oppure ferma nginx prima dell'esecuzione. Verifica il processo che occupa la porta con sudo ss -lntp | grep ':80'.

Timeout during connect (likely firewall problem) — Let's Encrypt non riesce a raggiungere la porta 80. Controlla i passaggi esterni: sudo ufw status (apri la porta con sudo ufw allow 'Nginx Full'), il firewall del provider VPS, e infine il DNS. Testa la raggiungibilità da un server esterno: curl -sSv http://example.com/.well-known/acme-challenge/test. Un record AAAA obsoleto produce lo stesso errore.

unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404 — la porta 80 è raggiungibile, ma il token non viene servito. La richiesta è finita in un server block differente (verifica quale gestisce default_server), oppure la directory passata a -w non è quella servita da nginx. Inserisci un file in /var/www/example.com/.well-known/acme-challenge/test e scaricalo esternamente; se ricevi un errore 404, il problema non è il certificato.

**DNS problem: NXDOMAIN looking up A for example.com — il nome non si risolve pubblicamente. Potrebbe trattarsi di nuovi record non ancora propagati o di un record in una zona non gestita dal registrar.

too many certificates already issued for: example.com — un limite di frequenza (rate limit), comune durante il debugging ripetitivo. Let's Encrypt limita i certificati duplicati — lo stesso identico set di nomi — a cinque a settimana, e permette separatamente 50 nuovi certificati per dominio registrato a settimana; l'unico modo per sbloccare la situazione è attendere. Esegui il debug in modalità 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 mai emesso o rimosso con certbot delete. Commenta il server block TLS, avvia nginx, emetti il certificato e ripristina il blocco.

open() "/etc/letsencrypt/options-ssl-nginx.conf" failed — quel file viene fornito con il pacchetto plugin di nginx. Su un sistema certonly senza python3-certbot-nginx, aggiungi il plugin oppure sostituisci la riga include con le tue impostazioni ssl_protocols e ssl_ciphers.

Gestione su larga scala

Un singolo certificato può contenere fino a 100 nomi. Utilizzare un unico certbot --nginx -d a.example.com -d b.example.com ... è una pratica rischiosa: se un record DNS obsoleto fallisce la validazione, tutti gli altri nomi su quel certificato smettono di funzionare. Utilizzare certificati separati per ogni sito garantisce l'indipendenza dei singoli servizi, soluzione ideale per server che ospitano molteplici applicazioni. Superata una piccola quantità di siti, un reverse proxy compatibile con ACME è la scelta migliore: un reverse proxy Traefik che esegue più app sotto Docker Compose gestisce autonomamente richieste e rinnovi, eliminando la necessità di usare Certbot.

Effettua il backup completo di /etc/letsencryptsudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt — mantenendo intatti i symlink. Questa struttura contiene accounts/, la chiave dell'account ACME, che non può essere rigenerata con gli stessi dati. Il passaggio a un nuovo VPS si riduce a: eseguire rsync della directory con -a, installare Certbot, aggiornare i record DNS ed eseguire certbot renew --dry-run prima del passaggio definitivo.

Il timer di rinnovo non viene trasferito automaticamente se si reinstalla il sistema o si passa a una nuova versione LTS. Dopo ogni migrazione, ripristino da snapshot o aggiornamento della distribuzione, eseguire systemctl list-timers 'certbot*' e --dry-run. Saltare questo passaggio causa l'interruzione del servizio dopo 89 giorni, solitamente alle ore 3:00, su un certificato che si pensava fosse in rinnovo automatico.

Tutto questo presuppone il controllo di una macchina con IP pubblico e porta 80 aperta verso l'esterno — ovvero un VPS. Le procedure descritte sono identiche per ogni VPS.

Le stesse procedure per i certificati sono valide su Apache invece di nginx, mentre quando un certificato pubblico non è disponibile, un certificato self-signed su Ubuntu è adatto ai servizi interni.

FAQ

Devo tenere 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. Certbot non implementa TLS-ALPN-01; pertanto, un firewall che apre solo la 443 blocca sia il primo rilascio che ogni rinnovo automatico successivo. Un redirect dalla porta 80 a HTTPS è accettabile, poiché la validazione lo segue. L'unico modo per evitare completamente la porta 80 è utilizzare il metodo 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, versione sufficiente per tutte le procedure di questa guida. Riceve patch di sicurezza tramite unattended-upgrades e non richiede snapd. Scegli snap solo se necessiti immediatamente dell'ultima release o di un plugin DNS distribuito esclusivamente come snap. In ogni caso, seleziona un solo metodo: due installazioni creano due timer di rinnovo che puntano allo stesso albero /etc/letsencrypt, e il timer dimenticato causerà il fallimento del rinnovo.

Certbot può emettere un certificato wildcard per nginx?

Solo tramite DNS-01. Un wildcard come *.example.com non ha un singolo hostname da cui scaricare il file di challenge; pertanto, --nginx, --webroot e --standalone non sono utilizzabili. Installa il plugin per il tuo provider DNS, inserisci un token API con permessi limitati in un file di credenziali accessibile solo da root, ed esegui certbot certonly --dns-cloudflare -d example.com -d '*.example.com', racchiudendo il wildcard tra virgolette per evitare l'interpretazione 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 su disco finché non viene ricaricato. certbot --nginx esegue il reload automaticamente, ma le esecuzioni di --webroot e --standalone no; di conseguenza, il rinnovo può avere successo mentre il browser visualizza ancora un certificato in scadenza. Inserisci uno script eseguibile in /etc/letsencrypt/renewal-hooks/deploy/ che esegua nginx -t && systemctl reload nginx, in modo che venga attivato dopo ogni rinnovo riuscito.

certbot renew --dry-run garantisce che il rinnovo funzioni?

In gran parte. Esegue la challenge reale contro l'ambiente di staging — stesso firewall, stesso DNS, stesso percorso del codice — senza costi di rate-limit e senza scrivere nulla su disco. Un esito positivo indica che la parte di rete è corretta. Non garantisce però l'esecuzione del deploy hook. Testa questo passaggio separatamente: esegui lo script del hook manualmente e controlla sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.