Il TTFB non dipende solo dall’applicazione: quanto costa davvero l’handshake TLS
Quando un sito HTTPS risponde lentamente, il riflesso condizionato di ogni sistemista è guardare al backend: query lente, cache assente, un’applicazione PHP o .NET che non scala. Ma prima ancora che la richiesta arrivi al codice applicativo, il client e il server devono completare un handshake TLS, negoziare un protocollo, verificare un certificato ed eventualmente riprendere una sessione precedente. Ognuno di questi passaggi ha un costo in millisecondi, e su siti ad alto traffico o con molte connessioni “fredde” (nuovi visitatori, CDN edge miss, mobile su rete instabile) quel costo si somma in modo tutt’altro che trascurabile.
La buona notizia è che gran parte di questo overhead è governato da una manciata di direttive Nginx che, se lasciate ai valori di default, non sono ottimizzate per la latenza. In questo articolo vediamo come intervenire in modo mirato su HTTP/2, HTTP/3, cache di sessione TLS, versioni del protocollo, cipher suite e OCSP stapling, con un occhio a cosa è davvero cambiato nel panorama TLS nel 2026 rispetto a configurazioni “storiche” che circolano ancora in molte guide.
HTTP/2 e HTTP/3: come attivarli correttamente
Su Nginx recente (1.25.1 e successivi) il supporto HTTP/2 non si attiva più come parametro del blocco listen, ma con una direttiva dedicata. La sintassi legacy listen 443 ssl http2; è deprecata e in alcune build genera un warning in fase di reload:
listen 443 ssl;
http2 on;
Per chi vuole spingersi oltre, HTTP/3 (basato su QUIC, quindi su UDP anziché TCP) è supportato nativamente da Nginx 1.25.0 in poi:
listen 443 ssl;
listen [::]:443 ssl;
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';
L’header Alt-Svc non è opzionale: senza di esso il browser non ha modo di sapere che il server parla anche HTTP/3, e continuerà a usare HTTP/2 anche se QUIC è configurato correttamente sul lato server. Va inoltre ricordato che molte reti aziendali e alcuni provider filtrano il traffico UDP sulla porta 443, quindi HTTP/3 va sempre trattato come miglioramento progressivo e non come sostituto esclusivo di HTTP/2.
Session cache e session ticket: evitare handshake completi
Ogni handshake TLS completo comporta uno scambio di chiavi asimmetrico, che è l’operazione più costosa dell’intero processo. La cache di sessione permette a un client che si riconnette di saltare questo passaggio:
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;
Una cache condivisa da 10 MB memorizza circa 4.000 sessioni: per un sito a traffico medio è più che sufficiente, ma su cluster con più worker o più nodi Nginx dietro un load balancer va dimensionata in base al numero di client unici attesi nella finestra di ssl_session_timeout. Un dettaglio spesso trascurato: da Nginx 1.23.2 la gestione delle chiavi dei session ticket è stata migliorata, ma se si opera un’infrastruttura con più server dietro lo stesso load balancer, le chiavi di ticket vanno sincronizzate tra i nodi (tipicamente con ssl_session_ticket_key e un file condiviso via configuration management), altrimenti il beneficio della ripresa di sessione si perde ogni volta che il client finisce su un nodo diverso da quello dell’handshake iniziale.
Versioni del protocollo: perché TLS 1.3 non è solo “più sicuro”
La direttiva sulle versioni supportate resta relativamente semplice:
ssl_protocols TLSv1.2 TLSv1.3;
Il punto tecnico interessante è che TLS 1.3 non porta solo benefici di sicurezza (rimozione di cipher obsoleti, forward secrecy obbligatoria), ma anche di performance pura: riduce l’handshake a un singolo round trip contro i due richiesti da TLS 1.2, e supporta la ripresa di sessione tramite un meccanismo (0-RTT/PSK) che rende le riconnessioni ancora più rapide. Per un client con RTT di rete di 80-100ms verso il server — scenario comune per utenti mobile o geograficamente distanti dal datacenter — il risparmio di un intero round trip si traduce direttamente in TTFB più basso.
Mantenere TLS 1.2 come fallback resta comunque prudente per compatibilità con client legacy (alcune librerie HTTP embedded, dispositivi IoT, versioni datate di Android). Limitarsi al solo TLS 1.3 va considerato solo per ambienti controllati, come API interne o servizi machine-to-machine dove si conoscono con certezza i client.
Cipher suite: meno è meglio, con TLS 1.3
Su TLS 1.2 la scelta delle cipher suite ha ancora impatto pratico:
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';
Da notare l’impostazione ssl_prefer_server_ciphers off: con TLS 1.3 e i client moderni, lasciare che sia il client a scegliere la cipher (in base a cosa è accelerato via hardware sul suo dispositivo, ad esempio AES-NI vs ChaCha20 su mobile) è generalmente più efficiente che imporre l’ordine lato server.
Con TLS 1.3, però, le cipher suite sono fissate dal protocollo stesso: mantenere una lunga stringa ssl_ciphers personalizzata e una direttiva ssl_ecdh_curve manuale non porta quasi nessun beneficio aggiuntivo per le connessioni che negoziano TLS 1.3, e serve solo a coprire il fallback su TLS 1.2. Molte configurazioni “best practice” che circolano online sono più complesse del necessario proprio perché non tengono conto di questo aspetto.
OCSP stapling: un capitolo da riscrivere nel 2026
Fino a poco tempo fa, l’OCSP stapling era una delle ottimizzazioni TLS più raccomandate: invece di far verificare al browser lo stato di revoca del certificato con una richiesta separata al CA, il server allega (“stapla”) una risposta OCSP firmata direttamente durante l’handshake, risparmiando un round trip:
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /path/to/full_chain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
Questo è un punto su cui molti sistemisti non sono ancora allineati: Let’s Encrypt ha dismesso il supporto OCSP nell’agosto 2025, passando esclusivamente a CRL (Certificate Revocation List) a corto ciclo di vita per la verifica di revoca. Se i certificati del vostro dominio provengono da Let’s Encrypt, le direttive sopra sono di fatto inerti: Nginx proverà a fare stapling ma non otterrà risposte valide, e conviene rimuoverle per tenere la configurazione pulita e i log privi di errori di risoluzione OCSP inutili. Se invece usate un CA diverso che continua a supportare OCSP, le direttive restano valide e utili.
Buffer TLS: un’ottimizzazione minore ma misurabile
ssl_buffer_size 4k;
Il valore di default di Nginx per il buffer di invio TLS è 16k, pensato per massimizzare il throughput su trasferimenti di grandi dimensioni. Per il traffico HTTP tipico — pagine HTML, chiamate API, asset di dimensioni moderate — un buffer più piccolo (4k) riduce la quantità di dati che devono essere cifrati e trasmessi prima che il client possa iniziare a processare la risposta, con un guadagno tipico nell’ordine di 30-50ms sul TTFB. È un’ottimizzazione minore rispetto a HTTP/2 o al session caching, ma essendo a costo zero (nessun trade-off di sicurezza) vale la pena applicarla su siti dove il TTFB è una metrica critica.
Configurazione completa di riferimento
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/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_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets on;
ssl_buffer_size 4k;
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always;
add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always;
}
Da personalizzare rimuovendo il blocco OCSP se il certificato proviene da Let’s Encrypt, e aggiungendo i blocchi listen ... quic reuseport; e l’header Alt-Svc se si vuole abilitare anche HTTP/3.
Cosa aspettarsi (e cosa no) da questa ottimizzazione
Vale la pena essere chiari sui limiti: il tuning TLS non risolve un’applicazione lenta. Se il backend impiega 800ms per generare una risposta, risparmiare 50ms sull’handshake è un’ottimizzazione marginale rispetto al problema reale. Dove il tuning TLS fa davvero la differenza è nel ridurre l’overhead fisso presente su ogni richiesta HTTPS, in particolare per le connessioni “fredde” e per utenti con RTT di rete elevato — scenari in cui il costo dell’handshake può facilmente superare il tempo di elaborazione lato server. Caching applicativo, ottimizzazione del backend e CDN restano le leve principali per le performance complessive, ma un livello TLS ben configurato è la base su cui tutte le altre ottimizzazioni si appoggiano, ed è spesso trascurata proprio perché “funziona già” con i valori di default.
Articolo ispirato e approfondito a partire da: Nginx tuning tips: HTTPS/TLS – Turbocharge TTFB/Latency, LinuxBlog.io.