Attivare un certificato SSL gratis su Nginx con Let’s Encrypt richiede meno di dieci minuti, ma la differenza tra una configurazione “che funziona” e una configurazione solida sta nei dettagli: il redirect corretto da HTTP a HTTPS, il rinnovo automatico realmente testato e gli header di sicurezza giusti.
In questa guida operativa trovi l’intero percorso, dall’installazione di Certbot fino al voto A+ su SSL Labs, con i comandi pronti da copiare e una sezione dedicata alla risoluzione degli errori più comuni (in primis la challenge fallita sulla porta 80). Come lettura successiva va bene Certificato SSL Let’s Encrypt: cos’è e come funziona.
Cosa ti serve prima di iniziare
- Un server Linux (Ubuntu 24.04 LTS, Debian 12/13, Rocky Linux 9 o derivate) con accesso
rootosudo. - Nginx già installato e funzionante (
nginx -vper verificare). - Un dominio valido con record DNS
A(eAAAAse usi IPv6) che punta all’IP pubblico del server. - Le porte 80 e 443 aperte su firewall e security group del provider.
- Un indirizzo email valido per gli avvisi tecnici dell’account ACME.
Verifica rapida del DNS prima di lanciare qualsiasi comando:
dig +short esempio.it
dig +short www.esempio.it
curl -I http://esempio.it
Se l’IP restituito non è quello del tuo server, fermati qui: la validazione HTTP-01 fallirà in ogni caso.

Let’s Encrypt e Certbot: come funzionano davvero
Let’s Encrypt è una Certificate Authority gratuita e automatizzata gestita dalla non-profit ISRG. Rilascia certificati DV (Domain Validation) attraverso il protocollo ACME: il client dimostra il controllo del dominio e la CA firma il certificato, senza intervento umano e senza costi.
Certbot è il client ACME ufficiale, mantenuto dalla EFF. Esistono alternative valide (acme.sh, lego, Caddy integrato), ma Certbot resta la scelta più documentata e con il plugin Nginx più maturo.
I tre metodi di validazione
| Challenge | Come funziona | Quando usarla |
|---|---|---|
| HTTP-01 | File temporaneo servito su /.well-known/acme-challenge/ sulla porta 80 |
Caso standard, sito pubblico raggiungibile |
| DNS-01 | Record TXT _acme-challenge nella zona DNS |
Certificati wildcard, server non esposti, LAN interne |
| TLS-ALPN-01 | Handshake TLS dedicato sulla porta 443 | Porta 80 bloccata dal provider |
Passo 1: installare Certbot su Nginx
Metodo consigliato: snap (sempre aggiornato)
sudo apt update
sudo apt install -y snapd
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
certbot --version
Metodo alternativo: pacchetti di sistema
Su Debian/Ubuntu:
sudo apt install -y certbot python3-certbot-nginx
Su Rocky Linux / AlmaLinux:
sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx
Nota: i pacchetti distro sono spesso indietro di qualche versione. Se devi usare funzionalità recenti (profili di certificato, nuovi plugin DNS), lo snap è la strada più sicura.

