Aggiornare PHP su un sito WordPress: rischi di una versione obsoleta e migrazione senza sorprese

Versioni di PHP supportate, rischi di una versione a fine vita e un metodo per migrare: PHPCompatibility, copia di prova, PHP-FPM affiancati e ritorno indietro.

Due servizi PHP-FPM affiancati su un server, etichettati 8.1 e 8.3: un interruttore sposta le richieste di un sito WordPress dal socket della versione vecchia, segnata in grigio come fine vita, a quello della versione nuova, mentre una freccia di ritorno indica il piano per tornare indietro

Tra i siti che bonifichiamo, la versione di PHP è uno dei dettagli che più spesso raccontano la storia dell’infezione. Un sito fermo a una versione non più supportata di solito ha anche plugin fermi da anni, perché le versioni nuove richiedono un PHP più recente; e un sito con plugin fermi è un sito con vulnerabilità note e mai corrette. L’aggiornamento di PHP viene rimandato perché fa paura: «e se si rompe qualcosa?». Fatto con metodo, il rischio si riduce a pochi minuti di lavoro e a un ritorno indietro che richiede un comando.

Il rischio di un aggiornamento di PHP si misura in ore e si può annullare. Il rischio di una versione obsoleta cresce ogni mese e non si annulla.

In questo articolo vediamo quali versioni di PHP sono supportate oggi e cosa raccomanda WordPress, perché una versione obsoleta è un problema di sicurezza anche quando «funziona tutto», come verificare la compatibilità di plugin e temi prima di cambiare, come far convivere più versioni di PHP-FPM sullo stesso server e come passare da una all’altra con un piano di ritorno pronto.

Quali versioni di PHP sono supportate oggi

Ogni ramo di PHP riceve due anni di supporto attivo, con correzioni di bug e di sicurezza, seguiti da due anni di sole correzioni di sicurezza. Secondo la pagina ufficiale delle versioni supportate, a fine ottobre 2026 i rami vivi sono quattro: PHP 8.2, che riceve solo correzioni di sicurezza fino al 31 dicembre 2026; PHP 8.3, solo sicurezza fino al 31 dicembre 2027; PHP 8.4, in supporto attivo fino al 31 dicembre 2026 e con correzioni di sicurezza fino al 31 dicembre 2028; PHP 8.5, in supporto attivo fino al 31 dicembre 2027 e di sicurezza fino al 31 dicembre 2029. L’elenco dei rami non più supportati comprende PHP 8.1, arrivato a fine vita il 31 dicembre 2025, PHP 8.0 (26 novembre 2023) e PHP 7.4 (28 novembre 2022).

Sul fronte di WordPress, la pagina dei requisiti raccomanda PHP 8.3 o superiore, e precisa che WordPress funziona anche con PHP 7.4, ma che queste versioni hanno raggiunto la fine del supporto e possono esporre il sito a vulnerabilità. Con WordPress 7.0 il minimo è salito a PHP 7.4, come annunciato sul blog degli sviluppatori con l’articolo sull’abbandono di PHP 7.2 e 7.3, e la tabella di compatibilità del manuale indica WordPress 6.9 e 7.0 compatibili con PHP 8.3, 8.4 e 8.5. In pratica: chi è su 8.2 ha due mesi per pianificare il passaggio, chi è su 8.1 o prima è già fuori tempo.

Diagramma a barre orizzontali su una linea del tempo dal 2020 al 2030, una barra per ogni versione di PHP. Le versioni 7.4, 8.0 e 8.1 sono grigie, con la data di fine vita: 28 novembre 2022, 26 novembre 2023 e 31 dicembre 2025. Le versioni 8.2, 8.3, 8.4 e 8.5 hanno una parte blu per il supporto attivo e una parte gialla per le sole correzioni di sicurezza, con fine rispettivamente il 31 dicembre 2026, 2027, 2028 e 2029. Una linea verticale rossa segna il 26 ottobre 2026, e un’etichetta verde indica che WordPress raccomanda PHP 8.3 o superiore.
I cicli di vita dei rami di PHP secondo php.net: a fine 2026 esce di scena anche PHP 8.2, e WordPress raccomanda già 8.3 o superiore.

Una precisazione doverosa: le distribuzioni Linux con supporto a lungo termine a volte continuano a correggere le proprie versioni di PHP anche dopo la fine del supporto ufficiale, per tutta la durata della release. Un PHP 8.1 di una distribuzione ancora supportata non è quindi necessariamente privo di patch. Il problema dell’ecosistema WordPress però resta, come vediamo subito.

Perché una versione obsoleta è un rischio

