En bref : chaque site d'un serveur devrait avoir son propre pool PHP-FPM, avec son propre utilisateur système et son propre socket. La séparation ne devient toutefois efficace que grâce aux détails : droits du socket réservés au serveur web, security.limit_extensions limité à .php, aucune page de statut publiquement accessible, limites propres pour les processus et la durée d'exécution, ainsi que des journaux séparés. Contre PHP2Shell lui-même, seule la mise à jour protège ; les pools limitent la portée d'une attaque.
Un serveur, plusieurs sites, un risque commun
Sur de nombreux serveurs, ce que personne n'avait prévu ainsi s'est développé au fil des années. Le site de l'entreprise, la boutique, une page de campagne de 2019 et un système de test tournent côte à côte, souvent sous le même utilisateur et dans le même pool. Si la page de campagne oubliée est compromise, les données clients de la boutique sont elles aussi exposées.
PHP-FPM, le gestionnaire de processus FastCGI de PHP, résout cela avec des pools : des groupes de processus workers aux paramètres propres. Le principe de base est expliqué dans PHP-FPM expliqué : gestionnaire de processus, pools et workers. La mise en place de pools séparés avec utilisateur dédié, dossier temporaire dédié et open_basedir est décrite dans le guide open_basedir et isolation par site. Cet article va un pas plus loin : comment vérifier les pools existants, et quels paramètres rendent la séparation solide ?
Point important pour la mise en perspective : avec PHP2Shell, les failles CVE-2026-109618 et CVE-2026-109619 se trouvent dans l'interpréteur PHP, que partagent tous les pools d'une version de PHP. Un pool dédié n'empêche pas l'exploitation. Mais il détermine si un attaquant atteint ensuite un site ou tous.
Pools PHP-FPM avec utilisateur dédié : vérifier l'existant
Avant de modifier quoi que ce soit, faites le point. Les commandes suivantes ne font que lire. Les chemins et numéros de version varient selon la distribution :
# Tous les pools avec utilisateur et socket
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf
# Pools en cours d'exécution et leurs utilisateurs
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'
# Sockets et leurs droits
ls -l /run/php/
# Vérifier la configuration (ne modifie rien)
sudo php-fpm8.3 -t
Soyez attentif à trois cas de figure : plusieurs sites dans le même pool, des pools qui tournent sous www-data ou même sous root, et des sockets accessibles en écriture à tous les utilisateurs. Chacun de ces cas sape la séparation.
Si plusieurs versions de PHP tournent sur le serveur, chacune a son propre service FPM avec ses propres répertoires de pools. Vérifiez-les toutes, pas seulement la version que vous avez configurée en dernier. Les anciennes versions avec des pools oubliés sont un risque, car elles ne sont souvent ni entretenues ni redémarrées après les mises à jour. Des indications se trouvent dans Plusieurs versions de PHP sur un serveur. Consignez le résultat dans un petit tableau : site, pool, utilisateur, socket, version de PHP. Cet aperçu est aussi la base de toute modification ultérieure.
Les paramètres les plus importants par pool
L'aperçu suivant s'appuie sur la configuration FPM du manuel PHP. Les valeurs pour les processus et les durées dépendent de votre charge et de votre application :
| Paramètre | Objectif | Recommandation |
|---|---|---|
user, group |
utilisateur des workers | un utilisateur dédié par site, jamais root |
listen.owner, listen.group, listen.mode |
droits sur le socket | uniquement le groupe du serveur web, mode 0660 |
listen.acl_users |
accès par ACL au lieu du propriétaire | n'inscrire que l'utilisateur du serveur web |
security.limit_extensions |
extensions exécutables | limiter à .php (par défaut .php .phar) |
clear_env |
vider les variables d'environnement | laisser à la valeur par défaut yes |
pm.max_children |
nombre maximal de workers | limiter pour qu'un site ne sature pas le serveur |
request_terminate_timeout |
interruption des requêtes bloquées | régler selon la requête légitime la plus longue |
pm.status_path, ping.path |
page de statut et de ping | ne définir que si nécessaire, et sécuriser en interne |
slowlog, request_slowlog_timeout |
journal des requêtes lentes | un fichier propre par pool |
Un extrait pour un pool durci ressemble à ceci :
; /etc/php/8.3/fpm/pool.d/shop.conf (extrait)
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
request_terminate_timeout = 60s
security.limit_extensions = .php
clear_env = yes
slowlog = /var/log/php/shop.slow.log
request_slowlog_timeout = 10s
php_admin_value[error_log] = /var/log/php/shop.error.log
Des paramètres comme memory_limit ou session.upload_progress.enabled se définissent par pool avec php_admin_value et php_admin_flag. Selon le manuel, ces valeurs ne peuvent pas être écrasées par ini_set(). Plus de détails dans php_admin_value : verrouiller les paramètres contre l'écrasement.
Bien situer les pages de statut et le renouvellement des workers
pm.status_path et ping.path ne sont actifs que s'ils sont définis. La page de statut montre les requêtes en cours, les processus et la charge. C'est utile pour la supervision, mais, accessible publiquement, c'est une source d'information inutile pour les attaquants. Ne transmettez ces chemins dans le serveur web que pour des adresses internes. La manière dont Nginx et PHP-FPM coopèrent en toute sécurité est décrite dans Sécuriser Nginx avec PHP-FPM.
pm.max_requests détermine après combien de requêtes un worker redémarre ; la valeur par défaut est 0, c'est-à-dire jamais. Une valeur supérieure à zéro aide contre une consommation de mémoire qui croît lentement. Il n'est pas conçu comme protection contre PHP2Shell : php.net ne le cite pas parmi les mesures transitoires et souligne que seule la mise à jour protège complètement. Après la mise à jour, il faut en outre redémarrer tous les workers de tous les pools, car sinon les anciens processus continuent de tourner avec le code vulnérable.
Tenez à jour un modèle pour les nouveaux pools et créez-y chaque nouveau site. La séparation se fait ainsi automatiquement, au lieu d'être rajoutée des années plus tard.
Erreurs typiques lors du changement
L'erreur la plus fréquente est un nouveau pool qui tourne bien sous un utilisateur dédié, mais dont les fichiers appartiennent toujours à www-data. Le pool ne peut alors pas écrire là où il le doit, et quelqu'un définit des droits trop larges. Clarifiez d'abord quels dossiers doivent être accessibles en écriture. La deuxième erreur concerne les tâches planifiées qui continuent de tourner sous l'ancien utilisateur et créent des fichiers avec un mauvais propriétaire. La troisième est un dossier de sessions commun à tous les pools, qui annule à nouveau la séparation.
Après chaque changement, testez la connexion, les uploads, les caches et les tâches planifiées du site, et surveillez le journal d'erreurs du pool.
Comment PHP SOS aide-t-il concrètement ?
- Nous recensons tous les pools, utilisateurs, sockets et paramètres, et montrons où des sites partagent aujourd'hui des processus ou des droits.
- Nous mettons en place pour chaque site un pool durci, avec droits de socket, limite d'extensions, limites de ressources et journaux propres.
- Nous sécurisons les pages de statut, redémarrons tous les workers de façon contrôlée et testons chaque site.
- Dans le cadre du durcissement de serveur, nous documentons la séparation comme modèle pour les projets futurs.
Questions fréquentes
Pourquoi chaque site devrait-il avoir son propre pool PHP-FPM ?
Un pool dédié avec son propre utilisateur système sépare les sites au niveau des processus et des droits sur les fichiers. Si un site est compromis, le code ne peut pas lire sans difficulté les fichiers des autres. De plus, limites et paramètres peuvent être définis séparément pour chaque site.
Un pool dédié protège-t-il contre PHP2Shell ?
Non. Les failles se trouvent dans l'interpréteur PHP lui-même, que tous les pools partagent. Seule la mise à jour vers une version corrigée, avec redémarrage de tous les workers, protège. Mais des pools séparés limitent la portée d'une attaque réussie.
Que fait security.limit_extensions ?
Selon le manuel PHP, ce paramètre limite les extensions de fichiers que PHP-FPM exécute comme script principal. Par défaut, ce sont .php et .phar. Les limiter à .php empêche par exemple que des fichiers image téléversés soient exécutés comme PHP si le serveur web est mal configuré.
La page de statut de PHP-FPM est-elle dangereuse ?
Elle révèle des détails sur les processus et les requêtes en cours. Si elle est publiquement accessible sans protection, elle facilite l'orientation des attaquants. Selon le manuel, elle n'est active que si pm.status_path est défini. Le serveur web devrait alors restreindre ce chemin aux adresses internes.