SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-07

Certificato autofirmato Ubuntu 24.04 per Chrome

Crea un certificato TLS autofirmato accettato da Chrome su Ubuntu 24.04: comando openssl con SAN, configurazione nginx o Apache e trust senza usare curl -k.

Cosa stai creando

Un certificato TLS autofirmato che i browser e i client moderni accettano effettivamente, un subjectAltName corretto, permessi sicuri per la chiave, configurato in nginx o Apache, oltre all'aspetto che quasi tutte le guide ignorano: fare in modo che i client si fidino correttamente del certificato, invece di ignorare gli avvisi e incorporare curl -k negli script per sempre. Alla fine troverai anche una CA privata in cinque comandi, utile quando un servizio interno diventa sei.

Prima di tutto, la decisione: un certificato autofirmato è lo strumento corretto molto meno spesso di quanto venga utilizzato. Se il servizio è raggiungibile da Internet con un nome DNS reale, interrompi la lettura e ottieni invece un certificato Let's Encrypt gratuito con certbot su nginx oppure l'equivalente per Apache. Non costa nulla, si rinnova automaticamente e tutti i browser del mondo si fidano già di questo certificato. Un certificato autofirmato su un sito pubblico abitua gli utenti a ignorare gli avvisi di sicurezza, un comportamento peggiore dell'utilizzo di HTTP semplice.

Un certificato autofirmato è lo strumento corretto quando Internet pubblico non è coinvolto: un pannello di amministrazione associato a un indirizzo del tunnel WireGuard sul tuo VPS, un server di staging su una rete privata, il traffico tra servizi backend, un dispositivo di un home lab oppure la sostituzione del certificato segnaposto che Webmin genera autonomamente sulla porta 10000. Let's Encrypt non può emettere certificati per 10.8.0.1 o git.internal.lan; inoltre, nessuna CA pubblica inserirà in un certificato un indirizzo IP privato o un TLD inventato. Per questi nomi, la CA sei tu.

Tutte le operazioni seguenti vengono eseguite su un sistema Ubuntu 24.04 appena installato, che include OpenSSL 3.0.x (openssl version per conferma). Nulla di quanto segue richiede l'accesso a Internet: tutto funziona in un ambiente isolato.

Perché il vecchio one-liner produce certificati rifiutati da Chrome

Il comando riportato da ogni tutorial precedente al 2017 è simile al seguente:

# Do not run this — shown so you recognise it in old guides
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout selfsigned.key -out selfsigned.crt

Il comando pone una serie di domande interattive, inserisce il nome host nel campo Common Name e produce un certificato senza l'estensione subjectAltName. Questo certificato non può essere utilizzato. Chrome ha smesso di leggere il Common Name nella versione 58, nell'aprile 2017. Inoltre, RFC 2818 aveva già sconsigliato la verifica tramite CN nel 2000. Firefox, Safari, curl e Python si comportano allo stesso modo. Un certificato identifica il server tramite l'estensione SAN oppure non lo identifica affatto. Il browser lo segnala con queste parole esatte:

NET::ERR_CERT_COMMON_NAME_INVALID

This server could not prove that it is git.internal.lan; its security
certificate does not specify Subject Alternative Names.

Nessuna modifica al trust store risolve questo errore, perché il certificato non contiene effettivamente alcun nome. Se in questo momento visualizzi NET::ERR_CERT_COMMON_NAME_INVALID, il certificato non contiene un SAN, oppure contiene un SAN errato, e devi generarne uno nuovo. Per fortuna, la correzione richiede un solo comando.

Creare un certificato accettato dai browser con un solo comando

OpenSSL ha introdotto il flag -addext nella versione 1.1.1. Non servono più le configurazioni elaborate dei vecchi tutorial per inserire un SAN. Su Ubuntu 24.04:

sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 730 -noenc \
  -keyout /etc/ssl/private/git.internal.key \
  -out /etc/ssl/certs/git.internal.crt \
  -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"