Il rischio diretto è evidente: una vulnerabilità scoperta nel motore PHP dopo la fine del supporto non viene più corretta. Il rischio indiretto è più insidioso e, nella nostra esperienza, più frequente. Gli sviluppatori di plugin e temi alzano la versione minima di PHP richiesta, dichiarata nell’intestazione Requires PHP. Quando l’aggiornamento di un plugin richiede un PHP più recente di quello installato, WordPress lo segnala ma non lo installa. Il sito resta così sull’ultima versione compatibile del plugin, e se la correzione di una vulnerabilità arriva solo nella versione nuova, quel sito resta esposto per sempre, anche se qualcuno clicca regolarmente «aggiorna tutto».

È un meccanismo silenzioso: il pannello non mostra errori, il sito funziona, e la vulnerabilità rimane lì finché uno scanner automatico non la trova. Lo stesso vale per le librerie incluse nei plugin e per i temi. Quando valutiamo l’esposizione di un sito a una vulnerabilità di un plugin, la prima domanda è se la versione corretta è installabile; con un PHP vecchio, spesso la risposta è no.

Quale PHP usa davvero il sito

Sembra una domanda banale e non lo è. Su un server possono convivere più versioni, e quella usata dal sito web può essere diversa da quella della riga di comando, che è quella usata da WP-CLI e dai cron. Il comando php -v risponde solo per la seconda. La versione servita al web si legge nella configurazione del web server o dalla sezione Salute del sito di WordPress, alla voce Server del pannello Informazioni. Evitare invece di creare un file phpinfo.php nella docroot: è uno dei file esposti per errore che gli scanner cercano per primi.

# Versione della riga di comando (WP-CLI, cron)
php -v
wp --info | grep 'PHP version'

# Versione servita al web: a quale socket PHP-FPM punta ciascun sito
grep -rn 'fastcgi_pass' /etc/nginx/sites-enabled/
grep -rn 'proxy:unix' /etc/apache2/sites-enabled/

# Versioni di PHP-FPM installate e pool attivi
ls /etc/php/
ls /run/php/

Se il server lo annuncia, la versione compare anche nell’intestazione X-Powered-By delle risposte, visibile con curl -sI. È comodo per una verifica, ma è un’informazione che non andrebbe regalata a chi cerca siti con versioni vulnerabili: dopo l’aggiornamento, come vedremo, conviene spegnerla.

Verificare la compatibilità prima di cambiare

Il core di WordPress è compatibile con le versioni recenti; i problemi, quando ci sono, arrivano da plugin e temi, soprattutto da quelli scritti su misura o non più mantenuti. Il primo controllo è un inventario: wp plugin list e wp theme list con versione e disponibilità di aggiornamenti, e per ciascun plugin la data dell’ultimo rilascio. Un plugin fermo da anni è un candidato alla sostituzione indipendentemente da PHP.

Il secondo controllo è l’analisi statica del codice con PHPCompatibilityWP, un insieme di regole per PHP_CodeSniffer che segnala le funzioni rimosse, le sintassi cambiate e le funzionalità deprecate tra una versione e l’altra, già tarato per ignorare i falsi positivi dovuti alle compatibilità interne di WordPress. Si installa in una cartella di lavoro separata, fuori dal sito, e si fa girare su plugin e temi indicando la versione di destinazione:

mkdir -p ~/verifica-php && cd ~/verifica-php
composer init -n --name=verifica/php
composer config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer config minimum-stability dev && composer config prefer-stable true
composer require --dev phpcompatibility/phpcompatibility-wp:"^3.0@dev"

# Compatibilità con PHP 8.3 e successivi, riepilogo per file
vendor/bin/phpcs -p --standard=PHPCompatibilityWP --runtime-set testVersion 8.3- \
  --extensions=php --report=summary \
  /var/www/esempio.it/htdocs/wp-content/plugins /var/www/esempio.it/htdocs/wp-content/themes

Il risultato va letto con giudizio. Gli errori vanno sempre verificati, gli avvisi sulle deprecazioni indicano codice che funziona ancora ma che smetterà di funzionare in una versione futura. Molti plugin controllano la versione di PHP prima di usare una funzione, e l’analisi statica non lo vede. Soprattutto, l’analisi non sostituisce la prova: non vede i problemi che dipendono dai dati, dalle estensioni installate o dal comportamento in esecuzione. Per quello serve una copia del sito.

La copia di prova e i log degli errori

La prova si fa su una copia del sito, con lo stesso codice e un database recente, servita dalla nuova versione di PHP. Prima di cominciare conviene attivare un log degli errori dedicato al pool di PHP-FPM della copia, così che deprecazioni e avvisi finiscano in un file leggibile e non nelle pagine o in un debug.log pubblico. Poi si percorrono le funzioni che contano: home, articoli, ricerca, moduli di contatto, login, area riservata, carrello e checkout completo con un pagamento di prova, pannello di amministrazione con l’editor, cron di WordPress.