Passo 2: preparare il server block Nginx
Certbot deve trovare un server_name corrispondente al dominio. Crea il file /etc/nginx/sites-available/esempio.it:
server {
listen 80;
listen [::]:80;
server_name esempio.it www.esempio.it;
root /var/www/esempio.it/public;
index index.html index.php;
location /.well-known/acme-challenge/ {
root /var/www/html;
allow all;
}
location / {
try_files $uri $uri/ =404;
}
}
Attiva e ricarica:
sudo ln -s /etc/nginx/sites-available/esempio.it /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Apri le porte sul firewall:
# UFW
sudo ufw allow 'Nginx Full'
# firewalld
sudo firewall-cmd --permanent --add-service={http,https}
sudo firewall-cmd --reload
Passo 3: emettere il certificato SSL gratuito
Modalità automatica (plugin Nginx)
Certbot modifica da solo la configurazione e inserisce il redirect:
sudo certbot --nginx -d esempio.it -d www.esempio.it \
--email [email protected] --agree-tos --no-eff-email --redirect
Più domini nello stesso certificato (SAN)
Un singolo certificato può coprire fino a 100 nomi host. Basta ripetere il flag -d:
sudo certbot --nginx \
-d esempio.it -d www.esempio.it \
-d shop.esempio.it -d api.esempio.it \
--agree-tos --no-eff-email --email [email protected]
Tutti i domini devono puntare allo stesso server e risultare validabili, altrimenti l’intera emissione fallisce.
Modalità webroot (zero modifiche automatiche)
Se preferisci mantenere il pieno controllo del file di configurazione, usa il plugin webroot: Certbot scrive solo il file di challenge e non tocca Nginx. Questo tipo di lavoro non crea problemi a uno studio di web design.
sudo mkdir -p /var/www/html/.well-known/acme-challenge
sudo certbot certonly --webroot -w /var/www/html \
-d esempio.it -d www.esempio.it
Certificato wildcard con DNS-01
sudo certbot certonly --manual --preferred-challenges dns \
-d esempio.it -d "*.esempio.it"
Per l’automazione completa esistono plugin dedicati ai principali provider DNS (Cloudflare, Route53, OVH, DigitalOcean). Esempio con Cloudflare:
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d esempio.it -d "*.esempio.it"
I file generati si trovano in /etc/letsencrypt/live/esempio.it/:
fullchain.pem: certificato + catena intermedia (è quello che serve a Nginx)privkey.pem: chiave privatacert.pemechain.pem: componenti separate, raramente necessarie
Passo 4: configurazione Nginx completa con redirect HTTP → HTTPS
Questa è la configurazione che usiamo in produzione, pensata per ottenere un punteggio A+ su SSL Labs:
# --- Redirect HTTP verso HTTPS ---
server {
listen 80;
listen [::]:80;
server_name esempio.it www.esempio.it;
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
# --- Server HTTPS ---
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name esempio.it www.esempio.it;
root /var/www/esempio.it/public;
index index.php index.html;
ssl_certificate /etc/letsencrypt/live/esempio.it/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/esempio.it/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
Applica e verifica:
sudo nginx -t && sudo systemctl reload nginx
Il dettaglio che quasi nessuna guida aggiorna: OCSP stapling
Molti tutorial ancora in circolazione consigliano di attivare ssl_stapling on;. Let’s Encrypt ha dismesso il servizio OCSP, quindi quelle direttive oggi generano solo warning nei log di Nginx senza alcun beneficio. Se le trovi nelle tue configurazioni, rimuovile:
# DA RIMUOVERE sui certificati Let's Encrypt
# ssl_stapling on;
# ssl_stapling_verify on;
# resolver 1.1.1.1 8.8.8.8 valid=300s;
La revoca viene oggi gestita tramite CRL lato browser (meccanismi come CRLite), non più tramite risposta OCSP allegata all’handshake.
HSTS preload: attenzione
Aggiungi il flag preload all’header HSTS solo quando sei certo che tutti i sottodomini funzioneranno in HTTPS per anni. L’inserimento nella preload list dei browser è difficile da annullare.

Passo 5: rinnovo automatico (cron o systemd timer)
I certificati Let’s Encrypt durano 90 giorni e il rinnovo parte automaticamente quando mancano meno di 30 giorni alla scadenza. Dal 2025 Let’s Encrypt ha inoltre smesso di inviare le email di avviso scadenza: l’automazione non è opzionale.
Verifica prima se il timer esiste già
Le installazioni via snap e via apt creano in autonomia un systemd timer:
systemctl list-timers | grep certbot
sudo systemctl status snap.certbot.renew.timer
Se il timer è attivo, non aggiungere un cron job: duplicheresti i tentativi inutilmente.
Test a secco (obbligatorio)
sudo certbot renew --dry-run
Un output con Congratulations, all simulated renewals succeeded conferma che il rinnovo funzionerà davvero.
Cron job manuale
Se il tuo sistema non usa systemd o preferisci cron:
sudo crontab -e
Aggiungi:
17 3,15 * * * /usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
Perché due esecuzioni al giorno a un minuto “strano”: Let’s Encrypt chiede esplicitamente di randomizzare l’orario per non sovraccaricare i server ACME alle ore tonde.
Deploy hook vs post hook
| Hook | Quando viene eseguito | Uso tipico |
|---|---|---|
--pre-hook |
Prima di ogni tentativo | Fermare un servizio in modalità standalone |
--deploy-hook |
Solo se il certificato è stato rinnovato | Reload di Nginx, copia su altri nodi |
--post-hook |
Dopo ogni tentativo, anche senza rinnovo | Riavviare servizi fermati nel pre-hook |
Per rendere permanente l’hook senza dipendere dal cron, crea /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh:
#!/bin/bash
/usr/bin/systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Monitoraggio della scadenza
sudo certbot certificates
echo | openssl s_client -connect esempio.it:443 -servername esempio.it 2>/dev/null \
| openssl x509 -noout -dates
Passo 6: verificare la configurazione con SSL Labs
Apri https://www.ssllabs.com/ssltest/ e inserisci il dominio. Spunta “Do not show the results on the boards” se non vuoi che il test resti pubblico. Cosa controllare:
- Certificate: catena completa, nessun “Chain issues: incomplete”. Se compare, hai puntato
cert.peminvece difullchain.pem. - Protocol Support: solo TLS 1.2 e TLS 1.3 attivi.
- Key Exchange e Cipher Strength: entrambi al 90 o 100%.
- HSTS: deve risultare Yes con max-age di almeno 6 mesi.
Controlli complementari da riga di comando:
# Catena e protocolli
nmap --script ssl-enum-ciphers -p 443 esempio.it
# Redirect 301 corretto
curl -sI http://esempio.it | head -n 5
# Scansione completa
docker run --rm -ti drwetter/testssl.sh https://esempio.it
Non dimenticare la parte applicativa: mixed content. Cerca riferimenti http:// hardcoded in temi, CSS e database, altrimenti il lucchetto resterà incompleto anche con un certificato perfetto.
Risoluzione degli errori più comuni
| Errore | Causa | Soluzione |
|---|---|---|
Timeout during connect (likely firewall problem) |
Porta 80 chiusa o filtrata | Apri la 80 su firewall locale e sul pannello cloud; verifica con nc -zv IP 80 dall’esterno |
404 Not Found sulla challenge |
Webroot errato o location che blocca i file nascosti | Allinea -w alla root reale ed escludi /.well-known/ dalle regole location ~ /\. |
DNS problem: NXDOMAIN |
Record A mancante o non propagato | Controlla con dig e attendi la propagazione prima di riprovare |
Invalid response ... "<!DOCTYPE html>" |
Un redirect o una SPA intercetta tutte le richieste | Inserisci il blocco /.well-known/acme-challenge/ prima del redirect verso HTTPS |
Could not bind to IPv4 or IPv6 (standalone) |
Nginx occupa già la porta 80 | Usa --nginx o --webroot, oppure ferma Nginx con i pre/post hook |
too many certificates already issued |
Rate limit settimanale superato | Attendi la finestra oraria e usa --dry-run o l’ambiente di staging per i test |
Browser: NET::ERR_CERT_AUTHORITY_INVALID |
Catena intermedia assente | Punta a fullchain.pem, mai a cert.pem |
| Redirect loop infinito | CDN o proxy in modalità Flexible SSL | Imposta la modalità Full (strict) e gestisci X-Forwarded-Proto |
Il caso più frequente: challenge fallita sulla porta 80
Nove volte su dieci la causa è una di queste. Procedi in ordine:
- Testa la raggiungibilità dall’esterno (non dal server stesso):
curl -I http://esempio.it/.well-known/acme-challenge/test - Crea un file di prova e verifica che sia servito:
echo ok | sudo tee /var/www/html/.well-known/acme-challenge/test - Controlla i log in tempo reale:
sudo tail -f /var/log/nginx/access.logdurante l’esecuzione di Certbot. Se non vedi la richiesta dei server ACME, il traffico non arriva al server e il problema è di rete, non di Nginx. - Verifica i permessi:
sudo chown -R www-data:www-data /var/www/html/.well-known - Se il provider blocca definitivamente la porta 80, passa a DNS-01 o a TLS-ALPN-01.
Per i test ripetuti usa sempre l’ambiente di staging, che non consuma i rate limit:
sudo certbot --nginx -d esempio.it --dry-run
# oppure
sudo certbot certonly --staging --webroot -w /var/www/html -d esempio.it

Casi particolari
Nginx come reverse proxy
Quando Nginx fa da frontend verso un’applicazione su altra porta, ricorda di passare lo schema corretto al backend, altrimenti otterrai loop di redirect:
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
Server in LAN o non esposti su internet
Per servizi interni (homelab, ambienti di staging, Nginx Proxy Manager) l’unica strada è la challenge DNS-01 con un dominio pubblico delegato a un provider DNS supportato: il certificato viene emesso senza che il server sia raggiungibile dall’esterno. Su Let’s Encrypt completa bene quanto sopra.
Revocare o eliminare un certificato
sudo certbot revoke --cert-path /etc/letsencrypt/live/esempio.it/cert.pem
sudo certbot delete --cert-name esempio.it
Checklist finale
- ✅
https://esempio.itrisponde con lucchetto valido - ✅
http://esempio.itrestituisce un 301 verso HTTPS - ✅ Voto A o A+ su SSL Labs, senza chain issues
- ✅
certbot renew --dry-runcompletato con successo - ✅ Timer systemd attivo oppure cron job configurato con deploy hook
- ✅ Nessun mixed content nella console del browser
- ✅ Sitemap, canonical e Search Console aggiornati alla versione HTTPS
FAQ
Come posso ottenere un certificato SSL gratuito?
Il modo più diffuso è Let’s Encrypt tramite Certbot: installi il client sul server, lanci certbot --nginx -d tuodominio.it e il certificato viene emesso e installato in pochi secondi. Altre CA gratuite sono ZeroSSL e Buypass Go SSL, utilizzabili con gli stessi client ACME.
Quanto dura un certificato Let’s Encrypt?
La durata standard è di 90 giorni. Il rinnovo automatico scatta quando mancano 30 giorni alla scadenza. Let’s Encrypt ha introdotto anche profili con certificati a vita molto breve (circa 6 giorni), pensati per infrastrutture completamente automatizzate.
Quanto costa un certificato SSL?
Con Let’s Encrypt il costo è zero, anche per i wildcard. I certificati commerciali DV partono da poche decine di euro l’anno, mentre OV ed EV possono superare i 200 euro: hanno senso solo quando serve validazione dell’organizzazione o garanzie contrattuali, non per un miglior livello di cifratura, che è identico.
Let’s Encrypt è adatto a un sito e-commerce?
Sì. La cifratura è la stessa dei certificati a pagamento e i browser non fanno alcuna distinzione visiva. Valuta un certificato OV solo se il tuo settore o i tuoi clienti richiedono esplicitamente la verifica dell’identità aziendale.
Devo rigenerare il certificato se aggiungo un sottodominio?
Sì, va riemesso includendo tutti i nomi: sudo certbot --nginx --cert-name esempio.it -d esempio.it -d www.esempio.it -d nuovo.esempio.it. In alternativa, un certificato wildcard copre tutti i sottodomini di primo livello. virtua.cloud ha trattato il tema a lungo.
Il passaggio a HTTPS influisce sulla SEO?
HTTPS è un fattore di ranking confermato da Google. L’importante è gestire la migrazione correttamente: redirect 301 permanenti, aggiornamento dei link interni e dei canonical, nuova property HTTPS in Search Console e sitemap aggiornata.
Cosa succede se dimentico di rinnovare?
Il certificato scade e i visitatori vedranno un avviso di sicurezza a tutto schermo, con conseguente crollo del traffico. Poiché gli avvisi email di scadenza non vengono più inviati, è indispensabile verificare periodicamente lo stato del timer di rinnovo o impostare un monitoraggio esterno sulla data di scadenza.
Hai bisogno di supporto nella configurazione del tuo server, nella migrazione a HTTPS o nell’hardening della tua infrastruttura web? Il team di Alede Design è a disposizione per analizzare il tuo caso specifico.