Funzione dei singoli flag:

  • -x509 genera direttamente un certificato autofirmato invece di una richiesta di firma.
  • -newkey rsa:4096 genera una nuova chiave nella stessa operazione. RSA 4096 è compatibile anche con i client meno recenti. Se tutti i client che si connettono sono moderni, -newkey ec -pkeyopt ec_paramgen_curve:P-256 è più piccolo e veloce.
  • -noenc è la sintassi di OpenSSL 3.x per il vecchio -nodes: la chiave non ha una passphrase. Entrambe le sintassi funzionano. Con una chiave protetta da passphrase, nginx resta in attesa dell'input a ogni avvio. Per una chiave server è quindi necessario usare questa opzione.
  • -days 730 indica una validità di due anni. Il motivo di questo valore è spiegato nella sezione sulla scadenza.
  • -subj fornisce le risposte alle domande interattive direttamente sulla riga di comando. Il CN è ormai solo descrittivo, ma impostalo comunque sul nome principale: alcuni strumenti lo visualizzano.
  • -addext "subjectAltName=..." è il flag fondamentale. Elenca ogni nome e ogni indirizzo IP che i client utilizzeranno: voci DNS: per i nomi host (sono ammessi anche wildcard come DNS:*.internal.lan), voci IP: per gli indirizzi. Se qualcuno accederà a https://10.8.0.1, deve essere presente la voce IP:10.8.0.1. Un SAN contenente solo il nome DNS riproporrebbe il problema NET::ERR_CERT_COMMON_NAME_INVALID.

Verifica che il SAN sia stato effettivamente inserito prima di configurare altro:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltName

Output corretto:

X509v3 Subject Alternative Name:
    DNS:git.internal.lan, IP Address:10.8.0.1

Se invece viene visualizzato No extensions in certificate, il certificato non contiene alcun SAN e i browser lo rifiuteranno. Rigeneralo invece di procedere.

Proteggi la chiave

Una chiave privata leggibile da ogni utente del server non è realmente privata. In Ubuntu, /etc/ssl/private è già 710 root:ssl-cert, impedendo l'accesso occasionale, ma imposta esplicitamente i permessi sul file:

sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.key

Nginx e Apache leggono entrambi i certificati come root prima di ridurre i privilegi, quindi la modalità root:root 600 è adatta. Se la chiave viene usata da un servizio eseguito con un utente dedicato che la carica direttamente, ad esempio un'app Node, Gitea o un demone Python, chown la proprietà a quell'utente del servizio, mantenendo comunque la modalità 600. Non impostare mai la modalità 644, non conservare una copia in un repository Git e non salvarne una copia in /tmp.

Collegarlo a nginx

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name git.internal.lan;

    ssl_certificate     /etc/ssl/certs/git.internal.crt;
    ssl_certificate_key /etc/ssl/private/git.internal.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
sudo nginx -t && sudo systemctl reload nginx

nginx -t deve stampare syntax is ok e test is successful prima che il reload esegua qualsiasi operazione. Se invece stampa SSL_CTX_use_PrivateKey_file() failed ... key values mismatch, il certificato e la chiave provengono da due generazioni diverse; consultare la sezione relativa alle modalità di errore.

Collegarlo ad Apache

sudo a2enmod ssl proxy proxy_http

ssl da solo non è sufficiente: il virtual host seguente usa ProxyPass e, senza mod_proxy e mod_proxy_http, il test della configurazione termina con Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration. Salva il virtual host come /etc/apache2/sites-available/git-internal.conf:

<VirtualHost *:443>
    ServerName git.internal.lan
    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/git.internal.crt
    SSLCertificateKeyFile   /etc/ssl/private/git.internal.key

    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
sudo a2ensite git-internal
sudo apache2ctl configtest && sudo systemctl reload apache2

configtest dovrebbe rispondere a Syntax OK. Ora esegui il test da una macchina client:

curl -v https://git.internal.lan/

e otterrai un errore:

curl: (60) SSL certificate problem: self-signed certificate

Non è un bug. È TLS che funziona: curl non ha mai visto il tuo certificato e rifiuta di comunicare con un server che non può autenticare. La sezione seguente contiene la soluzione corretta e non è quella adottata in questo momento da metà di Internet.

Fare in modo che i client si fidino del certificato ed evitare gli anti-pattern