; /etc/php/8.3/fpm/pool.d/esempio-staging.conf — log degli errori del pool di prova
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php/esempio-staging-error.log
php_admin_flag[display_errors] = off
# Errori raggruppati per tipo e messaggio, i più frequenti in cima
grep -E 'PHP (Fatal error|Warning|Deprecated)' /var/log/php/esempio-staging-error.log \
  | sed -E 's/^\[[^]]*\] //' | sort | uniq -c | sort -rn | head -30

Un errore fatale su una funzione chiave blocca il passaggio finché il plugin non viene aggiornato o sostituito. Gli avvisi e le deprecazioni, invece, si annotano e si segnalano agli autori, ma raramente impediscono l’aggiornamento. La copia di prova deve restare privata: protetta da password e fuori dall’indicizzazione, perché è a tutti gli effetti un secondo sito con gli stessi dati.

Più versioni di PHP-FPM sullo stesso server

Il modo più sicuro per aggiornare non è sostituire PHP, ma affiancare la versione nuova a quella vecchia e spostare i siti uno alla volta. Con PHP-FPM ogni versione ha il proprio servizio e i propri pool, ciascuno in ascolto su un socket diverso, e il web server decide per ogni sito a quale socket inviare le richieste. Su Debian e Ubuntu le versioni non incluse nella distribuzione si installano dal repository di Ondřej Surý; sulle distribuzioni della famiglia Red Hat, dal repository di Remi Collet.

# Debian/Ubuntu: nuova versione con le estensioni usate da WordPress
apt install php8.3-fpm php8.3-mysql php8.3-curl php8.3-gd php8.3-intl \
  php8.3-mbstring php8.3-xml php8.3-zip php8.3-imagick

# Estensioni presenti nella vecchia versione e mancanti nella nuova
diff <(php8.1 -m) <(php8.3 -m)

# Il pool del sito, copiato con un socket nuovo
cp /etc/php/8.1/fpm/pool.d/esempio.conf /etc/php/8.3/fpm/pool.d/esempio.conf
sed -i 's#/run/php/php8.1-fpm-esempio.sock#/run/php/php8.3-fpm-esempio.sock#' \
  /etc/php/8.3/fpm/pool.d/esempio.conf
php-fpm8.3 -t && systemctl reload php8.3-fpm

Nel copiare il pool va controllata ogni riga, perché è lì che vivono le impostazioni di sicurezza: utente e gruppo del pool, open_basedir, disable_functions, limiti di memoria e di upload. Se sono state impostate come descritto nella checklist di hardening, devono esserci anche nella versione nuova, così come le impostazioni globali eventualmente modificate nel php.ini della vecchia versione. Un aggiornamento che perde per strada open_basedir riapre la contaminazione tra siti che il pool separato serviva a impedire.

Schema di un server con NGINX a sinistra e tre siti configurati. Il sito A punta con fastcgi_pass al socket php8.1-fpm-a.sock, gestito dal servizio php8.1-fpm; i siti B e C puntano ai socket php8.3-fpm-b.sock e php8.3-fpm-c.sock del servizio php8.3-fpm. Ogni pool ha il suo utente, open_basedir e disable_functions. Una freccia tratteggiata mostra lo spostamento del sito A: si cambia una riga di fastcgi_pass e si ricarica NGINX, e il ritorno indietro è la stessa operazione al contrario. In basso, un riquadro separato ricorda che WP-CLI e i cron usano il PHP della riga di comando, da indicare esplicitamente con /usr/bin/php8.3.
Due versioni di PHP-FPM affiancate: ogni sito sceglie la sua con una riga nella configurazione del web server, e la riga di comando va allineata a parte.

Il passaggio e il piano di ritorno

Con il pool nuovo pronto e verificato sulla copia di prova, il passaggio in produzione è una riga. Prima si fa un backup completo di file e database, con le cautele descritte nell’articolo sui backup che conservano il malware; poi si cambia il socket nella configurazione del sito e si ricarica il web server. Le richieste nuove vanno alla versione nuova, quelle in corso terminano sulla vecchia.

# /etc/nginx/sites-available/esempio.it, nella location dei file PHP
#   prima: fastcgi_pass unix:/run/php/php8.1-fpm-esempio.sock;
fastcgi_pass unix:/run/php/php8.3-fpm-esempio.sock;

nginx -t && systemctl reload nginx

# Ritorno indietro: ripristinare il socket precedente e ricaricare
sed -i 's#php8.3-fpm-esempio.sock#php8.1-fpm-esempio.sock#' /etc/nginx/sites-available/esempio.it
nginx -t && systemctl reload nginx

