Table des matières :
- PHP-FPM en pratique : master, pools, workers et protocole FastCGI
- Flux d’exécution avec Nginx : ce qui se passe entre
locationetphp-fpm.sock - Flux d’exécution avec Apache : modphp n’est pas un plan A, proxyfcgi oui
- Paramètres qui font vraiment la perf :
pm.*, backlog, timeouts et OPcache - Observer et déboguer le flux : status page, slowlog, corrélation Nginx/Apache ↔ FPM
PHP-FPM en pratique : master, pools, workers et protocole FastCGI
PHP-FPM (FastCGI Process Manager) n’est pas « un mode PHP » au sens Apache/modphp ; c’est un gestionnaire de processus qui exécute PHP via la SAPI FPM (fpm-fcgi) en répondant à des requêtes FastCGI envoyées par un serveur web (Nginx, Apache via proxyfcgi) ou un proxy applicatif. La définition officielle est sans ambiguïté : « PHP-FPM (FastCGI Process Manager) is an alternative PHP FastCGI implementation with some additional features useful for heavy-loaded sites » (manuel PHP, section PHP-FPM : manuel PHP, section PHP-FPM). Le point clé côté ops : c’est un daemon (un master) qui pilote un ou plusieurs pools, eux-mêmes composés de processus enfants (workers) qui interprètent le code.
Concrètement, le processus master :
- démarre/arrête les workers selon la politique
pm, - recharge la configuration sans arrêt complet (reload via systemd, ex.
systemctl reload, ou via le mécanisme de reload prévu par la distribution/version), - collecte des métriques (
pm.status_path) et déclenche des mécanismes de sécurité (redémarrage d’urgence, limitations), - isole les environnements (un pool ≠ un autre pool), ce qui est utile pour séparer des applications (ou des parties d’une même boutique) avec des contraintes différentes.
Le flux interne est déterminé par trois blocs de configuration : php-fpm.conf (global), un fichier de pool (souvent /etc/php/8.2/fpm/pool.d/www.conf ou équivalent) et les paramètres PHP (via php.ini + php_admin_value[]/php_value[]). Chaque pool peut avoir un utilisateur (user/group), un endpoint (listen = /run/php/php-fpm.sock ou 127.0.0.1:9000), et une politique d’allocation (pm = static|dynamic|ondemand). En e-commerce (PrestaShop 8/9), on évite de mélanger back-office et front sur le même pool si vous voulez des limites et des timeouts distincts (BO plus « lourd », temps de génération plus variable, risques d’actions admin longues).
Une façon simple de matérialiser cette séparation (front/BO) est de créer deux pools, chacun avec ses limites :
- pool front : beaucoup de requêtes courtes, objectif = stabilité du p95/p99,
request_slowlog_timeoutbas pour attraper les outliers. - pool admin/cron : moins de requêtes mais parfois longues (imports, indexation, exports), objectif = éviter que ces traitements ne pénalisent le front.
Le protocole FastCGI est transporté soit sur socket UNIX (latence faible, pas routable, droits Unix à gérer), soit sur TCP (routable, facile à proxyfier, un peu plus d’overhead, surface d’exposition réseau). À l’exécution, Nginx/Apache envoie un set de variables (SCRIPT_FILENAME, REQUEST_METHOD, QUERY_STRING, etc.), PHP-FPM mappe ces variables à un script sur disque, puis le worker exécute : autoload Composer, bootstrap PrestaShop, dispatch contrôleur Symfony (PrestaShop 9) ou front controller historique, accès DB, rendu Twig/Smarty, puis renvoie un flux HTTP (status + headers + body) encapsulé dans FastCGI.
À garder en tête : FastCGI n’est pas HTTP. Quand vous diagnostiquez des 502/504, il faut raisonner « pipeline » : client → reverse proxy → serveur web → FastCGI → PHP → DB/cache → retour.
Flux d’exécution avec Nginx : ce qui se passe entre location et php-fpm.sock
Avec Nginx, il faut accepter un fait : Nginx ne lit pas .htaccess. Toute règle de réécriture, sécurité, cache, headers, doit être exprimée dans la conf Nginx. Sur PrestaShop, ça implique de transposer les patterns de réécriture (front controller) et de bloquer explicitement les répertoires sensibles. Ce point est souvent la vraie cause des « pages blanches »/404 lors d’une migration Apache → Nginx, plus que PHP-FPM lui-même.
Le flux côté Nginx est généralement :
1) Nginx termine TLS, parse la requête, matche un server puis un location.
2) Pour les URLs « propres », try_files choisit soit un fichier statique réel, soit redirige vers index.php (front controller).
3) Si la requête finit sur un location ~ \.php$, Nginx construit les paramètres FastCGI (via include fastcgi_params; ou fastcgi.conf) et forwarde vers fastcgi_pass.
Exemple minimal (Debian 12, Nginx 1.24+, PHP 8.2/8.3 FPM), avec deux garde-fous indispensables : try_files (évite d’envoyer des fichiers inexistants à PHP-FPM) et SCRIPT_FILENAME correct (évite le classique “Primary script unknown”).
server {
listen 443 ssl http2;
server_name shop.example;
root /var/www/prestashop/public; # PrestaShop 9 : public/ ; PrestaShop 8 : racine
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_read_timeout 60s;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~* \.(?:css|js|jpg|jpeg|png|webp|svg|ico|woff2)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000, immutable";
try_files $uri =404;
}
}
Le diable est dans les détails : fastcgi_params vs fastcgi.conf (selon distributions, la liste des paramètres inclus n’est pas identique), fastcgi_split_path_info (si vous servez des /index.php/xxx, typiquement en legacy), et la gestion de PATH_INFO. Un mauvais couplage SCRIPT_FILENAME/PATH_TRANSLATED peut générer des comportements non déterministes selon frameworks.
Points de vigilance très concrets en PrestaShop :
- Docroot exact : en PrestaShop 9, le
rootpointe souvent verspublic/. Une erreur de docroot donne soit des 404, soit des exécutions sur le mauvaisindex.php. - Ne pas passer des fichiers arbitraires à PHP :
try_filesest votre barrière de base contre des patterns qui finissent en exécution involontaire. - Sécurité des extensions : côté FPM,
security.limit_extensions = .php(valeur par défaut selon l’empaquetage) évite qu’un fichier non prévu soit interprété.
Pour aller plus loin sur le comportement FastCGI côté Nginx (buffers, paramètres, timeouts), la référence utile est la documentation officielle du module FastCGI : documentation officielle du module FastCGI (Nginx)
Mini-scenario (terrain) : une boutique migrée d’Apache vers Nginx « fonctionne » sur la home, mais toutes les pages catégories renvoient 404. Dans 80% des cas, ce n’est pas PHP-FPM : c’est un try_files absent ou mal placé (Nginx ne réécrit pas automatiquement comme un .htaccess PrestaShop), ou une règle location trop spécifique qui intercepte les URLs avant le front controller.
Flux d’exécution avec Apache : modphp n’est pas un plan A, proxyfcgi oui
Sur Apache 2.4, deux architectures coexistent : modphp (PHP dans le processus Apache) et PHP-FPM via modproxyfcgi (Apache reverse-proxy vers FPM). Pour les stacks modernes (HTTP/2, keep-alive massif, MPM event), modphp est rarement un bon compromis : il contraint l’architecture (notamment côté MPM), augmente la mémoire par process, et rend le scaling moins propre. La voie la plus robuste est généralement : Apache mpm_event + proxy_fcgi + PHP-FPM (documentation MPM event : documentation MPM event (Apache)).
Le flux Apache+FPM ressemble à Nginx côté logique, mais il se joue au niveau des handlers Apache :
1) Apache reçoit la requête, applique RewriteRule (souvent via .htaccess en mutualisé) ou mod_rewrite en vhost.
2) Si la ressource est un .php, Apache bascule sur un handler proxy:fcgi://....
3) Apache transmet la requête au socket FPM, récupère la réponse FastCGI, puis renvoie la réponse HTTP au client.
Exemple de vhost (Apache 2.4, PHP-FPM via socket UNIX). Note : à adapter si votre docroot est public/ (PrestaShop 9).
<VirtualHost *:443>
ServerName shop.example
DocumentRoot /var/www/prestashop/public
<Directory /var/www/prestashop/public>
AllowOverride All
Require all granted
</Directory>
# PHP-FPM via mod_proxy_fcgi
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost/"
</FilesMatch>
ProxyTimeout 60
</VirtualHost>
L’avantage opérationnel d’Apache dans l’écosystème PrestaShop historique, c’est .htaccess (réécritures, protections, headers) : beaucoup de boutiques « vivent » sur ces règles, notamment en environnement d’agence ou mutualisé. L’inconvénient, c’est la facilité à laisser dériver la conf (règles dupliquées, surcharge de AllowOverride, coût de lecture/résolution de .htaccess dans des arborescences profondes). Si vous visez une stack strictement maîtrisée (CI/CD infra, IaC, immutable), Nginx est souvent plus prévisible ; si vous devez supporter des environnements hétérogènes (agences, mutualisé, migrations), Apache+FPM reste un choix pragmatique.
Note « terrain » : en France/UE, on voit souvent des architectures hybrides (CDN + reverse proxy + Apache) pour des contraintes de conformité, de latence et de pics (soldes, Black Friday). Dans ces contextes, Apache+FPM fonctionne très bien… à condition d’aligner proprement les timeouts entre toutes les couches (proxy → Apache → FPM).
Paramètres qui font vraiment la perf : pm.*, backlog, timeouts et OPcache
Le goulot d’étranglement « PHP-FPM » n’est presque jamais PHP en soi ; c’est le dimensionnement du pool et la file d’attente. Quand pm.max_children est trop bas ou que le listen.backlog sature, vous obtenez des symptômes typiques : montée du TTFB, 502/504 côté reverse proxy, et surtout un max children reached dans les logs FPM. À l’inverse, surdimensionner max_children peut déclencher de l’OOM-kill ou une pression mémoire qui dégrade MySQL et le cache filesystem, donc une perf globale pire.
Choisir le bon mode pm
Sans ajouter de complexité inutile, le choix static/dynamic/ondemand a un impact réel :
Mode pm |
Comportement | Quand l’utiliser |
|---|---|---|
static |
max_children workers démarrés en permanence |
charge stable, latence prévisible, serveurs dédiés dimensionnés |
dynamic |
ajustement entre pm.min_spare_servers et pm.max_spare_servers |
la plupart des shops : compromis perf/ressources |
ondemand |
démarre à la demande, tue après pm.process_idle_timeout |
environnements à faible trafic, éviter l’idle memory (attention aux pics : latence de spawn) |
En e-commerce, dynamic est souvent le meilleur point de départ : vous gardez des workers chauds, sans payer le coût permanent d’un static trop large.
Dimensionner pm.max_children sans se tromper
La méthode robuste pour estimer pm.max_children est empirique et instrumentée : mesurez la RSS moyenne d’un worker en charge réelle (front + BO + cron) via ps -o rss= -C php-fpm8.2 ou smem, puis appliquez :
pm.max_children ≈ (RAM_disponible_pour_PHP) / (RSS_moyenne_worker)
Exemple réaliste sur PrestaShop (catalogue conséquent, modules tiers, PHP 8.2/8.3) : si un worker stabilise à 180–260 Mo RSS en pic, et que vous allouez 8 Go nets à PHP-FPM, vous êtes entre ~30 et ~45 workers. En pratique, gardez une marge pour le kernel page cache + MySQL + Redis + Nginx/Apache, et séparez si possible les pools (front vs BO). Pour la partie « serveur » (noyau Debian, sysctl, layout disques, etc.), vous pouvez recouper avec l’article interne : Serveur dédié PrestaShop : installation et optimisation Debian pour gros catalogue.
Deux réglages souvent sous-estimés en production :
pm.max_requests: recycle un worker après X requêtes (utile pour limiter l’impact de fuites mémoire de modules/SDK). Exemple : 500–2000 selon votre charge.request_terminate_timeout(pool) : coupe une requête trop longue côté FPM. À coordonner avec les timeouts Nginx/Apache/HAProxy (sinon vous créez des 504 « fantômes »).
Backlog et timeouts : éviter la file d’attente invisible
listen.backlog(pool) = taille de la file d’attente côté socket. Si elle est trop petite et que des pics arrivent, vous verrez de la listen queue sur/fpm-status, et côté Nginx/Apache des erreurs intermittentes.fastcgi_read_timeout(Nginx) /ProxyTimeout(Apache) = temps max attendu pour une réponse. Si ce timeout est plus court que la réalité de vos traitements admin/imports, vous aurez des 504 alors que PHP continue parfois à tourner (selon le cas).
Checklist de cohérence (simple et efficace) :
fastcgi_read_timeout(ouProxyTimeout) ≥max_execution_time(PHP) si vous autorisez réellement des scripts longs.request_terminate_timeout(FPM) ≤ timeout proxy, pour éviter des requêtes qui « tournent dans le vide » après abandon côté client.- timeouts spécifiques (BO, cron) isolés dans un pool dédié, plutôt que d’assouplir le front.
OPcache est l’autre axe non négociable : sans cache opcode correctement dimensionné, vous payez des parse/compile inutiles et vous amplifiez la latence sous charge (surtout avec un grand nombre de fichiers PHP, typique PrestaShop + modules). Les réglages à aligner (mémoire, max_accelerated_files, validate_timestamps, preload éventuel) sont détaillés ici : PHP OPcache : paramètres recommandés pour optimiser les performances. Un point souvent ignoré : un OPcache trop petit provoque des évictions → churn → perf erratique ; ce n’est pas « un peu moins rapide », c’est parfois des p95 qui explosent.
Observer et déboguer le flux : status page, slowlog, corrélation Nginx/Apache ↔ FPM
Sans observabilité, « PHP-FPM est lent » ne veut rien dire. Activez au minimum pm.status_path (et idéalement ping.path) pour inspecter active processes, listen queue et max children reached. Exemple côté pool :
; www.conf
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong
request_slowlog_timeout = 3s
slowlog = /var/log/php8.2-fpm.slow.log
catch_workers_output = yes
Exposez ensuite /fpm-status via Nginx/Apache, restreint à localhost/VPN (sinon vous offrez des infos de capacité à Internet). Côté Nginx :
location = /fpm-status {
allow 127.0.0.1;
deny all;
include fastcgi_params;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Ce que vous devez lire en priorité sur la status page :
listen queue> 0 de façon persistante : saturation (manque de workers, ou requêtes trop longues).max children reachedqui augmente :pm.max_childreninsuffisant ou requêtes bloquées (DB lente, API externe, locks).slow requestsen hausse : la perf se dégrade réellement (et pas seulement un pic de trafic).
Le slowlog est votre meilleur ami pour attribuer un p95 dégradé à un bloc concret : requêtes SQL lentes, rendu template, appel API externe, ou IO disque. Pour des lenteurs DB, il faut corréler avec votre instrumentation MySQL (slow query log, plan d’exécution, index) ; l’article interne PrestaShop MySQL : analyser slow query log et optimiser index sert de base. Et pour la perf « bout en bout » e-commerce (TTFB, CWV, gros catalogues), recoupez avec PrestaShop performance : optimiser gros catalogues et Core Web Vitals.
Une méthode de corrélation rapide (quand « ça rame ») :
1) Regardez les erreurs du serveur web (Nginx error.log / Apache error.log) : 502/504, upstream timed out, etc.
2) Regardez les logs FPM (et/ou journalctl -u php8.2-fpm) : max_children, timeouts, erreurs de script.
3) Consultez /fpm-status : queue et workers actifs.
4) Ouvrez le slowlog : quelle fonction/stack revient le plus ?
5) Si la stack pointe la DB, basculez sur le slow query log et les verrous (InnoDB) côté MySQL.
Pour passer du debug artisanal à la supervision, exportez des métriques : soit via scraping de /fpm-status (avec parsing), soit via un exporter dédié, et alertez sur listen queue > 0 durable, max children reached et augmentation du slow requests. La chaîne Prometheus/Alertmanager est un standard ; l’article interne Prometheus : configuration des alertes, métriques et requêtes PromQL vous donne la mécanique côté alerting. Si vous êtes derrière un reverse proxy type HAProxy, la corrélation des timeouts (client ↔ proxy ↔ webserver ↔ FPM) est critique : un timeout server HAProxy trop agressif peut ressembler à un « crash PHP » alors que c’est juste une requête lente ; voir HAProxy : configuration avancée ACL, health checks et sticky sessions.
Dernier point : pour diagnostiquer un flux d’exécution, évitez de « coller Xdebug en prod ». Utilisez-le en staging avec la même conf FPM et le même front (Nginx/Apache), et reproduisez avec des payloads réalistes. Les guides internes existent pour cadrer l’installation et éviter les faux positifs : Installer Xdebug 3 (CLI + web) proprement et Configurer le debug PHP dans VS Code. Si votre objectif est d’éliminer complètement le modèle « un process PHP par requête », regardez aussi les architectures à workers persistants (avec prudence sur la compatibilité PrestaShop) : FrankenPHP : serveur d’application PHP avec Caddy, HTTP/3 et HTTPS auto et FrankenPHP : stabilité des workers et compatibilité PrestaShop en pratique.
En mise en production, ce qui évite 80% des incidents « PHP-FPM + Nginx/Apache » tient en une checklist opérationnelle : (1) tester la conf webserver (nginx -t / apachectl configtest) avant reload, (2) valider que SCRIPT_FILENAME pointe bien sur le bon docroot (notamment si PrestaShop 9 est servi depuis public/), (3) verrouiller les timeouts cohérents entre proxy/webserver/FPM (proxy_read_timeout, fastcgi_read_timeout, ProxyTimeout, request_terminate_timeout), et (4) instrumenter /fpm-status + slowlog dès le jour 1.
Pour une boutique e-commerce « réelle », ces points deviennent encore plus critiques lors des pics (campagnes, soldes, opérations TV/marketplaces) : l’objectif n’est pas seulement d’être rapide « en moyenne », mais d’éviter la saturation en chaîne (queue FastCGI → timeouts proxy → retries clients → charge amplifiée). Le reste (tuning fin, séparation de pools, chiffrage de capacité) devient alors une itération mesurable, pas une séance de divination.