Prima analizziamo le correzioni errate, chiamandole con il loro nome. curl -k (o --insecure) inserito direttamente in uno script, verify=False nelle richieste Python, NODE_TLS_REJECT_UNAUTHORIZED=0 in Node: nessuna di queste opzioni rende attendibile il certificato. Disattivano la verifica del certificato, quindi il client comunica senza problemi con qualsiasi server che presenti un certificato qualsiasi, incluso quello inserito da un attaccante nel percorso di rete. Si mantiene il sovraccarico di TLS, ma si perde l'autenticazione, che è il motivo per cui TLS viene utilizzato. Peggio ancora, questi flag si propagano: vengono copiati in un job cron, poi in uno script di deploy e infine nel codice di produzione, finché nessuno ricorda più quali connessioni dovevano essere temporanee. Se un verify=False resta presente oltre la sessione di debug che lo ha originato, il progetto è errato.

La correzione corretta consiste nell'insegnare al sistema operativo di ogni client che questo certificato è una root attendibile. Sui client Ubuntu e Debian:

sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificates

La riga importante nell'output (dopo di essa compare un blocco Running hooks in /etc/ca-certificates/update.d...):

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

Queste righe nascondono due problemi comuni. Il file deve terminare con .crt; un'estensione .pem viene ignorata senza messaggi e si ottiene 0 added senza alcun messaggio di errore. Inoltre, il contenuto deve essere in formato PEM e il file deve iniziare con -----BEGIN CERTIFICATE-----; prima convertire un file binario DER con openssl x509 -inform der -in file.der -out file.crt. Aggiungere direttamente il certificato self-signed come root funziona perché un certificato self-signed è la propria root.

Dopo questa operazione, curl, wget, git, apt e qualsiasi altro programma che utilizzi OpenSSL con il bundle di sistema considerano attendibile il server senza alcun flag. Alcuni client utilizzano un proprio trust store e richiedono una configurazione separata:

  • Chrome/Chromium su Linux legge un database NSS, non il trust store di sistema: sudo apt install libnss3-tools, quindi certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt per ogni utente.
  • Firefox utilizza un proprio store: Impostazioni → Privacy e sicurezza → Certificati → Importa, oppure impostare security.enterprise_roots.enabled su true in about:config per fare in modo che legga lo store di sistema.
  • Python requests include un proprio bundle di CA (certifi) e ignora lo store di sistema: passare verify="/usr/local/share/ca-certificates/git.internal.crt" oppure esportare REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt.
  • Node.js: esportare NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt.

Sui client Windows, fare doppio clic su .crt e installarlo in Autorità di certificazione radice attendibili; su macOS, aggiungerlo al portachiavi System tramite Accesso Portachiavi e contrassegnarlo come Considera sempre attendibile.

Una root per molti servizi: una piccola CA privata

La gestione della fiducia per singolo certificato non è scalabile: sei servizi per quattro macchine client significano ventiquattro installazioni del certificato di fiducia, e ogni nuovo servizio ne aggiunge altre. La soluzione è una CA privata: i client si fidano di una sola root e con questa si firma il certificato di ogni servizio.

L'opzione più semplice è mkcert, disponibile nei repository di Ubuntu 24.04 e in grado di gestire gli archivi NSS di Chrome e Firefox, che update-ca-certificates non gestisce:

sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1

mkcert -install crea una root e la registra in tutti gli archivi di fiducia presenti sulla macchina; il terzo comando genera git.internal.lan+2.pem e git.internal.lan+2-key.pem, pronti da inserire negli snippet di nginx o Apache riportati sopra. Questo strumento presuppone una macchina di sviluppo: la chiave della root resta sulla macchina che ha eseguito -install. È quindi perfetto per un laptop di sviluppo, ma non è adatto a una flotta di server.

Per i server, OpenSSL esegue tutte le operazioni della CA con cinque comandi:

openssl genrsa -out lab-ca.key 4096
openssl req -x509 -new -key lab-ca.key -sha256 -days 3650 \
  -out lab-ca.crt -subj "/CN=Lab Internal CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out git.key 2048