Il piano di ritorno funziona solo se il pool vecchio resta attivo e invariato finché la nuova versione non ha dimostrato di reggere: per una o due settimane, con un occhio al log degli errori del pool nuovo. Su Apache con PHP-FPM il principio è identico, cambia solo la direttiva, cioè il socket indicato in SetHandler "proxy:unix:…|fcgi://localhost". Sui pannelli di hosting la scelta della versione per sito è di solito un menu, ma dietro c’è esattamente questo meccanismo.

Nelle prime ore conviene guardare anche il sito dall’esterno, attraverso i log di accesso: un aumento delle risposte con codice 500 su percorsi precisi, come l’endpoint di un plugin o una pagina del checkout, indica un errore che la copia di prova non ha fatto emergere. Il php.ini, inoltre, è distinto per ogni versione: limiti di memoria e di upload, fuso orario ed expose_php = Off, che evita di annunciare la versione nelle intestazioni HTTP, vanno riportati a mano nel file della versione nuova.

# Risposte 5xx dopo il passaggio, per codice e percorso (formato combined)
awk '$9 ~ /^5/ {print $9, $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

# Differenze tra i php.ini di PHP-FPM delle due versioni
diff /etc/php/8.1/fpm/php.ini /etc/php/8.3/fpm/php.ini | grep -E 'memory_limit|upload_max|post_max|expose_php|date.timezone'

Resta la riga di comando, che molti dimenticano. WP-CLI, gli script di manutenzione e i cron del sito usano il PHP predefinito del sistema, che non cambia quando si modifica il pool. Il modo più chiaro è indicare la versione in modo esplicito nelle righe di cron, invece di cambiare il predefinito per tutto il server; e già che si è lì, vale la pena rileggere tutto il crontab dell’utente, dove a volte si nascondono backdoor che nessuno ha mai notato.

# crontab dell'utente del sito: cron di WordPress eseguito con PHP 8.3
*/5 * * * * /usr/bin/php8.3 /usr/local/bin/wp --path=/var/www/esempio.it/htdocs cron event run --due-now --quiet
Percorso di migrazione in sei passi collegati da frecce: inventario di plugin e temi; analisi con PHPCompatibilityWP e testVersion 8.3-; copia di prova servita dalla nuova versione con il log degli errori del pool; backup e passaggio in produzione cambiando fastcgi_pass; osservazione del log per una o due settimane con il pool vecchio ancora attivo; rimozione della versione vecchia quando nessun sito la usa più. Dal passo di osservazione parte una freccia rossa di ritorno indietro al pool vecchio, con l’indicazione che richiede un comando e pochi secondi. Sotto, un promemoria: allineare la versione della riga di comando per WP-CLI e cron, e riportare open_basedir e disable_functions nel pool nuovo.
Dall’inventario alla rimozione della versione vecchia: il ritorno indietro resta possibile finché il pool precedente non viene spento.

Dopo la migrazione

Quando il log del pool nuovo è pulito e nessun sito usa più la versione vecchia, il suo pacchetto va rimosso: una versione di PHP installata e dimenticata è un rischio anche se nessun sito la usa, perché resta eseguibile dalla riga di comando e da eventuali configurazioni rimaste indietro. Da quel momento conviene segnare in calendario la prossima scadenza, con qualche mese di anticipo: rispetto alla corsa degli ultimi giorni, la migrazione pianificata costa molto meno.

Se il sito è fermo da anni su una versione obsoleta, è probabile che non sia l’unico problema: vale la pena controllare anche che i plugin rimasti indietro non abbiano già aperto la porta a qualcuno. Se trovi segni di compromissione, o vuoi affrontare l’aggiornamento su un sito che è già stato infettato, scrivici: ripuliamo file, database e sistema, cron e processi compresi, con una pulizia garantita in 6, 12 o 24 ore per ogni installazione WordPress, e lasciamo il sito su una versione di PHP supportata.

IL PROSSIMO PASSO

Il tuo sito
è infetto?

Ogni ora conta: il malware continua a colpire visitatori e clienti, e Google, l’hosting e le blacklist non aspettano. Scegli il piano, paga online e condividi gli accessi: la bonifica parte subito, a prezzo fisso e con tempi garantiti.

Richiedi la bonifica

Ti porta ai piani: bonifica entro 6, 12 o 24 ore dalla ricezione degli accessi.

MANAGED SERVER S.R.L.info@rimozionemalware.com +39 02 5656 9681

Lunedì – venerdì · 09:30 – 19:30

Preferisci un modulo online?
Contattaci su managedserver.it ↗