Velocizzare WordPress con nginx, Redis e OPcache: guida pratica
Un WordPress lento su un VPS è quasi sempre un WordPress che esegue PHP e interroga il database a ogni visita. La soluzione è mettere tre cache una sopra l'altra: la cache delle pagine di nginx (fastcgi_cache) per i visitatori anonimi, Redis come cache degli oggetti per tutto ciò che resta dinamico e OPcache per non ricompilare PHP a ogni richiesta. Poi si sistemano versione PHP, immagini e database, e si misura il risultato con il TTFB.
In questa guida trovi le configurazioni per Ubuntu 24.04 con nginx e PHP-FPM, i comandi per verificarle e, in fondo, come lo stesso schema è già applicato in Koapanel.
Da dove arriva la lentezza
Quando un visitatore apre una pagina, senza cache succede questo: nginx passa la richiesta a PHP-FPM, WordPress carica core, tema e plugin, esegue decine (a volte centinaia) di query su MariaDB e genera l'HTML. Su un sito con molti plugin sono facilmente diverse centinaia di millisecondi di CPU per ogni pagina, e sotto carico i processi PHP finiscono e le richieste si mettono in coda.
Ogni livello di cache taglia una parte di questo lavoro:
| Livello | Cosa evita | Per chi funziona |
|---|---|---|
| Cache delle pagine (nginx) | PHP e database, del tutto | Visitatori non collegati |
| Cache degli oggetti (Redis) | Molte query ripetute | Tutti, anche admin e carrello |
| OPcache | Lettura e compilazione dei file PHP | Tutte le richieste PHP |
1. Cache delle pagine con nginx fastcgi_cache
È il livello che fa la differenza più grande: una pagina già pronta viene servita da nginx leggendo un file, senza toccare PHP. Non serve un plugin di cache: lo fa nginx con il modulo fastcgi.
Nel contesto http definisci dove salvare la cache. Su Ubuntu i file in /etc/nginx/conf.d/ sono già inclusi dentro http, quindi crea /etc/nginx/conf.d/fastcgi-cache.conf:
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m max_size=1g inactive=60m use_temp_path=off;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
Poi, nel blocco server del sito, decidi cosa non mettere in cache e attiva la cache nella location PHP:
set $skip_cache 0;
if ($request_method = POST) { set $skip_cache 1; }
if ($query_string != "") { set $skip_cache 1; }
if ($request_uri ~* "/wp-admin/|/wp-json/|/xmlrpc.php|wp-.*\.php|/feed/|sitemap(_index)?\.xml|/cart/|/checkout/|/my-account/") { set $skip_cache 1; }
if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in|woocommerce_items_in_cart|woocommerce_cart_hash") { set $skip_cache 1; }
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_cache WORDPRESS;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
add_header X-Cache $upstream_cache_status;
}
Controlla la sintassi, ricarica e verifica l'intestazione X-Cache: la prima richiesta dà MISS, la seconda HIT.
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://www.esempio.it/ | grep -i x-cache
curl -sI https://www.esempio.it/ | grep -i x-cache
Quanto deve durare la cache
Il problema di ogni cache delle pagine è l'invalidazione: pubblichi un articolo e la home in cache mostra ancora quello vecchio. La versione open source di nginx non ha un comando per cancellare una singola pagina; hai due strade:
- durata breve (da 30 secondi a qualche minuto, la cosiddetta microcache): i contenuti si aggiornano da soli e sotto carico quasi tutte le richieste restano servite dalla cache;
- durata lunga e svuotamento manuale (o da un plugin che cancella i file della cache) quando pubblichi.
Per svuotare tutto a mano:
sudo find /var/cache/nginx/wordpress -type f -delete
Per la maggior parte dei siti la durata breve è la scelta più sicura: nessun contenuto vecchio visibile a lungo, nessuna dipendenza da plugin.
2. Cache degli oggetti con Redis
La cache delle pagine non aiuta chi è collegato, chi ha il carrello pieno o le chiamate a wp-admin. Qui entra Redis: WordPress salva in memoria i risultati delle query e le opzioni, invece di chiederli a MariaDB ogni volta.
sudo apt install redis-server php8.3-redis
sudo systemctl restart php8.3-fpm
Limita la memoria di Redis e fagli scartare le chiavi meno usate quando è pieno, in /etc/redis/redis.conf:
maxmemory 256mb
maxmemory-policy allkeys-lru
Per una cache non serve salvare su disco: puoi disattivare i salvataggi commentando le righe save o impostando save "". Poi sudo systemctl restart redis-server.
In wp-config.php, prima della riga «That's all, stop editing!», indica dove si trova Redis e un prefisso diverso per ogni sito, così più siti non si sovrascrivono le chiavi:
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_PREFIX', 'esempio_it:' );
Installa e attiva il plugin Redis Object Cache con WP-CLI, eseguito come utente del sito (qui www-data):
sudo -u www-data wp --path=/var/www/esempio.it plugin install redis-cache --activate
sudo -u www-data wp --path=/var/www/esempio.it redis enable
sudo -u www-data wp --path=/var/www/esempio.it redis status
Una nota di sicurezza: un solo Redis condiviso tra siti di clienti diversi permette a un sito di leggere le chiavi degli altri. Su un server con più clienti è meglio un'istanza per sito, raggiungibile solo da un socket locale.
3. OPcache
OPcache tiene in memoria il codice PHP già compilato. Su Ubuntu è già installata e attiva, ma i valori predefiniti sono piccoli per WordPress con molti plugin. Crea /etc/php/8.3/fpm/conf.d/99-opcache.ini:
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=1
opcache.revalidate_freq=60
sudo systemctl restart php8.3-fpm
Con revalidate_freq=60 PHP controlla se i file sono cambiati al massimo una volta al minuto: dopo un aggiornamento manuale di un file il cambiamento può comparire con un minuto di ritardo. Il JIT di PHP 8 serve soprattutto per calcoli pesanti; su WordPress, che passa gran parte del tempo ad attendere database e rete, di solito non cambia molto.
4. Versione PHP e PHP-FPM
Ogni nuova versione di PHP è in genere più veloce della precedente, e le versioni vecchie non ricevono più patch di sicurezza. WordPress consiglia PHP 8.3 o superiore e MariaDB 10.11 o superiore. Secondo il calendario ufficiale di PHP, la 8.2 riceve patch di sicurezza solo fino al 31 dicembre 2026. Ubuntu 24.04 include PHP 8.3; le versioni 8.4 e 8.5 si installano dall'archivio ondrej/php. Prima di cambiare versione, prova il sito su una copia: qualche plugin datato può dare errori.
Controlla anche il pool di PHP-FPM (/etc/php/8.3/fpm/pool.d/www.conf): pm.max_children va calcolato sulla RAM libera divisa per la memoria media di un processo PHP (spesso 60-100 MB con WordPress). Troppo basso e le richieste si accodano, troppo alto e il server va in swap.
5. Immagini e file statici
Le immagini sono spesso la parte più pesante della pagina e incidono sui Core Web Vitals più del TTFB.
- Carica immagini già ridimensionate e usa formati moderni: WordPress supporta WebP e, dalla 6.5, anche AVIF.
- Il caricamento differito (
loading="lazy") è già automatico in WordPress per le immagini sotto la parte visibile. - Fai mettere in cache ai browser i file statici:
location ~* \.(?:css|js|jpg|jpeg|gif|png|webp|avif|svg|ico|woff2)$ {
expires 30d;
access_log off;
try_files $uri =404;
}
- Se il pubblico è lontano dal server, una CDN davanti al sito accorcia la distanza per immagini, CSS e JavaScript.
6. Database
- Opzioni caricate automaticamente: plugin disinstallati lasciano spesso dati in
wp_optionsche vengono letti a ogni pagina. Controlla quanti byte pesano:
sudo -u www-data wp --path=/var/www/esempio.it option list --autoload=on --format=total_bytes
- Revisioni: limita quelle conservate con
define( 'WP_POST_REVISIONS', 10 );inwp-config.php. - Transient scaduti:
wp transient delete --expired. - WP-Cron: di serie gira durante le visite. Meglio disattivarlo con
define( 'DISABLE_WP_CRON', true );e lanciarlo dal cron di sistema ogni 5 minuti. - MariaDB:
innodb_buffer_pool_sizein/etc/mysql/mariadb.conf.d/50-server.cnfdovrebbe contenere i dati usati più spesso; su un VPS che ospita anche PHP, una fetta ragionevole della RAM, non tutta. - Per trovare le query lente usa il plugin Query Monitor o lo slow query log di MariaDB.
Come misurare: TTFB e PageSpeed
Misura prima e dopo, sempre da un browser o da un computer non collegato a wp-admin (gli utenti collegati saltano la cache delle pagine).
Il TTFB (tempo al primo byte) dice quanto ci mette il server a rispondere. Con curl:
for i in 1 2 3 4 5; do curl -o /dev/null -s -w '%{time_starttransfer}\n' https://www.esempio.it/; done
Guarda la mediana, non il singolo valore. Secondo web.dev un TTFB buono è di 0,8 secondi o meno; con la cache delle pagine attiva su un server vicino si scende di solito molto sotto.
PageSpeed Insights misura l'esperienza completa: LCP, CLS, INP. Distingui i dati di laboratorio (una prova simulata) da quelli reali degli utenti Chrome, che compaiono solo per siti con abbastanza traffico. Se il TTFB è buono ma il punteggio resta basso, il problema è di solito nel front-end: immagini, font, JavaScript di terze parti.
Come lo fa Koapanel
Se non vuoi mantenere a mano questi file su ogni sito, Koapanel applica lo stesso schema dalla sezione WordPress (documentazione):
- Cache delle pagine nginx già configurata, con durata predefinita di 60 secondi. Non vengono mai messe in cache richieste POST, indirizzi con parametri, utenti collegati, carrello, checkout e account (anche WooCommerce), area admin, REST API, feed e sitemap. Il pulsante Svuota tutte cancella la cache del sito e la percentuale di hit è calcolata sul registro di accesso delle ultime 24 ore.
- Redis privato per ogni sito, raggiungibile solo su un socket locale (nessuna porta di rete), con memoria limitata (64 MB di serie) e senza salvataggi su disco; il plugin Redis Object Cache viene installato e attivato dal pannello.
- OPcache attiva per tutto il server, con hit rate e memoria della versione PHP del sito visibili nel pannello.
- Misura del TTFB della home con e senza cache (mediana di 5 misure), con lo storico delle misure.
- PHP dalla 7.4 alla 8.5, scelto per ogni sito, con memoria e limiti regolabili sito per sito (versioni PHP).
- WP-Cron sul cron di sistema, ogni 5 minuti.
Cosa non c'è: Koapanel usa solo nginx, quindi niente Apache e regole .htaccess, e niente LiteSpeed con LSCache. Se il tuo stack è già basato su LiteSpeed e ne sei soddisfatto, LSCache è un'ottima alternativa a questo schema. Per mettere in piedi il server da zero trovi la guida WordPress su VPS.
Domande frequenti
Serve un plugin di cache se uso nginx fastcgi_cache?
No per la cache delle pagine: la gestisce nginx. Un plugin può servire per ottimizzare CSS, JavaScript e immagini, o per svuotare la cache quando pubblichi.
Redis sostituisce la cache delle pagine?
No, sono complementari. La cache delle pagine evita del tutto PHP per i visitatori anonimi; Redis accelera le pagine che devono comunque passare da PHP, come wp-admin e il carrello.
La cache delle pagine rompe WooCommerce?
Non se escludi carrello, checkout, account e i cookie di WooCommerce, come nell'esempio sopra. Prova sempre un ordine completo dopo averla attivata.
Quale versione di PHP scegliere?
Una supportata e compatibile con i tuoi plugin: oggi 8.3 o 8.4 sono scelte sicure, 8.5 è la più recente. Prova prima su una copia del sito.
Perché PageSpeed resta basso anche con il TTFB buono?
Perché PageSpeed misura anche il caricamento nel browser: immagini grandi, font e script esterni pesano più del server.
Prova la configurazione già pronta
Puoi installare Koapanel gratis su un Ubuntu 24.04 nuovo (fino a 3 siti per uso personale) e confrontare il TTFB prima e dopo dalla scheda WordPress, oppure provare la demo pubblica su https://demo.koapanel.app/. Piani e limiti sono nella pagina prezzi.