openssl req -new -key git.key -out git.csr -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"
openssl x509 -req -in git.csr -CA lab-ca.crt -CAkey lab-ca.key \
  -CAcreateserial -days 730 -sha256 -copy_extensions copy -out git.crt

Il problema è nell'ultimo comando: per impostazione predefinita, openssl x509 -req rimuove tutte le estensioni dalla CSR, incluso il SAN aggiunto con attenzione. -copy_extensions copy, un'opzione di OpenSSL 3.x e quindi disponibile su 24.04, trasferisce anche queste estensioni; se la ometti, il certificato firmato non contiene il SAN e Chrome mostra di nuovo NET::ERR_CERT_COMMON_NAME_INVALID. Verifica con lo stesso controllo openssl x509 -noout -ext subjectAltName usato in precedenza.

Distribuisci lab-ca.crt ai client seguendo i passaggi precedenti per l'archivio di fiducia, una sola volta per ogni macchina. Proteggi lab-ca.key come una credenziale critica: imposta la modalità 600 e, idealmente, conservala su una macchina che non sia uno dei server per cui firma i certificati, perché chiunque ne entri in possesso può creare un certificato per qualsiasi nome riconosciuto dai client.

Scadenza e rotazione

La durata dei certificati delle CA pubbliche si sta riducendo rapidamente. Il CA/Browser Forum ha limitato a 200 giorni, a partire da marzo 2026, la durata dei certificati appena emessi e considerati attendibili pubblicamente, rispetto ai precedenti 398 giorni. Il limite scenderà a 100 giorni nel 2027 e a 47 giorni entro marzo 2029. Queste regole si applicano però alle CA attendibili pubblicamente. La CA privata non è soggetta a questi limiti e i browser non li applicano alle root installate manualmente. Esiste tuttavia un limite concreto: le piattaforme Apple rifiutano qualsiasi certificato server TLS valido per più di 825 giorni, indipendentemente dall’autorità che lo ha emesso. Se iPhone o Mac devono connettersi, mantieni quindi i certificati leaf entro due anni. -days 730 rispetta questo limite ovunque; una root valida dieci anni con certificati leaf validi due anni è una configurazione interna pratica.

I certificati con validità lunga presentano un solo tipo di problema: smettono di funzionare in modo silenzioso e simultaneo, in una data che nessuno ricorda di avere scelto. Controlla i certificati disponibili:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddate

Inserisci il rinnovo in un calendario reale oppure configura cron perché invii un promemoria 30 giorni prima. openssl x509 -checkend 2592000 -in cert.crt restituisce un codice diverso da zero quando la scadenza rientra nel numero di secondi indicato. Se usi già Uptime Kuma per il monitoraggio dello stato, i relativi monitor HTTPS segnalano gratuitamente l'approssimarsi della scadenza del certificato.

La rotazione con una CA privata è semplice: esegui di nuovo i comandi per creare la CSR e firmarla, sostituisci i file e ricarica il web server. La root non è cambiata, quindi nessun client rileva la modifica.

Modalità di errore e stringhe visualizzate

NET::ERR_CERT_AUTHORITY_INVALID, è lo stato previsto prima di installare la relazione di trust, non un difetto del certificato. Se persiste dopo l'installazione della CA root: su Linux, Chrome legge NSS invece dell'archivio di sistema; vedere il passaggio certutil. In alternativa, il file copiato non terminava con .crt e update-ca-certificates restituiva 0 added. È anche possibile che il server presenti un certificato diverso da quello considerato attendibile: confrontare le impronte con openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256.

NET::ERR_CERT_COMMON_NAME_INVALID, il certificato non contiene alcun SAN oppure il SAN non include il nome visualizzato nella barra degli indirizzi. Il caso tipico è che il SAN elenchi DNS:git.internal.lan, mentre l'utente abbia aperto https://10.8.0.1. Le modifiche all'archivio di trust non possono risolvere questo problema; occorre emettere nuovamente il certificato includendo la voce mancante.

