
Il TTFB, Time to First Byte, misura quanto passa da quando il browser comincia a chiedere una pagina a quando arriva il primo byte della risposta. Su un sito WordPress dentro quel tempo ci sono i redirect, la rete e il lavoro del server, e ogni strumento che lo misura ne guarda una parte diversa. Per questo PageSpeed Insights, i DevTools di Chrome, curl e la Salute del sito possono dare quattro numeri diversi per la stessa pagina, senza che nessuno sia sbagliato.
Ridurlo vuol dire capire prima quale parte pesa. Questa guida parte dalla misura e poi segue la richiesta nell’ordine in cui incontra le sue fasi: i redirect, la connessione, la cache di pagina, il tempo di PHP quando la cache non c’è, il server sotto carico e, per ultimo, l’hosting.
Una nota sulle fonti. Definizioni e soglie vengono dalla documentazione di Google su web.dev e Chrome for Developers, dal codice di WordPress 7.1.3 e dai manuali di curl e PHP. I comandi li ho eseguiti il 12 ottobre 2026 su un’installazione di prova di WordPress 7.1.3 con nginx 1.30.5, PHP-FPM 8.5.11, MariaDB 11 e Redis, e lo stesso giorno ho misurato il mio sito da una connessione in Italia. Dove compare un numero di versione, accanto c’è la data.
Che cosa misura il TTFB, e perché ogni strumento dà un numero diverso
Cinque fasi prima del primo byte
La definizione di Google è una somma. Il TTFB comprende:
- il tempo dei redirect, se l’indirizzo chiesto rimanda a un altro;
- l’avvio del service worker, sui siti che ne hanno uno;
- la risoluzione DNS del nome del sito;
- la connessione e la negoziazione TLS;
- la richiesta, fino all’arrivo del primo byte della risposta.
Le prime quattro voci sono rete e configurazione. Il lavoro di WordPress sta tutto nell’ultima, dove PHP carica il core, i plugin e il tema, interroga il database e costruisce l’HTML, oppure una cache restituisce una copia già pronta.
Un TTFB di un secondo può essere fatto di novecento millisecondi di server e cento di rete, o del contrario, e le due cure non hanno niente in comune. Lo dice anche la documentazione del Chrome UX Report, il database dei dati reali di Chrome: il TTFB “non è una misurazione diretta del tempo di risposta del server”.
Le soglie: 0,8 secondi per il TTFB, 600 millisecondi per il server
Per web.dev un TTFB è buono fino a 0,8 secondi e scarso sopra 1,8, sui dati delle visite reali, e Google stessa lo presenta come un’indicazione di massima. Il TTFB non è un Core Web Vital. Un sito può stare sopra la soglia senza danni se FCP e LCP, che vengono dopo, restano buoni.
PageSpeed Insights, nella parte di laboratorio, usa un’altra soglia. Con Lighthouse 13, del 10 ottobre 2025, il vecchio controllo “Reduce server response times” non esiste più, e anche PageSpeed Insights mostra al suo posto l’insight Latenza di richiesta di download del documento. Sulla prima richiesta l’insight segnala tre cose: i redirect, un server che risponde in più di 600 millisecondi e un HTML non compresso. La soglia è più bassa perché quel tempo non comprende DNS e redirect, che il TTFB invece conta.
Nel codice di Chrome che calcola l’insight, un commento indica anche l’obiettivo vero: 100 millisecondi.
WordPress ha una misura sua. Dalla 6.1 il controllo sulla cache di pagina della Salute del sito chiede tre volte la home al server stesso, prende il valore di mezzo e lo confronta con 600 millisecondi, una soglia che il codice dichiara presa da web.dev.
Quale TTFB ti sta mostrando lo strumento che hai aperto
| Strumento | Cosa misura | Da dove | Soglia |
|---|---|---|---|
| PageSpeed Insights, dati reali | Il TTFB completo, redirect compresi, al 75° percentile degli ultimi 28 giorni | I Chrome dei visitatori, se il sito ha abbastanza traffico | 0,8 s |
| PageSpeed Insights, latenza del documento | Il tempo di risposta del server, senza DNS né redirect | Un test di laboratorio sull’indirizzo finale | 600 ms |
| DevTools, scheda Timing | “In attesa della risposta del server”: un giro di rete più il tempo del server | Il tuo browser | Nessuna |
curl, %{time_starttransfer} | DNS, connessione, TLS e server, senza i redirect | Il computer da cui lo lanci | Nessuna |
| Salute del sito, cache di pagina | Tre richieste del server alla propria home, il valore di mezzo | Il server stesso | 600 ms |
| Intestazione Server-Timing | Le fasi che misuri tu nel codice | Il server | Nessuna |
C’è anche un modo di abbassare il numero senza toccare il server: la risposta 103 Early Hints, con cui server o CDN mandano subito l’elenco dei file da scaricare mentre la pagina è ancora in costruzione. Per web.dev quella risposta conta come primo byte (Chrome aveva cambiato la misura nella versione 115, ed è tornato indietro nella 133). Se il TTFB crolla il giorno in cui attivi gli Early Hints, il server è rimasto quello di prima.
Scomporre il TTFB: redirect, rete o server
Il primo passo è separare le fasi.
Da terminale, una fase alla volta
curl registra l’istante in cui finisce ogni fase, contato dall’inizio della richiesta: risoluzione del nome, connessione, TLS, primo byte. Sono tempi che si sommano, come spiega il manuale di curl, e la durata di una fase è la differenza fra il suo valore e quello prima. Questo comando, per un indirizzo https, fa tre richieste e stampa le durate in millisecondi.
URL="https://www.iltuosito.it/"
for i in 1 2 3; do
curl -s -o /dev/null -w '%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer}\n' "$URL"
done | awk '{ printf "dns %4.0f ms | tcp %4.0f | tls %4.0f | attesa %4.0f | primo byte %4.0f ms\n", $1*1000, ($2-$1)*1000, ($3-$2)*1000, ($4-$3)*1000, $4*1000 }'Sul mio sito, la sera del 12 ottobre 2026, la home dava fra 3 e 6 millisecondi di risoluzione del nome (54 alla prima prova, quando il nome non era ancora in cache), fra 11 e 25 di connessione, fra 18 e 37 di TLS e fra 62 e 137 di attesa, per un primo byte fra 98 e 229 millisecondi.
La parte più grande era l’attesa, che è la stessa cosa che i DevTools chiamano “In attesa della risposta del server”, un giro di rete più il lavoro del server. Se l’attesa è bassa e il totale è alto, il tempo si perde prima di arrivare al server; se è alta, il problema sta dentro.
Il comando usa HTTP/1.1 o HTTP/2, quello che curl sceglie da solo. Con l’opzione --http3-only connessione e TLS si fondono e la colonna tcp smette di corrispondere a un giro di rete: per confrontare i due protocolli conta time_appconnect, l’istante in cui la connessione cifrata è pronta. Come si attiva HTTP/3 e cosa fa guadagnare è nella guida su attivare HTTP/3 e misurarne l’effetto.
I redirect si misurano a parte, seguendo la catena da un indirizzo vecchio o scritto senza https e senza www:
curl -s -o /dev/null -L -w 'salti: %{num_redirects}, tempo nei redirect: %{time_redirect} s, arrivo: %{url_effective}\n' http://iltuosito.it/La stessa pagina con e senza cache
Il secondo confronto dice quanto lavora la cache di pagina. Per quasi tutte le cache un indirizzo con un parametro mai visto è una pagina che non hanno ancora, o che non conservano affatto, e la pagina va generata da capo. È il metodo che suggerisce anche la guida di Google all’ottimizzazione del TTFB per vedere il tempo che la cache nasconde.
URL="https://www.iltuosito.it/una-pagina/"
for i in 1 2 3; do
echo "con cache $(curl -s -o /dev/null -w '%{time_starttransfer}' "$URL") s"
echo "senza cache $(curl -s -o /dev/null -w '%{time_starttransfer}' "$URL?nocache=$RANDOM$RANDOM") s"
doneSul mio sito, il 12 ottobre, la home rispondeva in 0,16 secondi con la cache e in 0,45 senza, come valore di mezzo di cinque prove. Sono circa tre decimi di secondo di lavoro tolti a ogni visita che la cache serve. Se i due tempi sono uguali la cache non sta servendo. È uno dei controlli mensili della manutenzione, perché un aggiornamento può spegnere una cache senza dirlo.
Il parametro va inventato, perché alcune cache ignorano quelli noti delle campagne, come utm_source. E nel browser la prova si fa da una finestra anonima, perché da collegati a WordPress quasi nessuna cache serve la copia pronta.
Nel browser e nella Salute del sito
Nei DevTools di Chrome la stessa scomposizione sta nella scheda Rete: si ricarica la pagina, si seleziona la prima richiesta, quella del documento, e si apre Timing. In italiano le fasi si chiamano “Ricerca DNS”, “Connessione iniziale”, “SSL”, “Richiesta inviata” e “In attesa della risposta del server”. Se il server manda l’intestazione Server-Timing, sotto compare una sezione “Server Timing” con le sue voci.
In bacheca, Strumenti → Salute del sito ha quattro controlli che riguardano la fase del server, tutti con l’etichetta Prestazioni. Sono la cache di pagina e la cache di oggetti persistenti, dalla 6.1, le opzioni caricate automaticamente, dalla 6.6, e la cache Opcode, dalla 7.0. Nella scheda Informazioni c’è anche lo stato di OPcache, con la memoria occupata e la percentuale di hit.
Chi usa WordPress in italiano deve sapere una cosa.
Nel controllo della cache di pagina, la riga con il tempo del server è tradotta allo stesso modo sopra e sotto la soglia, e in tutti e due i casi dice “Dovrebbe essere meno dei 600 millisecondi raccomandati”. Il valore poi viene chiamato “media”, mentre è la mediana di tre richieste. Sul banco di prova, con una mediana di 3 millisecondi, la Salute del sito mostrava proprio quella frase, e l’esito lo diceva solo l’icona all’inizio della riga: una spunta sotto i 600 millisecondi, un punto esclamativo sopra.
Prima del server: redirect, DNS e connessione
Ogni redirect si somma al TTFB
Un redirect costa una richiesta intera, con il suo giro di rete e la sua risposta, prima che parta quella buona. I dati reali lo contano nel TTFB, mentre i test di laboratorio di solito misurano l’indirizzo finale e non lo vedono, e un sito può avere un laboratorio pulito e un TTFB alto sul campo anche solo per questo. Web.dev indica come cause frequenti l’indirizzo scritto senza https e la barra finale messa o tolta, e consiglia di eliminare per primi i redirect interni al sito, gli unici sotto il tuo controllo.
Il 12 ottobre 2026 il mio sito, chiamato con http e senza www, faceva due salti prima della home, da http a https e poi verso www, e spendeva nei redirect un quinto di secondo, come valore di mezzo di cinque prove (da 0,12 a 0,49 secondi). Il secondo salto lo faceva il server d’origine, e una regola di redirect sulla CDN lo risolve prima di arrivarci. I link interni vanno scritti sull’indirizzo finale, e la regola che resta si scrive come spiegato nella guida su scrivere il redirect sulla forma finale dell’indirizzo.
Il salto da http a https si può togliere dal lato del browser. Con l’intestazione HSTS il sito dice al browser di usare sempre https, e dalla visita successiva la conversione avviene prima di partire, senza giri di rete. La lista di precaricamento HSTS fa lo stesso dalla prima visita, a patto di un max-age di almeno un anno, delle direttive includeSubDomains e preload, di tutti i sottodomini in https e di un primo redirect da http a https sullo stesso nome.
Si sceglie sapendo che vincola tutti i sottodomini e che uscirne è lento.
DNS, TCP e TLS: dove conta la distanza
La risoluzione DNS pesa solo quando il nome non è già in cache, nel browser o nel resolver del provider: alla prima visita conta, alle successive quasi mai. Connessione e TLS si pagano invece a ogni connessione nuova, e ciascuna costa almeno un giro di rete fino al server. Il TLS 1.3 è progettato per tenere la negoziazione al minimo, e HTTP/3 fonde connessione e cifratura risparmiando un altro giro.
Più il visitatore è lontano, più costa ogni giro.
Una CDN chiude la connessione nel nodo più vicino al visitatore, e così DNS, connessione e TLS restano brevi anche per chi sta lontano. Se però la CDN non tiene in cache l’HTML, la pagina arriva comunque dal server d’origine e la fase del server resta quella di prima. Web.dev avverte che il viaggio fino all’origine può annullare buona parte del vantaggio.
Sul mio sito, servito da Cloudflare dal nodo di Milano, DNS, connessione e TLS insieme stanno sui 40 millisecondi, e il resto del TTFB è attesa.
La cache di pagina: c’è, ma sta servendo?
Due cache, due segnali diversi
La cache di pagina è l’intervento che sposta di più il TTFB, perché salta tutto il lavoro di PHP e del database. Su un sito WordPress ce ne possono essere due, da controllare separatamente. La prima sta sul server, ed è un plugin che risponde prima che WordPress carichi gli altri plugin, attraverso il file advanced-cache.php, oppure la cache del server web, come quella di LiteSpeed o di nginx. La seconda sta sulla CDN.
La CDN non mette in cache l’HTML per conto suo. Cloudflare, per esempio, non lo fa per impostazione predefinita e risponde cf-cache-status: DYNAMIC, che vuol dire “non idonea alla cache”: la richiesta va all’origine come se la CDN non ci fosse. Per tenere l’HTML sul nodo servono una regola di cache, con le esclusioni per chi è collegato, oppure APO per WordPress, che richiede il plugin di Cloudflare e sul piano gratuito è un servizio a pagamento.
Il mio sito ha tutte e due le situazioni. Cloudflare risponde DYNAMIC, e la cache di pagina sta sul server, in W3 Total Cache, che sul mio sito non aggiunge nessuna intestazione che la riveli. La cache funziona, e lo dice solo il tempo, con il confronto della sezione precedente.
Chi la scavalca senza avvisare
Una cache attiva non serve tutte le visite. Restano fuori, quasi sempre per scelta:
- chi è collegato a WordPress, compreso chi fa le prove nel browser con la bacheca aperta in un’altra scheda;
- le pagine personali dei negozi: WooCommerce chiede di escludere carrello, checkout e account e indica i cookie che segnalano un carrello attivo, come
woocommerce_items_in_cart; - gli indirizzi con parametri nuovi, come quelli delle campagne, che per una cache sono pagine diverse se non è configurata per ignorarli;
- la prima visita dopo uno svuotamento della cache, o dopo la scadenza della copia.
Su un sito editoriale queste visite sono poche. Su un negozio o un’area riservata possono essere la maggioranza, ed è per questo che lì il TTFB senza cache conta più di quello con la cache.
Come si legge la risposta
Quando la cache si dichiara, lo fa con un’intestazione. WordPress stesso, nel controllo della Salute del sito, ne cerca una lista: cf-cache-status di Cloudflare, x-cache di Fastly e di altri proxy, x-litespeed-cache di LiteSpeed, x-cache-status e x-proxy-cache di nginx, x-varnish di Varnish, più le intestazioni standard come age e cache-control. Il segnale buono è un HIT, o un age maggiore di zero. Una cache che non dichiara niente si riconosce solo dal tempo.
Per scegliere e configurare il plugin c’è il confronto fra W3 Total Cache e WP Rocket, e le impostazioni che annullano la cache senza dirlo sono nella diagnosi completa di un sito lento.
Quando la cache non può aiutare: il tempo di PHP
Le visite che la cache non serve mostrano il tempo vero del server, ed è lì che si cerca, con tre strumenti: un’intestazione che porta i tempi di PHP nella risposta, un plugin che dice chi li spende e i controlli che WordPress fa da sé.
Misurarlo con Server-Timing
L’intestazione Server-Timing porta nella risposta i tempi misurati dal server, e Chrome li mostra nella scheda Timing; web.dev la consiglia per capire un TTFB alto. WordPress non la manda.
La aggiunge Performance Lab, il plugin del team che lavora sulle prestazioni del core, che però si dichiara un plugin di prova, da usare in produzione con cautela. Dalla 6.9 bastano poche righe in un mu-plugin, grazie a un hook del core che permette di mandare intestazioni a pagina generata. Il file va nella cartella wp-content/mu-plugins/, per esempio come server-timing.php:
<?php
/**
* Plugin Name: Server-Timing minimo
* Description: Manda in un'intestazione il tempo di PHP prima del template, del template e in totale. WordPress 6.9 o successivo.
*/
add_action( 'wp_before_include_template', function () {
$GLOBALS['st_prima_del_template'] = timer_float();
}, 1 );
add_action( 'wp_finalized_template_enhancement_output_buffer', function () {
if ( headers_sent() || ! isset( $GLOBALS['st_prima_del_template'] ) ) {
return;
}
$prima = $GLOBALS['st_prima_del_template'];
$totale = timer_float();
header( sprintf(
'Server-Timing: wp-before-template;dur=%.1f, wp-template;dur=%.1f, wp-total;dur=%.1f',
$prima * 1000,
( $totale - $prima ) * 1000,
$totale * 1000
) );
} );La prima funzione segna il tempo quando WordPress sta per caricare il template, misurato da timer_float(), una funzione del core che conta dall’inizio della richiesta. La seconda, a pagina finita, manda tre durate in millisecondi: il lavoro fino al template, con core, plugin, tema e query principale, quello del template e il totale. Registrare l’azione basta ad accendere il buffer in cui WordPress tiene la pagina prima di mandarla. Nei temi classici dalla 6.9 c’è già, e nei temi a blocchi, dice la nota sulle prestazioni della 6.9, non cambia quasi nulla perché la pagina viene composta comunque prima di partire.
Con il mu-plugin attivo, una risposta che porta l’intestazione è stata generata da PHP, e una che non la porta arriva da una cache che risponde prima di WordPress. Sul banco, con W3 Total Cache e la cache su disco, la prima visita alla home aveva l’intestazione e un TTFB di 116 millisecondi, le tre successive niente intestazione e 5 millisecondi; con il parametro sempre diverso l’intestazione tornava, con circa 34 millisecondi di PHP. Se una cache conservasse anche le intestazioni ripeterebbe quelle della prima volta, e il segno sarebbe un valore identico da una richiesta all’altra. Quando è alto wp-before-template il tempo se ne va in core, plugin, tema e query principale, quando è alto wp-template nella costruzione della pagina.
Finita l’analisi il file si toglie: i tempi del server non servono a chi visita.
Query Monitor: query, chiamate esterne e timeline
Per sapere chi spende quel tempo serve Query Monitor. A chi è amministratore mostra le query al database con il componente che le ha fatte, le chiamate HTTP verso servizi esterni con il loro tempo, gli hook e i file di traduzione caricati, e dalla versione 4.0, del 7 aprile 2026, ha anche una vista a timeline della richiesta. Da amministratore la cache di pagina non ti serve, quindi vedi il tempo di generazione per intero.
Le chiamate esterne sono le più subdole, perché il loro tempo dipende da un altro server, e una sola chiamata lenta si somma al TTFB di ogni visita non in cache. È successo anche al core. Dalla 6.7 alla 6.8 la cache dei feed RSS non funzionava, e nei test del team prestazioni un articolo con il widget dei feed richiedeva 267 millisecondi, contro i 35 della 6.9 che l’ha riparata. Per il codice tuo la regola è la stessa: la risposta di un servizio esterno si conserva in un transient, e la chiamata ha un timeout breve.
Object cache persistente: quando la chiede WordPress
WordPress tiene già in memoria, per la durata di una richiesta, opzioni, articoli e termini letti dal database. Una cache di oggetti persistente, con Redis o Memcached, li conserva da una richiesta all’altra. La Salute del sito la consiglia sempre su un multisito, e su un sito singolo quando le opzioni caricate a ogni richiesta superano le 500 voci o i 100 KB, oppure quando articoli, commenti, termini, utenti o opzioni arrivano a 1.000 righe. Le soglie sono nel codice di WordPress 7.1.3 e si cambiano con un filtro.
Una pagina servita dalla cache di pagina non arriva nemmeno a PHP, e la cache di oggetti lavora sulle altre: utenti collegati, carrello e checkout, bacheca, ricerche, chiamate REST. Sui siti dei clienti uso Redis, con il plugin Redis Object Cache (versione 3.0.0 del 18 settembre 2026), che mette in wp-content il file object-cache.php. Il server Redis lo deve offrire l’hosting. Con WP-CLI si vede subito se la cache è attiva:
wp cache type
wp redis statusIl primo comando è di WP-CLI e risponde Default quando non c’è una cache persistente: sul banco, dopo aver attivato il plugin, rispondeva Redis. Il secondo arriva con il plugin, e le righe da guardare sono Status: Connected e Drop-in: Valid.
PHP e OPcache
Ogni versione di PHP riceve due anni di supporto attivo e due di sole correzioni di sicurezza. Al 12 ottobre 2026, secondo la pagina delle versioni supportate, la 8.2 ha correzioni di sicurezza fino al 31 dicembre 2026 e la 8.3 fino al 31 dicembre 2027, mentre la 8.4 e la 8.5 sono in supporto attivo. WordPress raccomanda la 8.3 o successive. In pratica conta che la versione riceva ancora aggiornamenti, e conviene provare l’aggiornamento di PHP su una copia prima del sito vero.
OPcache tiene in memoria il codice PHP già compilato, e senza ogni richiesta ricompila i file di core, plugin e tema; dalla 7.0 la Salute del sito controlla che sia attivo. Su un sito in italiano c’è un dettaglio in più, perché le traduzioni si caricano a ogni richiesta. Dalla 6.5 WordPress le legge da file PHP, i .l10n.php che arrivano con i pacchetti di lingua di WordPress.org e che OPcache tiene in memoria come il resto del codice. Per le traduzioni che non arrivano da WordPress.org, il plugin Performant Translations converte i vecchi file .mo nello stesso formato.
Autoload e WP-Cron: due costi da conoscere
Le opzioni con il caricamento automatico vengono lette a ogni richiesta. Dalla 6.6 la Salute del sito avvisa quando superano gli 800 KB, e questo comando stampa il totale calcolato nello stesso modo:
wp eval 'require_once ABSPATH . "wp-admin/includes/class-wp-site-health.php"; echo size_format( WP_Site_Health::get_instance()->get_autoloaded_options_size() ) . PHP_EOL;'
L’esempio della documentazione di WP-CLI, wp option list --autoload=on --format=total_bytes, conta solo le opzioni con valore on o yes, mentre dalla 6.6 WordPress assegna auto a quelle create senza una scelta esplicita. Sul banco il comando di WP-CLI dava 16.482 byte, la Salute del sito 23.078. Le voci da togliere vengono quasi sempre da plugin disinstallati male, e come ripulire le opzioni in autoload senza rompere niente è spiegato nella guida alla pulizia del sito.
WP-Cron ha smesso di pesare sul TTFB con la 6.9.
Fino alla 6.8, quando una visita trovava un’attività in scadenza, WordPress lanciava la richiesta a wp-cron.php prima di mandare la pagina, e quella richiesta, pensata per non bloccare, a volte bloccava fino a un secondo. Dalla 6.9 parte allo shutdown, a pagina già inviata, tranne che con la costante ALTERNATE_WP_CRON. Sul banco, con l’avvio del cron rallentato apposta di un secondo, come nel test del ticket che ha portato al cambio, la home dava un TTFB di 0,09 secondi e un tempo totale di 1,10. Riportato l’avvio dov’era fino alla 6.8, il TTFB saliva a 1,12. Un cron di sistema resta utile per la regolarità delle attività e per il carico, ma non più per il TTFB.
Sotto carico: quando i processi PHP finiscono
Su un server con PHP-FPM il numero massimo di processi PHP è fissato da pm.max_children. Quando sono tutti occupati, le richieste nuove aspettano in coda e il TTFB sale per tutte le pagine, anche se nessuna è diventata più lenta. Succede nei picchi di traffico e con i bot che insistono sugli indirizzi che la cache non serve, come il login, la ricerca o admin-ajax.php. Per distinguere un attacco da un picco di traffico e per limitare i tentativi prima che arrivino a PHP ci sono due guide.
La pagina di stato di PHP-FPM dice se succede. listen queue conta le richieste in attesa di un processo libero, max children reached quante volte il limite è stato toccato, slow requests quante richieste hanno superato il tempo fissato in request_slowlog_timeout, e per ognuna il file indicato in slowlog riporta dove si trovava il codice. Si attiva nel pool di PHP-FPM e si apre solo dal server:
; nel pool di PHP-FPM, per esempio /etc/php-fpm.d/www.conf pm.status_path = /fpm-status request_slowlog_timeout = 2s slowlog = /var/log/php-fpm/slow.log
# nella configurazione di nginx, dentro il blocco server del sito
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
fastcgi_pass unix:/run/php-fpm/www.sock;
}Il socket di fastcgi_pass è quello del tuo pool, lo stesso che nginx usa già per PHP. Dopo aver ricaricato PHP-FPM e nginx, la pagina si legge dal server:
curl -s -H 'Host: www.iltuosito.it' http://127.0.0.1/fpm-status
Sul banco ho lasciato tre processi e ho lanciato insieme otto richieste a una pagina che impiegava tre secondi. Una pagina normale, che a riposo rispondeva in 50-70 millisecondi, ha aspettato fra 6 e 6,4 secondi il primo byte, nelle tre volte che ho ripetuto la prova. max children reached è passato a 1 e slow requests a 8, lo slowlog indicava file, riga e funzione del codice lento, e nel log di PHP-FPM era comparso l’avviso “server reached pm.max_children setting”.
listen queue invece è rimasta a zero, perché PHP-FPM la coda la calcola solo per i pool in ascolto su TCP, come 127.0.0.1:9000: con lo stesso picco e il pool su TCP la pagina di stato segnava 2 richieste in coda e un massimo di 7. Con un socket Unix il segnale che vale è max children reached, insieme all’avviso nel log.
Su un hosting condiviso queste impostazioni non si toccano, e se il TTFB sale solo in certe ore la domanda giusta per l’hosting è se i processi PHP arrivano al limite.
Quando è l’hosting
Una macchina più grande non accelera un plugin lento.
L’hosting diventa il collo di bottiglia quando la cache di pagina funziona, le visite senza cache sono state ripulite da query lente e chiamate esterne, PHP è aggiornato con OPcache attivo, e l’attesa senza cache resta sopra i 600 millisecondi o sale con il traffico. A quel punto servono i criteri per scegliere fra hosting condiviso e VPS e, per spostare il sito senza fermarlo, una procedura in un ordine preciso.
In che ordine intervenire
L’ordine segue il rapporto fra quanto costa un intervento e quanto sposta, e parte sempre da una misura.
| Fase | Il segnale | Cosa fare | Dove si misura |
|---|---|---|---|
| Redirect | Salti prima della pagina, TTFB reale molto sopra quello di laboratorio | Link sull’indirizzo finale, redirect sulla CDN, HSTS | curl con -L, dati reali di PageSpeed Insights |
| Cache di pagina | Stesso tempo con e senza parametro | Configurarla e verificarla, valutare l’HTML in cache sulla CDN | curl con e senza parametro |
| PHP senza cache | Attesa sopra i 600 millisecondi sulle pagine fuori cache | Server-Timing, Query Monitor, object cache, PHP e OPcache | Server-Timing, Query Monitor, Salute del sito |
| Rete | DNS, connessione e TLS lunghi per chi è lontano | CDN, TLS 1.3, HTTP/3 | curl a fasi, DevTools |
| Carico | TTFB che sale solo nei picchi | Pagina di stato di PHP-FPM, freno ai bot | max children reached, slowlog |
| Hosting | Tutto il resto fatto, attesa ancora alta | Cambiare macchina o piano | Attesa senza cache |
Il TTFB resta un mezzo.
Nella 6.9 il buffer dei temi classici ha alzato il TTFB di Twenty Twenty-One di un paio di millisecondi, nei test del team prestazioni, e ha abbassato l’LCP del 6%: un intervento così è un buon intervento.
Domande frequenti
Qual è un buon TTFB per un sito WordPress?
Per Google un TTFB è buono fino a 0,8 secondi sulle visite reali e scarso sopra 1,8. Per il solo tempo del server PageSpeed Insights segnala un problema sopra i 600 millisecondi, e il codice di Chrome indica come obiettivo 100. Una pagina servita dalla cache dovrebbe stare molto sotto: il mio sito, dall’Italia, risponde in circa 0,16 secondi.
Il TTFB conta per il posizionamento su Google?
Il TTFB non è un Core Web Vital. I Core Web Vitals sono LCP, INP e CLS, e sono le metriche che Google dice in linea con quello che i suoi sistemi di ranking premiano. Il TTFB conta in modo indiretto, perché viene prima di FCP e LCP e ogni millisecondo in più si somma a loro.
Perché PageSpeed Insights dice che il server è lento se dal mio computer risponde in fretta?
I due numeri misurano cose diverse da posti diversi. Il laboratorio di PageSpeed Insights fa la richiesta da un server di Google, con le sue condizioni di rete, e se la cache non ha la pagina pronta vede il tempo pieno di PHP. Dal tuo computer, vicino al server e con la cache calda, il numero è più basso. Se il laboratorio va peggio dei dati reali l’ambiente di prova è più severo della media, se vanno peggio i dati reali c’entrano cache, redirect o rete.
Cloudflare abbassa il TTFB?
Abbassa la parte di rete, perché la connessione si chiude nel nodo vicino al visitatore. La parte del server resta uguale finché l’HTML non è in cache sulla CDN, e Cloudflare non ce lo mette per impostazione predefinita, come mostra cf-cache-status: DYNAMIC. Per toglierla servono una regola di cache o APO.
Basta un plugin di cache per abbassare il TTFB?
Per le pagine che serve sì, ed è l’intervento che sposta di più. Restano fuori chi è collegato, carrello, checkout e account dei negozi, gli indirizzi con parametri nuovi e la prima visita dopo uno svuotamento. Prima di contarci conviene verificare che stia servendo, con il confronto fra la stessa pagina con e senza parametro.
GZIP o Brotli riducono il TTFB?
La compressione rende più piccolo l’HTML e accorcia il download, ma il primo byte non arriva prima. PageSpeed Insights la controlla nello stesso insight perché riguarda la stessa richiesta. Sulle pagine generate al momento comprimere al volo può anche allungare un poco la risposta, come ricorda web.dev, ed è uno dei lavori che una CDN fa meglio.
In sintesi
Il TTFB è una somma, e si riduce sapendo quale addendo pesa. Si misura per fasi, con curl o con i DevTools, e si confronta la stessa pagina con e senza cache. Poi si tolgono i redirect, si mette in cache quello che si può e si lavora sul tempo di PHP dove la cache non arriva, controllando che il server regga i picchi. L’hosting viene per ultimo, dopo tutto il resto.
Se preferisci che lo guardi io
Quando un cliente mi chiede un’analisi delle prestazioni parto da PageSpeed Insights, poi guardo il server e poi il codice. È il lavoro del servizio di ottimizzazione delle prestazioni. Se vuoi che guardi il tuo sito, scrivimi l’indirizzo e cosa ti sembra lento.

Lascia un commento