Vous êtes passé à Ubuntu 24.04, tout semblait bien, puis votre site a commencé à afficher des 502 Bad Gateway comme s’il répétait un exercice de reprise après sinistre. Vous redémarrez php-fpm, ça tient quelques minutes, puis ça retombe. Vous scrutez les logs Nginx comme s’ils vous devaient de l’argent.
Voici la réalité : les plantages de PHP-FPM sont rarement « mystérieux ». Ils se règlent le plus souvent en trouvant une ligne de journal décisive. Trouvez cette ligne, et vous cesserez de deviner pour commencer à corriger.
La ligne de journal à trouver (et pourquoi elle compte)
Si PHP-FPM « plante », vous voulez la ligne qui dit qui l’a tué ou quel signal l’a arrêté. Pas le 502 de Nginx. Pas l’avertissement WordPress. Pas encore la pile d’app. La ligne que vous devez trouver est l’une de celles-ci :
- Ligne du noyau OOM :
Out of memory: Killed process ... (php-fpm...)ou des entréesoom-kill:. - Ligne de résultat systemd :
Main process exited, code=killed, status=9/KILLoustatus=11/SEGV. - Plantage d’un enfant PHP-FPM :
child ... exited on signal 11 (SIGSEGV)ouchild ... exited with code 255. - Échecs de socket/listen :
unable to bind listening socket,Address already in use,Permission denied.
Ces lignes décident de votre branche de réalité :
- OOM killer ? Arrêtez d’ajuster PHP et commencez à dimensionner la mémoire,
pm.max_childrenet le comportement mémoire par requête. - SIGSEGV ? Considérez-le comme un plantage natif : bug d’extension, cas limite du JIT d’Opcache, mémoire partagée corrompue, ou module mal compilé.
- Status 9/KILL sans OOM ? Probablement watchdog/timeouts systemd, scripts administratifs, limites de cgroup ou limites en container.
- Problèmes de bind/permission sur socket ? C’est une erreur de déploiement/configuration, pas un problème de performance.
Une citation à garder sur un post‑it, parce qu’elle impose de la discipline :
Idée paraphrasée — Werner Vogels : « Tout échoue ; construisez des systèmes qui s’attendent à l’échec et récupèrent vite. »
Nous allons faire la partie « s’attendre » : attrapez la bonne ligne d’abord, puis ouvrez la bonne boîte à outils. Votre budget de disponibilité mérite mieux que « redémarrer et espérer ».
Playbook de diagnostic rapide (premier/deuxième/troisième)
Ceci est le chemin rapide quand la production saigne et que vous n’avez pas de temps pour une danse interprétative des logs.
Première étape : confirmez ce que « planter » signifie aux yeux de systemd
- Vérifiez l’état du service et le dernier code de sortie.
- Décidez : OOM vs signal vs échec de configuration vs problème de dépendance.
Deuxième étape : cherchez le tueur (kernel OOM, kill systemd, ou segfault)
- Recherchez dans
journalctlles termesoom,killed process,SEGV,SIG. - Si vous voyez OOM : arrêtez-vous et corrigez la mémoire et la concurrence. N’« optimisez PHP » qu’après.
Troisième étape : corrélez avec les logs Nginx et des pools pour connaître l’ampleur
- Les erreurs correspondent‑elles à des pics de trafic, des cron, des déploiements ou des sauvegardes ?
- Est‑ce un pool ou tous les pools ?
- Les requêtes sont‑elles lentes/bloquées avant la mort ? Si oui, activez le slowlog et ajustez les timeouts.
C’est tout. Si vous ne trouvez pas la ligne du tueur en 10 minutes, vous cherchez probablement au mauvais endroit ou vos logs sont mal routés.
Faits et contexte intéressants (ce qui explique les bizarreries d’aujourd’hui)
- PHP-FPM n’a pas toujours été « PHP par défaut ». FastCGI Process Manager a commencé comme un jeu de patchs tiers avant d’être fusionné dans le noyau PHP il y a des années, ce qui explique certaines options « héritées » encore présentes.
- systemd a changé le workflow de dépannage. Le journal contient souvent la vérité même quand les logs applicatifs ne disent rien, parce que systemd enregistre codes de sortie, signaux et boucles de redémarrage.
- Le code de sortie 139 est généralement un segfault. Selon la convention Linux, 128 + numéro du signal ; le signal 11 (SIGSEGV) devient 139. Ce n’est pas magique, c’est de l’arithmétique avec des conséquences.
- Le OOM killer est une décision de politique, pas un bug. Le noyau tue un processus pour garder le système vivant. Si c’est PHP-FPM qui est choisi, il vous indique quel processus était « le plus tuable » sous pression.
- Opcache est une fonctionnalité de performance qui vit en mémoire partagée. Quand elle dysfonctionne, elle peut faire tomber plusieurs workers de façon apparemment aléatoire, parce que l’état partagé est le dénominateur commun.
- Les sockets Unix sont plus rapides, mais plus stricts. Les écoutes TCP sont tolérantes côté permissions ; les sockets Unix ne le sont pas. Un mauvais mode/propriétaire et Nginx criera tandis que PHP-FPM prétendra être « en cours d’exécution ».
pm.max_childrenn’est pas « le nombre de cœurs CPU que vous avez ». C’est le nombre de processus PHP concurrents autorisés, et la mémoire est généralement la vraie limite bien avant le CPU.- Les mises à niveau Ubuntu peuvent subtilement modifier des valeurs par défaut. Des builds PHP plus récents, un durcissement du unit systemd ou des bibliothèques OpenSSL/ICU mises à jour peuvent changer le comportement et la stabilité des extensions.
Blague #1 : PHP-FPM, c’est comme un café — si vous ouvrez la porte à des clients illimités avec un seul barista, votre « latence » devient un style de vie.
Tâches pratiques : commandes, sorties et décisions
Vous vouliez des tâches concrètes, pas des bonnes ondes. Chaque tâche ci‑dessous inclut : la commande, une sortie réaliste, ce que ça signifie, et la décision à prendre.
Tâche 1 : Vérifier l’état du service et le code de sortie
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: failed (Result: signal) since Mon 2025-12-29 09:14:11 UTC; 2min 3s ago
Process: 18244 ExecStart=/usr/sbin/php-fpm8.3 --nodaemonize --fpm-config /etc/php/8.3/fpm/php-fpm.conf (code=killed, signal=SEGV)
Main PID: 18244 (code=killed, signal=SEGV)
CPU: 2.114s
Sens : systemd a vu PHP-FPM mourir par SIGSEGV. Ce n’est pas « il a ralenti ». C’est un plantage.
Décision : Enquêter sur les extensions/Opcache/JIT/coredump plutôt que d’ajuster les timeouts d’abord.
Tâche 2 : Extraire les lignes décisives du journal (depuis le dernier boot)
cr0x@server:~$ journalctl -u php8.3-fpm -b -n 200 --no-pager
Dec 29 09:14:11 server systemd[1]: php8.3-fpm.service: Main process exited, code=killed, status=11/SEGV
Dec 29 09:14:11 server systemd[1]: php8.3-fpm.service: Failed with result 'signal'.
Dec 29 09:14:11 server systemd[1]: php8.3-fpm.service: Scheduled restart job, restart counter is at 5.
Dec 29 09:14:11 server systemd[1]: Stopped php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager.
Sens : Segfault répété ; systemd est en boucle de redémarrage.
Décision : Stabiliser en désactivant des fonctionnalités suspectes (JIT), envisager les coredumps, et identifier la requête/extension qui plante.
Tâche 3 : Vérifier les preuves du kernel OOM killer
cr0x@server:~$ journalctl -k -b | grep -Ei 'oom|out of memory|killed process' | tail -n 20
Dec 29 08:57:02 server kernel: oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0,global_oom,task_memcg=/system.slice/php8.3-fpm.service,task=php-fpm8.3,pid=17602,uid=33
Dec 29 08:57:02 server kernel: Out of memory: Killed process 17602 (php-fpm8.3) total-vm:1884320kB, anon-rss:812344kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:2308kB oom_score_adj:0
Dec 29 08:57:03 server kernel: systemd[1]: php8.3-fpm.service: Main process exited, code=killed, status=9/KILL
Sens : PHP-FPM a été tué par OOM, pas « planté au hasard ». Le SEGV de la Tâche 2 peut être un incident différent ; ne mélangez pas les timelines.
Décision : Traiter comme un problème de capacité/concurrence : réduire pm.max_children, corriger les fuites mémoire, ou ajouter RAM/swap. Vérifier aussi si PHP-FPM est dans un cgroup avec un plafond mémoire.
Tâche 4 : Identifier quels pools sont impliqués
cr0x@server:~$ ls -1 /etc/php/8.3/fpm/pool.d/
www.conf
api.conf
worker.conf
Sens : Plusieurs pools. Les pannes peuvent être isolées à un pool avec des réglages différents.
Décision : Restreindre le périmètre : trouver quel pool montre des sorties d’enfants ; ne pas supposer que « PHP est en panne » si un pool est cassé.
Tâche 5 : Vérifier les logs de pool PHP-FPM pour les morts d’enfants et les requêtes lentes
cr0x@server:~$ grep -R --line-number -E 'SIGSEGV|exited on signal|server reached pm\.max_children|slowlog|terminated' /var/log/php8.3-fpm.log | tail -n 30
14592:WARNING: [pool api] child 19044 exited on signal 11 (SIGSEGV) after 3.221146 seconds from start
14593:NOTICE: [pool api] child 19061 started
14610:WARNING: [pool www] server reached pm.max_children setting (20), consider raising it
14625:WARNING: [pool www] child 19104, script '/var/www/site/index.php' (request: "GET /") executing too slow (5.112 sec), logging
Sens : Deux problèmes distincts : le pool api segfaulte ; le pool www est limité en capacité et lent.
Décision : Diviser le travail : stabiliser le pool API (extensions/JIT), puis régler la concurrence/timeouts du pool WWW.
Tâche 6 : Confirmer l’alignement du socket d’écoute et de l’upstream Nginx
cr0x@server:~$ ss -xlpn | grep -E 'php|fpm'
u_str LISTEN 0 4096 /run/php/php8.3-fpm.sock 32564 * 0 users:(("php-fpm8.3",pid=19212,fd=8))
Sens : Le socket existe et est écouté par PHP-FPM.
Décision : Si Nginx affiche encore 502, vérifiez les permissions sur le socket et l’utilisateur/groupe Nginx ; ce n’est pas un socket manquant.
Tâche 7 : Vérifier la propriété et le mode du socket
cr0x@server:~$ stat -c '%n %U:%G %a' /run/php/php8.3-fpm.sock
/run/php/php8.3-fpm.sock www-data:www-data 660
Sens : Le socket appartient à www-data et est group-writeable ; typique si Nginx tourne en tant que www-data.
Décision : Si Nginx tourne sous l’utilisateur nginx, ajoutez‑le au groupe du socket ou ajustez listen.owner/listen.group/listen.mode dans le pool.
Tâche 8 : Inspecter le log d’erreurs Nginx pour l’erreur précise d’upstream
cr0x@server:~$ tail -n 30 /var/log/nginx/error.log
2025/12/29 09:14:12 [error] 20220#20220: *884 connect() to unix:/run/php/php8.3-fpm.sock failed (111: Connection refused) while connecting to upstream, client: 198.51.100.24, server: example, request: "GET / HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example"
2025/12/29 09:14:20 [error] 20220#20220: *901 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 198.51.100.29, server: example, request: "POST /api HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example"
Sens : Première erreur : PHP-FPM n’acceptait pas (arrêt ou redémarrage). Seconde erreur : PHP-FPM a accepté mais n’a pas répondu assez vite.
Décision : Corriger la boucle de crash/redémarrage d’abord. Les timeouts sont souvent le symptôme d’un manque de workers ou d’I/O bloquante.
Tâche 9 : Vérifier si PHP-FPM est plafonné en mémoire par systemd/cgroups
cr0x@server:~$ systemctl show php8.3-fpm -p MemoryMax -p MemoryHigh -p TasksMax -p OOMPolicy
MemoryMax=536870912
MemoryHigh=0
TasksMax=512
OOMPolicy=stop
Sens : Il y a un plafond à 512 MiB pour le service. Sous charge, vous l’atteindrez même si l’hôte a de la RAM libre.
Décision : Augmenter ou supprimer le plafond (prudemment), puis revoir le dimensionnement des pools. Si vous ne l’avez pas défini, auditez qui l’a fait.
Tâche 10 : Mesurer la mémoire par worker pour dimensionner pm.max_children raisonnablement
cr0x@server:~$ ps -o pid,rss,cmd -C php-fpm8.3 --sort=-rss | head -n 8
PID RSS CMD
19244 148320 php-fpm: pool www
19241 142908 php-fpm: pool www
19262 139440 php-fpm: pool api
19251 136112 php-fpm: pool www
19212 31240 php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)
Sens : Les workers sont à ~135–150 MiB RSS chacun sous la charge actuelle. 20 children peuvent représenter ~3 Go rien que pour les workers, plus Opcache et le reste.
Décision : Si vous avez 2–4 Go de RAM, pm.max_children=20 est une illusion. Réduisez les enfants ou la mémoire par requête (appli/limites), ou ajoutez de la RAM.
Tâche 11 : Activer et lire le slowlog PHP-FPM pour attraper « ça a bloqué puis c’est mort »
cr0x@server:~$ sudo grep -nE 'slowlog|request_slowlog_timeout' /etc/php/8.3/fpm/pool.d/www.conf
55:request_slowlog_timeout = 5s
56:slowlog = /var/log/php8.3-fpm-www-slow.log
Sens : Le slowlog se déclenche à 5s. C’est assez agressif pour attraper les requêtes pathologiques sans vous noyer.
Décision : Si vous n’avez pas de slowlog activé pendant les incidents, vous choisissez l’ignorance. Activez‑le dans les pools affectés, puis reproduisez sous charge.
Tâche 12 : Confirmer la version PHP et les modules chargés pour repérer les suspects de crash
cr0x@server:~$ php-fpm8.3 -v
PHP 8.3.6 (fpm-fcgi) (built: Nov 21 2025 10:14:22)
Copyright (c) The PHP Group
Zend Engine v4.3.6, Copyright (c) Zend Technologies
with Zend OPcache v8.3.6, Copyright (c), by Zend Technologies
cr0x@server:~$ php -m | egrep -i 'opcache|redis|imagick|swoole|xdebug'
imagick
opcache
redis
Sens : Opcache est présent (attendu). Imagick est une source fréquente de « crash natif » parce qu’il se lie aux bibliothèques ImageMagick ; redis peut aussi avoir un comportement sensible aux versions.
Décision : Si vous voyez SIGSEGV : désactivez temporairement les extensions à haut risque dans le pool qui plante pour isoler. Le but est d’arrêter le plantage, puis de réintroduire.
Tâche 13 : Vérifier la gestion des coredumps (pour rendre les plantages exploitables)
cr0x@server:~$ coredumpctl list php-fpm8.3 | head
TIME PID UID GID SIG COREFILE EXE
Mon 2025-12-29 09:14:11 UTC 18244 0 0 11 present /usr/sbin/php-fpm8.3
Sens : Un core existe. C’est de l’or pour déboguer les plantages natifs.
Décision : Si possible, extraire une backtrace en staging avec des symboles de debug ; en production, au moins préserver le core et le corréler avec les changements de déploy et les modules activés.
Tâche 14 : Vérifier les limites de descripteurs de fichiers (vecteur sournois d’instabilité)
cr0x@server:~$ systemctl show php8.3-fpm -p LimitNOFILE
LimitNOFILE=1024
Sens : 1024 fichiers ouverts peuvent être trop peu pour des sites chargés (sockets, fichiers, logs, upstreams). Quand vous atteignez ce plafond, vous obtenez des échecs étranges : accepts ratés, opens ratés, parfois des timeouts en cascade.
Décision : Augmentez LimitNOFILE dans un override systemd si vous êtes à l’échelle ; puis vérifiez en runtime.
Tâche 15 : Vérifier les refus AppArmor (Ubuntu adore AppArmor)
cr0x@server:~$ journalctl -k -b | grep -i apparmor | tail -n 10
Dec 29 09:02:41 server kernel: audit: type=1400 apparmor="DENIED" operation="open" profile="/usr/sbin/php-fpm8.3" name="/srv/secrets/api-key.txt" pid=18011 comm="php-fpm8.3" requested_mask="r" denied_mask="r" fsuid=33 ouid=0
Sens : Un processus PHP-FPM s’est vu refuser l’accès. Cela peut causer des erreurs fatales, des tentatives sans fin, ou des défaillances en cascade selon le comportement de l’application.
Décision : Corrigez les chemins et permissions ou ajustez le profil AppArmor. Ne faites pas « chmod 777 » pour résoudre le problème.
Tâche 16 : Valider la configuration avant de redémarrer (arrêtez d’envoyer une syntaxe cassée)
cr0x@server:~$ php-fpm8.3 -t
[29-Dec-2025 09:20:55] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful
Sens : La syntaxe est OK. Ce n’est pas une garantie de sémantique correcte, mais cela évite des redémarrages humiliants.
Décision : Faites de php-fpm -t une étape obligatoire dans les pipelines de déploiement et les hooks pré-restart.
Modes de défaillance sur Ubuntu 24.04 : à quoi ça ressemble
Modèle A : OOM kills (le classique « il redémarre, puis meurt à nouveau »)
Symptômes :
- 502 intermittents sous charge, pire lors de pics de trafic ou de jobs en lot.
status=9/KILLdans systemd,Out of memory: Killed processdans les logs du noyau.- Les workers affichent de grands RSS ; l’utilisation mémoire grimpe jusqu’à la mort soudaine.
Mécanique :
- Chaque worker est un processus avec son propre poids mémoire. Avec
pm=dynamic, la concurrence augmente avec la demande, et la mémoire monte avec elle. - Les requêtes peuvent allouer beaucoup de mémoire pour du JSON, l’hydratation d’ORM, le traitement d’images, la génération de PDF, ou simplement une fuite.
- Si vous exécutez tout sur une VM (web + db + caches), vous organisez un match pour la RAM.
Modèle B : Segfaults (code de sortie 139, SIGSEGV)
Symptômes :
- systemd affiche
status=11/SEGVou les logs montrent des enfants sortant sur SIGSEGV. - Souvent lié à une route spécifique, un type d’upload, une opération image, ou un appel d’extension.
- Peut commencer après une mise à jour : version mineure de PHP, mise à jour de bibliothèques, ou recompilation d’extension.
Mécanique :
- PHP lui‑même est en C. Les extensions sont en C. Un segfault signifie que quelqu’un a touché de la mémoire qu’il ne fallait pas. Le code userland PHP peut le déclencher indirectement.
- Opcache et JIT peuvent amplifier des bugs rares parce qu’ils modifient les chemins d’exécution et les layouts mémoire.
- Des paquets mixtes ou des modules
.soobsolètes causent des incompatibilités ABI ; ça peut tourner un moment puis planter.
Modèle C : Liaison de socket et permissions (pas un plantage, mais ça y ressemble)
Symptômes :
- PHP-FPM ne parvient pas à démarrer : « unable to bind listening socket. »
- Log d’erreur Nginx :
connect() ... failed (13: Permission denied).
Mécanique :
- Fichier socket obsolète ou mauvaise adresse
listen. - Pool tournant sous un utilisateur différent de celui attendu ; Nginx ne peut pas accéder au socket.
- Deux pools essaient de binder le même chemin de socket.
Modèle D : « Pas planté » — juste bloqué ou saturé
Symptômes :
- Nginx indique upstream timed out ; PHP-FPM reste « active (running). »
- Ligne de log :
server reached pm.max_children. - Le slowlog montre la même fonction bouchée : appels DB, appels réseau, I/O filesystem.
Mécanique :
- Vous êtes à court de workers. La file s’accumule. Nginx attend. Les clients partent.
- Ou vous avez des workers, mais ils sont bloqués sur quelque chose d’externe et ne retournent pas au pool.
Correctifs qui tiennent (OOM, segfaults, sockets, limites)
Groupe de correctifs 1 : Empêcher les OOM en dimensionnant la concurrence selon la mémoire
La vérité peu glamour : la plupart des « plantages » PHP-FPM sous charge sont auto‑infligés en autorisant plus de concurrence que la RAM ne peut supporter.
1) Réduire pm.max_children en se basant sur le RSS observé
Utilisez la Tâche 10 pour mesurer le RSS réaliste pendant des requêtes typiques et en pic. Faites ensuite le calcul. Si les workers moyennent 140 MiB RSS et que vous pouvez allouer 1,5 GiB aux workers PHP, vous êtes dans la dizaine d’enfants, pas 30.
Dans le fichier de pool :
cr0x@server:~$ sudo grep -nE 'pm\.max_children|pm\.start_servers|pm\.min_spare_servers|pm\.max_spare_servers' /etc/php/8.3/fpm/pool.d/www.conf
90:pm.max_children = 20
91:pm.start_servers = 4
92:pm.min_spare_servers = 2
93:pm.max_spare_servers = 6
Décision : Baissez pm.max_children à un nombre sûr, puis observez la mise en file et la latence. Mieux vaut mettre en file que mourir.
2) Mettre un plafond sur la mémoire par requête (stratégiquement)
Ne définissez pas memory_limit sur quelque chose d’« énorme » parce qu’une page de rapport en a besoin. C’est ainsi que vous donnez à chaque worker une grenade. Gérer les tâches lourdes de façon asynchrone, ou les isoler dans un pool dédié avec des contrôles stricts.
3) Corriger l’anti-pattern « un pool fait tout »
Créez des pools séparés pour :
- Requêtes web publiques (sensible à la latence)
- Requêtes API (souvent en rafales)
- Admin/cron/background (avide en mémoire, tolérance au délai plus grande)
Cela limite le rayon d’action. Une exportation admin incontrôlée ne devrait pas évincer votre page d’accueil de la mémoire.
4) Revoir les plafonds mémoire systemd si présents
Si MemoryMax est défini (Tâche 9), ajustez‑le avec un override systemd. C’est un changement chirurgical, pas un rituel.
cr0x@server:~$ sudo systemctl edit php8.3-fpm
# (editor opens; add the following)
# [Service]
# MemoryMax=0
# LimitNOFILE=65535
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Active: active (running) since Mon 2025-12-29 09:28:12 UTC; 3s ago
Décision : Si vous supprimez les plafonds, compensez avec des pm.max_children raisonnables et du monitoring, sinon vous déplacerez la panne vers un OOM global sur la VM.
Groupe de correctifs 2 : Arrêter les segfaults en isolant le coupable
Les segfaults finissent par être déterministes. Votre travail est de réduire l’espace de recherche.
1) Désactiver temporairement le JIT si activé
Le JIT peut être rapide ; il peut aussi amplifier des crashs pour des cas limites. Si vous ne savez pas si le JIT est activé, supposez que quelqu’un l’a activé « pour la performance » puis a oublié.
cr0x@server:~$ php -i | grep -i opcache.jit
opcache.jit => tracing => tracing
opcache.jit_buffer_size => 128M => 128M
Désactivez‑le (pour l’instant) dans /etc/php/8.3/fpm/conf.d/10-opcache.ini ou avec un override dédié :
cr0x@server:~$ sudo sed -n '1,120p' /etc/php/8.3/fpm/conf.d/10-opcache.ini
opcache.enable=1
opcache.memory_consumption=256
opcache.jit=tracing
opcache.jit_buffer_size=128M
Décision : Mettez opcache.jit=0 et opcache.jit_buffer_size=0, redémarrez, et voyez si les plantages cessent. Si oui, vous avez trouvé un levier important.
2) Désactiver les extensions à haut risque par pool
Vous pouvez contrôler certaines valeurs INI par pool avec php_admin_value, mais le chargement des modules est généralement global. En pratique, isolez en exécutant une instance FPM séparée ou un pool distinct avec un php.ini différent lorsque possible, ou désinstallez l’extension pendant une fenêtre de maintenance.
Si Imagick est impliqué et que les crashs correspondent aux opérations images, testez en le retirant en staging ou désactivez la feature flag qui le déclenche.
3) Capturer un core dump et une backtrace (la méthode adulte)
Quand le plantage coûte cher, arrêtez de deviner. Utilisez coredumpctl (Tâche 13) et extrayez des informations. Même sans symboles complets, vous pouvez souvent voir le nom du module qui plante.
4) Purger les paquets mixtes / modules obsolètes après une mise à jour
Les mises à niveau Ubuntu peuvent laisser des modules anciens. Assurez‑vous de ne pas charger une extension compilée pour une version mineure différente de PHP.
cr0x@server:~$ php -i | grep -E '^extension_dir'
extension_dir => /usr/lib/php/20230831 => /usr/lib/php/20230831
Décision : Vérifiez que extension_dir correspond à la version API PHP installée et que les modules dans ce répertoire proviennent du même jeu de paquets/build.
Groupe de correctifs 3 : Rendre les sockets et permissions ennuyeux
Les problèmes de socket font perdre du temps parce qu’ils ressemblent à « PHP en panne » alors que PHP-FPM pense être correct.
1) Assurer des sockets d’écoute uniques par pool
Si deux pools écoutent le même chemin de socket, l’un gagne, l’autre échoue, et votre outage devient aléatoire.
cr0x@server:~$ grep -R --line-number '^listen\s*=' /etc/php/8.3/fpm/pool.d/
/etc/php/8.3/fpm/pool.d/www.conf:34:listen = /run/php/php8.3-fpm.sock
/etc/php/8.3/fpm/pool.d/api.conf:34:listen = /run/php/php8.3-fpm.sock
Décision : Corrigez immédiatement : donnez à chaque pool son propre socket (ou port TCP), et mettez à jour les upstreams Nginx en conséquence.
2) Aligner l’utilisateur Nginx avec les permissions du socket
Vérifiez l’utilisateur Nginx :
cr0x@server:~$ grep -nE '^\s*user\s+' /etc/nginx/nginx.conf
1:user www-data;
Décision : Si Nginx tourne en nginx, alors mettez listen.group=nginx (ou ajoutez nginx au groupe www-data), et conservez les permissions 660.
Groupe de correctifs 4 : Empêcher les boucles de restart de devenir des outages
Les boucles de redémarrage systemd sont utiles jusqu’à ce qu’elles ne le soient plus. Si PHP-FPM plante instantanément, systemd continuera d’essayer, consommant CPU, remplissant les logs, et rendant l’expérience Nginx… excitante.
1) Ralentir la boucle de restart pendant le debug
Ajoutez un override systemd avec RestartSec. Cela vous donne du temps et réduit le bruit des logs.
cr0x@server:~$ sudo systemctl edit php8.3-fpm
# [Service]
# RestartSec=5s
Décision : Utilisez‑le comme ceinture de sécurité temporaire, pas comme solution durable. L’objectif n’est pas « redémarrer plus lentement », c’est « arrêter de planter ».
2) Utiliser reload quand c’est sûr, restart quand nécessaire
Reload est plus doux ; restart est un marteau. Pour les changements de config que PHP-FPM peut recharger, faites :
cr0x@server:~$ sudo systemctl reload php8.3-fpm
cr0x@server:~$ journalctl -u php8.3-fpm -n 5 --no-pager
Dec 29 09:33:10 server systemd[1]: Reloaded php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager.
Décision : Si vous suspectez une corruption mémoire ou des problèmes d’extension, restart est plus sûr. Reload ne vous sauvera pas d’un état de processus empoisonné.
Blague #2 : Redémarrer PHP-FPM toutes les cinq minutes n’est pas « auto‑guérissant ». C’est « auto‑apaisant », et ça n’impressionne pas votre rotation on‑call.
Trois mini-histoires du monde de l’entreprise (ce qui foire vraiment)
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
Ils ont migré un stack e‑commerce de taille moyenne vers Ubuntu 24.04 pendant un week‑end. Le plan de changement était solide : nouvelles AMI, blue/green, canary. Lundi matin, le canary avait l’air correct. À midi, les tickets de support ont commencé à affluer : des paiements sporadiques échouaient avec des 502.
L’ingénieur on‑call a supposé une « instabilité réseau » parce que les erreurs étaient intermittentes et groupées autour d’un appel à l’API de paiement. Ils ont traqué TLS upstream, le caching DNS, même le conntrack du firewall. Ils étaient intelligents, occupés, et ils avaient tort.
La véritable piste était dans le journal du noyau : des OOM kills ciblant php-fpm. La nouvelle image de base avait un drop‑in systemd définissant MemoryMax pour « durcir ». Il provenait d’un template de service plus petit où ça avait du sens. Ici, c’était un piège. Sous trafic réel, une poignée de grosses requêtes panier poussait les workers PHP au plafond du cgroup.
Une fois qu’ils ont arrêté de supposer « réseau », la correction a pris une heure : augmenter le plafond mémoire, réduire pm.max_children selon le RSS mesuré, et séparer le pool admin utilisé pour les imports. Ils ont aussi ajouté une alerte sur les événements OOM kill. Pas parce que c’est chic. Parce que c’est la ligne de journal qui coupe court aux disputes.
L’intérêt n’était pas la correction ; c’était le mode de panne. Des 502 intermittents peuvent être purement une question de capacité. Votre intuition tentera de blâmer le réseau. Le noyau vous dira tranquillement que c’est la mémoire.
Mini‑histoire 2 : L’optimisation qui a rejailli
Une autre équipe avait un problème de latence. Les pages étaient lentes après une mise à jour applicative. Quelqu’un a proposé d’activer l’Opcache JIT parce qu’il avait lu que ça pouvait améliorer les charges CPU‑intensives. Ils l’ont activé globalement, augmenté le buffer, et ont proclamé victoire après un benchmark rapide.
Deux jours plus tard, des workers PHP-FPM ont commencé à segfaulter aléatoirement. Pas constamment, pas immédiatement reproductible. Juste assez pour déclencher des redémarrages pendant les heures de pointe, qui sont devenus visibles par les clients. Les logs montraient child exited on signal 11. C’est le genre de ligne qui vous fait contempler le plafond une minute.
Ils ont bien fait ensuite : ils ont reverté le JIT et les plantages ont cessé. Ça ne prouvait pas que le JIT était « mauvais ». Ça prouvait que le changement interagissait avec leur ensemble d’extensions et leur mix de requêtes d’une manière que le benchmark n’avait pas exercée. Le benchmark frappait le chemin heureux. La production frappe toujours le chemin bizarre.
Le postmortem a été franc : les fonctionnalités de performance sont des changements, pas un déjeuner gratuit. Ils ont instauré une politique : activer le JIT seulement par pool, derrière un déploiement contrôlé, et avec monitoring du taux de crash. La chose drôle est que la politique revenait moins cher que l’incident. Ça se passe souvent comme ça.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une société SaaS faisait tourner plusieurs pools PHP : www pour le trafic interactif, api pour les partenaires, et jobs pour les tâches background. Chaque pool avait son propre socket, son propre slowlog, et des timeouts stricts. Ce n’était pas glamour. C’était aussi la raison pour laquelle leur incident a été seulement vaguement gênant.
Un après‑midi, un job background a commencé à générer d’énormes PDF à cause d’un mauvais changement de template. La mémoire par requête a doublé. Le pool jobs a commencé à atteindre son propre pm.max_children et à mettre en file. Les workers ont tourné. Mais le site public est resté disponible.
Parce que le rayon d’impact était contenu, l’on‑call a pu déboguer sans feu client. Le slowlog a capturé des stacks montrant le chemin du générateur de PDF. Ils ont reverté le template, vidé la file de jobs, puis réduit la limite mémoire pour ce pool afin qu’il échoue rapidement la prochaine fois.
La leçon n’était pas « soyez malin ». C’était « segmentez ». Quand PHP-FPM plante, l’isolation est votre amortisseur. Des pools séparés sont des disjoncteurs opérationnels, et ils coûtent presque rien comparés au temps d’arrêt.
Erreurs courantes : symptôme → cause racine → correctif
1) Symptom : Nginx affiche 502 ; PHP-FPM est « active (running) »
Cause racine : Mauvais chemin de socket dans Nginx, ou mismatch de permissions sur le socket, ou Nginx pointe vers un pool qui n’écoute pas.
Correctif : Utilisez ss -xlpn pour confirmer le socket exact ; vérifiez que l’upstream Nginx correspond ; vérifiez le propriétaire/mode avec stat ; alignez listen.owner/listen.group/listen.mode.
2) Symptom : PHP-FPM redémarre sans cesse ; systemd montre status=9/KILL
Cause racine : Kernel OOM kill ou enforcement cgroup MemoryMax.
Correctif : Cherchez dans journalctl -k les lignes OOM ; inspectez systemctl show pour les plafonds mémoire ; réduisez pm.max_children ; corrigez les endpoints gourmands en mémoire ; augmentez la RAM si nécessaire.
3) Symptom : Code de sortie 139 / status=11/SEGV
Cause racine : Plantage natif dans PHP ou une extension, souvent lié à des mises à jour de bibliothèques, Opcache/JIT, Imagick, ou des incompatibilités ABI.
Correctif : Désactivez le JIT ; retirez/désactivez les extensions suspectes ; capturez le core avec coredumpctl ; assurez‑vous d’un jeu de paquets cohérent ; reproduisez en staging avec les mêmes binaires.
4) Symptom : « server reached pm.max_children » et latence en hausse
Cause racine : Pas assez de workers pour la concurrence des requêtes, ou workers bloqués en I/O (DB, réseau, filesystem), donc ils ne retournent pas au pool.
Correctif : Utilisez le slowlog pour trouver les appels bloquants ; corrigez les index DB ou les appels externes ; ajustez pm.max_children seulement après confirmation de marge mémoire ; envisagez caching et jobs asynchrones.
5) Symptom : PHP-FPM ne démarre plus après une mise à jour
Cause racine : Erreur de configuration de pool, sockets d’écoute dupliqués, ou pid/socket obsolète.
Correctif : Lancez php-fpm -t ; grep dans les fichiers de pool pour des listen dupliqués ; supprimez le socket obsolète si nécessaire ; redémarrez proprement.
6) Symptom : « Permission denied » aléatoire sur des fichiers censés être lisibles
Cause racine : Refus AppArmor ou utilisateur/groupe incorrect dans le pool.
Correctif : Vérifiez les logs d’audit du noyau pour des refus AppArmor ; gardez les secrets dans des emplacements approuvés ; ajustez le profil ou le chemin ; n’élargissez pas les permissions à outrance.
7) Symptom : Ça fonctionne pendant des heures, puis ça commence à échouer jusqu’à un redémarrage
Cause racine : Fuite mémoire, fuite de descripteurs, ou fragmentation/pression d’Opcache sous certains patterns de trafic.
Correctif : Suivez la croissance du RSS par worker ; augmentez LimitNOFILE si nécessaire ; ajoutez un reload gracieux périodique seulement si vous comprenez la fuite ; corrigez la cause dans l’app/extension.
Checklists / plan étape par étape
Checklist A : Triage en production (15 minutes)
- Exécutez
systemctl status php8.3-fpm. Notez le statut/signal de sortie. - Récupérez
journalctl -u php8.3-fpm -b. Trouvez la ligne contenantSEGV,KILLou des erreurs de bind. - Recherchez dans les logs du noyau pour OOM :
journalctl -k -b | grep -Ei 'oom|killed process'. - Vérifiez le log d’erreurs Nginx pour le type d’upstream (refus vs timeout vs permission).
- Confirmez l’existence et les permissions du socket :
ss -xlpn,stat. - Identifiez quel pool échoue (nom du pool dans les logs PHP-FPM).
Checklist B : Stabiliser (la journée même)
- Si OOM : réduisez immédiatement
pm.max_childrenet isolez les jobs lourds dans un pool séparé. - Si SEGV : désactivez le JIT (si activé) et retirez temporairement les extensions à risque ; conservez les coredumps.
- Si saturation/timeout : activez le slowlog, paramétrez
request_slowlog_timeout, et confirmez quepm.max_childrenn’est pas trop bas. - Définissez un
request_terminate_timeoutréaliste par pool pour éviter les blocages infinis. - Augmentez
LimitNOFILEsi vous approchez des limites de FD.
Checklist C : Faire en sorte que ça n’arrive plus (cette semaine)
- Ajoutez des alertes sur les événements OOM kill du noyau ciblant PHP-FPM.
- Suivez les métriques des pools PHP-FPM : processus actifs, file d’écoute, max children atteint, percentiles de durée des requêtes.
- Documentez l’intention et le budget de ressources pour chaque pool : web vs api vs jobs.
- Testez les montées de version avec des patterns de trafic représentatifs, pas juste un curl sur la page d’accueil.
- Rendez la validation de config (
php-fpm -t) obligatoire dans les déploiements.
FAQ
Pourquoi Ubuntu 24.04 ferait planter PHP-FPM « plus » qu’avant ?
Ce n’est généralement pas Ubuntu qui « cause » directement ; les mises à jour changent les versions PHP, les bibliothèques liées et les valeurs par défaut systemd. Cela peut exposer des bugs d’extension, des plafonds mémoire, ou des changements de caractéristique de performance.
Quel log lire en premier : Nginx, PHP-FPM ou systemd ?
Commencez par systemd/journal (journalctl -u php8.3-fpm) et les logs du noyau pour OOM. Nginx vous dit le symptôme ; systemd/noyau vous disent la cause.
Que signifie « status=11/SEGV » ?
Le processus est mort d’un segmentation fault (SIGSEGV). Traitez‑le comme un plantage natif : suspectez les extensions, Opcache/JIT, incompatibilités ABI ou problèmes de bibliothèques.
Que signifie « server reached pm.max_children », et dois‑je juste l’augmenter ?
Cela signifie que tous les workers étaient occupés et que les nouvelles requêtes ont été mises en file. L’augmenter peut aider si vous avez de la marge mémoire et que les workers ne sont pas bloqués. Si vous êtes déjà proche des limites mémoire, augmenter convertira la latence en un OOM kill.
Comment dimensionner correctement pm.max_children ?
Mesurez le RSS des workers sous charge réelle (ps trié par RSS), décidez combien de RAM vous pouvez dédier aux workers PHP, puis divisez. Gardez une marge pour l’OS, Nginx, les caches et les pics.
Le swap est‑il une solution valide pour les OOM PHP-FPM ?
Le swap peut prévenir des OOM immédiats, mais il peut aussi transformer votre serveur en machine à latence. Utilisez le swap comme filet de sécurité, pas comme plan de capacité. Si le swap grimpe en usage en conditions normales, vous êtes sous‑dimensionné ou mal configuré.
Une seule mauvaise requête peut‑elle planter tous les workers PHP-FPM ?
Oui. Une requête déclenchant un segfault dans un chemin d’extension partagé peut tuer des workers à répétition. De plus, des problèmes d’état partagé (mémoire partagée d’Opcache) peuvent créer des défaillances corrélées entre workers.
Dois‑je utiliser TCP au lieu des sockets Unix pour éviter les 502 ?
Non. TCP vs socket n’est rarement la cause racine. Utilisez TCP si vous avez besoin de séparation réseau ou de limites conteneur ; sinon, corrigez la configuration de permissions et d’écoute.
Quelle est la manière la plus rapide d’attraper des requêtes PHP lentes ou bloquées ?
Activez request_slowlog_timeout et slowlog par pool, et définissez des timeouts de terminaison raisonnables. Le slowlog vous donne des traces de pile pour « pourquoi ça bloque ? » sans deviner.
systemd peut‑il lui‑même tuer PHP-FPM ?
Oui — via des limites de ressources (MemoryMax, TasksMax), des watchdogs/timeouts dans des configurations inhabituelles, ou des politiques de redémarrage agressives qui interagissent avec une config défaillante. Inspectez avec systemctl show.
Conclusion : prochaines étapes à faire aujourd’hui
Si PHP-FPM plante sur Ubuntu 24.04, arrêtez de le traiter comme la météo. Trouvez la ligne qui nomme le tueur : kernel OOM, signal systemd, ou sortie d’enfant PHP-FPM. Cette unique ligne vous indique quelle boîte à outils ouvrir.
Faites ces étapes suivantes dans l’ordre :
- Récupérez
systemctl statusetjournalctl -u; notez le statut/signal de sortie. - Vérifiez les logs du noyau pour des OOM kills ; si présents, corrigez immédiatement la concurrence vs la mémoire.
- Si SEGV, désactivez le JIT (si activé), isolez les extensions, et préservez les coredumps.
- Séparez les pools par workload et donnez à chaque pool son socket et son budget de ressources.
- Activez le slowlog dans le pool affecté, puis corrigez ce qu’il montre (DB, appels externes, filesystem).
Une fois cela fait, PHP-FPM redevient ce qu’il devrait être : une infrastructure ennuyeuse. Le meilleur type.