curl: (60) SSL certificate problem: self-signed certificate, curl non considera attendibile il certificato. La variante self-signed certificate in certificate chain indica la stessa condizione per un certificato firmato dalla CA privata. Correzione temporanea: curl --cacert lab-ca.crt https://...; correzione permanente: configurare l'archivio di trust. Non usare -k.

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (oppure Expecting: CERTIFICATE REQUEST o no start line): formato PEM errato. È stato fornito a OpenSSL un file del tipo sbagliato: una chiave o una CSR quando era atteso un certificato, oppure un file binario DER quando era atteso un file PEM. head -1 filename mostra il contenuto effettivo del file; un certificato inizia con -----BEGIN CERTIFICATE-----. Per i file DER, convertirli con openssl x509 -inform der -in file.der -out file.crt.

nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch): certificato e chiave non corrispondono, in genere perché il comando di generazione è stato eseguito due volte e i file sono stati combinati in modo errato. Verificare openssl x509 -in git.internal.crt -noout -pubkey | sha256sum rispetto a openssl pkey -in git.internal.key -pubout | sha256sum; hash uguali indicano una coppia corrispondente. Se differiscono, rigenerare entrambi insieme.

FAQ

Perché Chrome continua a indicare "Non sicuro" dopo la creazione di un certificato autofirmato?

Se l'errore è NET::ERR_CERT_AUTHORITY_INVALID, il certificato è corretto: Chrome non ha ancora motivo di considerarlo attendibile. Installalo, oppure installa la root della tua CA privata, nell'archivio di attendibilità del client. Ricorda che su Linux Chrome usa il database NSS tramite certutil, non l'archivio di sistema. Se l'errore è NET::ERR_CERT_COMMON_NAME_INVALID, il certificato non contiene un Subject Alternative Name corrispondente all'URL e deve essere emesso nuovamente con -addext "subjectAltName=...".

Come posso fare in modo che curl consideri attendibile un certificato autofirmato senza usare -k?

Copia il certificato, in formato PEM e con estensione .crt, in /usr/local/share/ca-certificates/ ed esegui sudo update-ca-certificates. L'output deve indicare 1 added. Da quel momento curl lo verifica come qualsiasi certificato pubblico. Per una singola richiesta senza modificare il sistema, curl --cacert /path/to/cert.crt esegue la verifica usando soltanto quel file; -k disabilita completamente la verifica e non deve comparire negli script.

Per quanto tempo può essere valido un certificato autofirmato?

Tecnicamente per tutto il tempo necessario. I limiti del CA/Browser Forum, 200 giorni oggi e 47 entro il 2029, si applicano alle CA pubblicamente attendibili, non alla fiducia privata. In pratica, limita a 825 giorni la validità dei certificati server, perché i dispositivi Apple rifiutano certificati più lunghi indipendentemente dall'emittente. Una root privata con validità di dieci anni e certificati leaf di due anni (-days 730) è un'impostazione ragionevole. Pianifica comunque il rinnovo, perché un certificato interno scaduto può interrompere silenziosamente tutti i servizi in una data che nessuno ricorda.

Devo usare un certificato autofirmato o Let's Encrypt?

Se il servizio ha un nome DNS pubblico ed è raggiungibile da Internet, usa sempre Let's Encrypt: è gratuito, automatizzato e già considerato attendibile da ogni client. Un certificato autofirmato, o una CA privata, serve per i casi in cui Let's Encrypt non può emettere certificati: IP privati, nomi host accessibili solo internamente come .lan, reti isolate e servizi deliberatamente nascosti dietro una VPN. La scelta dipende dalla raggiungibilità e dalla denominazione, non dal livello di sicurezza. La crittografia è identica.

Perché il mio certificato viene rifiutato anche dopo averlo aggiunto a /usr/local/share/ca-certificates?

Controlla tre aspetti. Il file deve terminare con .crt. Un'estensione .pem viene ignorata senza messaggi e update-ca-certificates restituisce 0 added. Il contenuto deve essere testo PEM che inizia con -----BEGIN CERTIFICATE-----, non un file binario DER. Inoltre, l'applicazione deve usare effettivamente l'archivio di sistema. Chrome su Linux, Firefox, Python requests, Node.js e Java mantengono ciascuno un archivio di attendibilità privato e richiedono l'aggiunta separata del certificato.