Prouver une mise à niveau PHP avant de basculer, avec un retour arrière prêt
Une nouvelle branche PHP sans risque : installation côte à côte, lint et scan du code, trace de ce que le trafic charge vraiment, puis changement d'un seul socket.

La plupart des mises à niveau PHP échouent de la même façon : la nouvelle version est installée par-dessus l'ancienne, la page d'accueil se charge, tout le monde se détend, et le premier administrateur qui ouvre un écran rarement utilisé le lendemain matin obtient une page blanche. Voici la méthode que nous utilisons pour trouver ces problèmes sur l'établi, avant la bascule, avec un chemin de retour qui prend quelques secondes.
Pourquoi les mises à niveau continuent de casser des choses
Chaque branche de PHP retire des fonctions, resserre la gestion des types, et transforme d'anciens avertissements en erreurs fatales. Une application qui a grandi au fil des années porte des chemins de code que personne n'a exercés depuis longtemps : un export, un ancien module de paiement, un rapport d'administration. Un smoke test des pages publiques ne les atteindra jamais.
Les applications commerciales ajoutent une seconde couche. Les fichiers protégés par un encodeur ne s'exécutent que si le loader de la nouvelle branche PHP supporte le format avec lequel ils ont été encodés, et c'est une propriété de chaque fichier, pas de la page marketing de l'application.
Première étape : installer côte à côte, jamais par-dessus
Installez la PHP cible à côté de l'actuelle. Sur RHEL et ses reconstructions, les software collections de Remi rendent cela simple : chaque branche vit dans son propre préfixe, avec son propre service FPM et son propre socket. Créez un pool pour le site sur la nouvelle version, identique à l'ancien pool à l'exception du binaire et du chemin du socket.
Rien n'a encore changé pour les visiteurs. Les deux versions tournent, et le serveur web pointe toujours vers l'ancien socket.
Deuxième étape : passer chaque fichier au lint avec le nouveau binaire
Le test le moins cher est de demander à la nouvelle PHP d'analyser chaque fichier :
NEW_PHP=/path/to/new/php
find /var/www/app -name '*.php' -print0 \
| xargs -0 -n1 -P8 "$NEW_PHP" -l 2>&1 \
| grep -v '^No syntax errors'
Cela attrape la syntaxe que la nouvelle branche n'accepte plus. Cela n'attrape pas les fonctions retirées ni les comportements modifiés, parce que ceux-là n'échouent qu'à l'exécution. Pour cela, lancez un scan statique de compatibilité, par exemple PHP_CodeSniffer avec le standard PHPCompatibility, réglé sur votre branche cible :
phpcs -p --standard=PHPCompatibility --runtime-set testVersion <target> \
--extensions=php /var/www/app
Attendez-vous à du bruit venant des bibliothèques embarquées et du code mort. La liste est une carte, pas un verdict.
Troisième étape : savoir quels fichiers le trafic charge vraiment
Une application peut contenir des dizaines de milliers de fichiers PHP, dont une fraction seulement s'exécute un jour. Vous voulez que l'effort de compatibilité soit dépensé sur cette fraction, et vous voulez être sûr qu'elle inclut les parcours des utilisateurs connectés et des administrateurs.
OPcache le sait déjà. Après une journée de travail normale sur la version actuelle, avec des personnes qui se connectent et des administrateurs qui font leur travail, demandez-lui quels scripts il a compilés :
<?php
// protect this file: admin network only, and delete it afterwards
$s = opcache_get_status(true);
foreach (array_keys($s['scripts']) as $f) { echo $f, "\n"; }
Enregistrez la liste, comparez-la aux résultats du lint et du scan, et les problèmes qui seront réellement rencontrés remontent en tête. Les fichiers qui apparaissent dans le scan mais jamais dans la trace sont des candidats à la suppression, pas à la correction.
Quatrième étape : lire les en-têtes des fichiers encodés
Pour les logiciels commerciaux encodés, vérifiez chaque fichier plutôt que de faire confiance à une bannière de compatibilité. Les fichiers encodés portent un en-tête en texte clair qui nomme l'encodeur et le format :
grep -rl --include='*.php' 'ionCube Loader' /var/www/app \
| while read -r f; do head -c 400 "$f" | strings | head -3; done | sort | uniq -c
Regroupez les fichiers par format d'encodeur, puis confirmez que le loader de votre branche PHP cible exécute ce format. Un module encodé il y a des années pour une ancienne branche est exactement le fichier qui refusera de se charger sur la nouvelle, et il vaut mieux l'apprendre d'un en-tête que d'un client.
Cinquième étape : répéter sur le nouveau pool
Pointez un hostname de test, ou votre propre IP via une règle sur un header, vers le nouveau socket pendant que la production reste sur l'ancien. Parcourez les chemins issus de la trace OPcache, avec une attention particulière à ceux que seuls les utilisateurs connectés et les administrateurs atteignent, et surveillez le journal d'erreurs du nouveau pool pendant ce temps :
tail -F /var/log/php-fpm/app-new-error.log | grep -E 'Fatal|Uncaught|Deprecated'
Les avis de dépréciation ne sont pas des échecs aujourd'hui, mais ils sont la liste pour la prochaine mise à niveau. Notez-les.
Sixième étape : changer une ligne, garder l'ancien pool en marche
La bascule est un seul changement dans le serveur web : le socket FastCGI.
# fastcgi_pass unix:/run/php-fpm/app-old.sock;
fastcgi_pass unix:/run/php-fpm/app-new.sock;
nginx -t && systemctl reload nginx
Le retour arrière est la même modification à l'envers, appliquée en quelques secondes, sans changement de paquets et sans redémarrage d'autre chose que la configuration du serveur web. Convenez de l'horaire avec le propriétaire du site, surveillez les taux d'erreur et les temps de réponse pendant la première heure, et gardez l'ancien pool en marche pendant une période de rodage assez longue pour couvrir les tâches hebdomadaires et mensuelles. Ne le retirez qu'ensuite.
Sur une stack à plusieurs nœuds, faites-le un nœud web à la fois derrière le load balancer, pour que même une mauvaise surprise n'atteigne qu'une fraction du trafic pendant quelques minutes.
Septième étape : garder cron et la CLI au même pas
Le serveur web n'est pas le seul à exécuter PHP. Les tâches planifiées, les queue workers et les outils en ligne de commande appellent souvent un chemin de binaire codé en dur. Trouvez-les :
grep -rE 'php[0-9]*( |$)|/bin/php' /etc/cron* /var/spool/cron /etc/systemd/system 2>/dev/null
Passez-les sur le nouveau binaire dans la même fenêtre de changement, ou délibérément après, mais jamais par accident.
Votre checklist
- La nouvelle PHP installée à côté de l'ancienne, avec son propre pool et son propre socket.
- Chaque fichier passé au lint avec le nouveau binaire, et un scan de compatibilité pour la branche cible.
- Une trace OPcache de ce que le trafic réel charge, parcours des utilisateurs connectés et des administrateurs compris.
- Les fichiers encodés regroupés par format et confrontés au nouveau loader.
- Une répétition sur le nouveau pool avec son journal d'erreurs ouvert.
- Une bascule d'une seule ligne, l'ancien pool conservé jusqu'à la fin du rodage, et cron déplacé délibérément.
Si vous avez une mise à niveau que vous repoussez, nous serons heureux de la planifier avec vous.
Related
- /what-we-do/data-web/php-upgrades-security-patching/
- /what-we-do/data-web/performance-audits/
- /what-we-do/data-web/high-availability-web-tiers/
Publié le 15 septembre 2026.
Toutes les notes de terrain