Sécurité PHP · 14 min read · Jan 22, 2026

Sécurité PHP-FPM/Nginx dans les environnements d'hébergement partagé (Debian/Ubuntu)

Sécurité PHP-FPM/Nginx dans les environnements d’hébergement partagé (Debian/Ubuntu)

Version 1.0
Auteur : Falko Timme
Suivez-moi sur Twitter

Si vous souhaitez utiliser nginx et PHP-FPM pour des environnements d’hébergement partagé, vous devez vous préoccuper de la sécurité. Dans les environnements Apache/PHP, vous pouvez utiliser suExec et/ou suPHP pour faire exécuter PHP sous des comptes utilisateurs individuels au lieu d’un utilisateur système comme www-data. Il n’y a pas de tel mécanisme pour PHP-FPM, mais heureusement, PHP-FPM nous permet de configurer un “pool” pour chaque site web qui fait exécuter les scripts PHP en tant qu’utilisateur/groupe défini dans ce pool. Cela vous donne tous les avantages de suPHP, et en plus, vous n’avez pas de problèmes de transfert FTP ou SCP car les scripts PHP n’ont pas besoin d’appartenir à un utilisateur/groupe spécifique pour être exécutés en tant qu’utilisateur/groupe défini dans le pool.

Je ne donne aucune garantie que cela fonctionnera pour vous !

1 Remarque préliminaire

J’utilise un vhost appelé www.example.com / example.com ici avec le document root /var/www/www.example.com/web.

Vous devriez avoir une installation LEMP fonctionnelle, comme montré dans ces tutoriels :

  • Installer Nginx avec support PHP5 et MySQL sur Debian Squeeze
  • Installer Nginx avec PHP5 (et PHP-FPM) et support MySQL sur Ubuntu 11.04

Une note pour les utilisateurs d’Ubuntu :

Parce que nous devons exécuter toutes les étapes de ce tutoriel avec des privilèges root, nous pouvons soit préfixer toutes les commandes de ce tutoriel avec la chaîne sudo, soit devenir root tout de suite en tapant

sudo su

2 Ce que nous avons jusqu’à présent

Sur Debian/Ubuntu, le répertoire de pool de PHP-FPM est /etc/php5/fpm/pool.d/ - c’est là que de nouveaux pools seront créés. Le php.ini utilisé par PHP-FPM est /etc/php5/fpm/php.ini. Il y a déjà un pool, www.conf - examinons-le :

vi /etc/php5/fpm/pool.d/www.conf

| ; Démarrer un nouveau pool nommé 'www'. ; la variable $pool peut être utilisée dans n'importe quelle directive et sera remplacée par le ; nom du pool ('www' ici) [www] ; Préfixe par pool ; Il ne s'applique qu'aux directives suivantes : ; - 'slowlog' ; - 'listen' (unixsocket) ; - 'chroot' ; - 'chdir' ; - 'php_values' ; - 'php_admin_values' ; Lorsqu'il n'est pas défini, le préfixe global (ou /usr) s'applique à la place. ; Remarque : Cette directive peut également être relative au préfixe global. ; Valeur par défaut : aucune ;prefix = /path/to/pools/$pool ; L'adresse sur laquelle accepter les requêtes FastCGI. ; Les syntaxes valides sont : ; 'ip.add.re.ss:port' - pour écouter sur un socket TCP à une adresse spécifique sur ; un port spécifique ; ; 'port' - pour écouter sur un socket TCP à toutes les adresses sur un ; port spécifique ; ; '/path/to/unix/socket' - pour écouter sur un socket unix. ; Remarque : Cette valeur est obligatoire. listen = 127.0.0.1:9000 ; Définir la file d'attente listen(2). Une valeur de '-1' signifie illimité. ; Valeur par défaut : 128 (-1 sur FreeBSD et OpenBSD) ;listen.backlog = -1 ; Liste des adresses ipv4 des clients FastCGI qui sont autorisés à se connecter. ; Équivalent à la variable d'environnement FCGI_WEB_SERVER_ADDRS dans le PHP FCGI original ; (5.2.2+). N'a de sens qu'avec un socket d'écoute TCP. Chaque adresse ; doit être séparée par une virgule. Si cette valeur est laissée vide, les connexions seront ; acceptées de n'importe quelle adresse IP. ; Valeur par défaut : toute ;listen.allowed_clients = 127.0.0.1 ; Définir les permissions pour le socket unix, si un est utilisé. Sous Linux, les permissions ; de lecture/écriture doivent être définies pour permettre les connexions d'un serveur web. De nombreux ; systèmes dérivés de BSD permettent des connexions indépendamment des permissions. ; Valeurs par défaut : l'utilisateur et le groupe sont définis comme l'utilisateur en cours ; le mode est défini sur 0666 ;listen.owner = www-data ;listen.group = www-data ;listen.mode = 0666 ; Utilisateur/groupe Unix des processus ; Remarque : L'utilisateur est obligatoire. Si le groupe n'est pas défini, le groupe de l'utilisateur par défaut ; sera utilisé. user = www-data group = www-data ; Choisissez comment le gestionnaire de processus contrôlera le nombre de processus enfants. ; Valeurs possibles : ; static - un nombre fixe (pm.max_children) de processus enfants ; ; dynamic - le nombre de processus enfants est défini dynamiquement en fonction des ; directives suivantes : ; pm.max_children - le nombre maximum d'enfants qui peuvent ; être vivants en même temps. ; pm.start_servers - le nombre d'enfants créés au démarrage. ; pm.min_spare_servers - le nombre minimum d'enfants dans l'état 'inactif' ; (en attente de traitement). Si le nombre ; de processus 'inactifs' est inférieur à ce ; nombre, alors certains enfants seront créés. ; pm.max_spare_servers - le nombre maximum d'enfants dans l'état 'inactif' ; (en attente de traitement). Si le nombre ; de processus 'inactifs' est supérieur à ce ; nombre, alors certains enfants seront tués. ; Remarque : Cette valeur est obligatoire. pm = dynamic ; Le nombre de processus enfants à créer lorsque pm est défini sur 'static' et le ; nombre maximum de processus enfants à créer lorsque pm est défini sur 'dynamic'. ; Cette valeur fixe la limite sur le nombre de requêtes simultanées qui seront ; servies. Équivalent à la directive ApacheMaxClients avec mpm_prefork. ; Équivalent à la variable d'environnement PHP_FCGI_CHILDREN dans le PHP ; CGI original. ; Remarque : Utilisé lorsque pm est défini sur 'static' ou 'dynamic' ; Remarque : Cette valeur est obligatoire. pm.max_children = 50 ; Le nombre de processus enfants créés au démarrage. ; Remarque : Utilisé uniquement lorsque pm est défini sur 'dynamic' ; Valeur par défaut : min_spare_servers + (max_spare_servers - min_spare_servers) / 2 ;pm.start_servers = 20 ; Le nombre minimum souhaité de processus serveurs inactifs. ; Remarque : Utilisé uniquement lorsque pm est défini sur 'dynamic' ; Remarque : Obligatoire lorsque pm est défini sur 'dynamic' pm.min_spare_servers = 5 ; Le nombre maximum souhaité de processus serveurs inactifs. ; Remarque : Utilisé uniquement lorsque pm est défini sur 'dynamic' ; Remarque : Obligatoire lorsque pm est défini sur 'dynamic' pm.max_spare_servers = 35 ; Le nombre de requêtes que chaque processus enfant doit exécuter avant de se régénérer. ; Cela peut être utile pour contourner les fuites de mémoire dans des bibliothèques tierces. Pour ; un traitement de requêtes infini, spécifiez '0'. Équivalent à PHP_FCGI_MAX_REQUESTS. ; Valeur par défaut : 0 ;pm.max_requests = 500 ; L'URI pour voir la page d'état FPM. Si cette valeur n'est pas définie, aucune URI ne sera ; reconnue comme une page d'état. Par défaut, la page d'état affiche les informations suivantes : ; conn acceptées - le nombre de requêtes acceptées par le pool ; ; pool - le nom du pool ; ; gestionnaire de processus - statique ou dynamique ; ; processus inactifs - le nombre de processus inactifs ; ; processus actifs - le nombre de processus actifs ; ; total processus - le nombre de processus inactifs + actifs. ; maximum d'enfants atteints - nombre de fois, la limite de processus a été atteinte, ; lorsque pm essaie de démarrer plus d'enfants (ne fonctionne que pour ; pm 'dynamique') ; Les valeurs de 'processus inactifs', 'processus actifs' et 'total processus' sont ; mises à jour chaque seconde. La valeur de 'conn acceptées' est mise à jour en temps réel. ; Exemple de sortie : ; conn acceptées : 12073 ; pool : www ; gestionnaire de processus : statique ; processus inactifs : 35 ; processus actifs : 65 ; total processus : 100 ; maximum d'enfants atteints : 1 ; Par défaut, la sortie de la page d'état est formatée en tant que text/plain. Passer soit ; 'html' soit 'json' en tant que chaîne de requête renverra la syntaxe de sortie correspondante. ; Exemple : ; http://www.foo.bar/status ; http://www.foo.bar/status?json ; http://www.foo.bar/status?html ; Remarque : La valeur doit commencer par un slash (/). La valeur peut être ; n'importe quoi, mais il peut ne pas être judicieux d'utiliser l'extension .php ou cela ; peut entrer en conflit avec un vrai fichier PHP. ; Valeur par défaut : non défini ;pm.status_path = /status ; L'URI de ping pour appeler la page de surveillance de FPM. Si cette valeur n'est pas définie, aucune ; URI ne sera reconnue comme une page de ping. Cela pourrait être utilisé pour tester de l'extérieur ; que FPM est vivant et répond, ou pour ; - créer un graphique de disponibilité de FPM (rrd ou autre) ; ; - retirer un serveur d'un groupe s'il ne répond pas (équilibrage de charge) ; ; - déclencher des alertes pour l'équipe opérationnelle (24/7). ; Remarque : La valeur doit commencer par un slash (/). La valeur peut être ; n'importe quoi, mais il peut ne pas être judicieux d'utiliser l'extension .php ou cela ; peut entrer en conflit avec un vrai fichier PHP. ; Valeur par défaut : non défini ;ping.path = /ping ; Cette directive peut être utilisée pour personnaliser la réponse d'une requête de ping. La ; réponse est formatée en tant que text/plain avec un code de réponse 200. ; Valeur par défaut : pong ;ping.response = pong ; Le délai d'attente pour servir une seule requête après lequel le processus de travail sera ; tué. Cette option doit être utilisée lorsque l'option ini 'max_execution_time' ne ; stoppe pas l'exécution du script pour une raison quelconque. Une valeur de '0' signifie 'off'. ; Unités disponibles : s (secondes) (par défaut), m (minutes), h (heures), ou d (jours) ; Valeur par défaut : 0 ;request_terminate_timeout = 0 ; Le délai d'attente pour servir une seule requête après lequel un backtrace PHP sera ; enregistré dans le fichier 'slowlog'. Une valeur de '0s' signifie 'off'. ; Unités disponibles : s (secondes) (par défaut), m (minutes), h (heures), ou d (jours) ; Valeur par défaut : 0 ;request_slowlog_timeout = 0 ; Le fichier journal pour les requêtes lentes ; Valeur par défaut : non défini ; Remarque : slowlog est obligatoire si request_slowlog_timeout est défini ;slowlog = log/$pool.log.slow ; Définir la limite de descripteur de fichier ouvert rlimit. ; Valeur par défaut : valeur définie par le système ;rlimit_files = 1024 ; Définir la taille maximale du cœur rlimit. ; Valeurs possibles : 'illimité' ou un entier supérieur ou égal à 0 ; Valeur par défaut : valeur définie par le système ;rlimit_core = 0 ; Chroot à ce répertoire au démarrage. Cette valeur doit être définie comme un ; chemin absolu. Lorsque cette valeur n'est pas définie, chroot n'est pas utilisé. ; Remarque : vous pouvez préfixer avec '$prefix' pour chroot au préfixe du pool ou à l'une ; de ses sous-répertoires. Si le préfixe du pool n'est pas défini, le préfixe global ; sera utilisé à la place. ; Remarque : le chroot est une excellente fonctionnalité de sécurité et doit être utilisé chaque fois ; que possible. Cependant, tous les chemins PHP seront relatifs au chroot ; (error_log, sessions.save_path, ...). ; Valeur par défaut : non défini ;chroot = ; Chdir à ce répertoire au démarrage. ; Remarque : un chemin relatif peut être utilisé. ; Valeur par défaut : répertoire actuel ou / lorsque chroot chdir = / ; Rediriger la sortie stdout et stderr des travailleurs dans le journal d'erreurs principal. Si non défini, stdout et ; stderr seront redirigés vers /dev/null conformément aux spécifications FastCGI. ; Remarque : dans un environnement très chargé, cela peut provoquer un certain retard dans le temps ; de traitement de la page (plusieurs ms). ; Valeur par défaut : non ;catch_workers_output = yes ; Passer des variables d'environnement comme LD_LIBRARY_PATH. Tous les $VARIABLEs sont pris de ; l'environnement actuel. ; Valeur par défaut : environnement propre ;env[HOSTNAME] = $HOSTNAME ;env[PATH] = /usr/local/bin:/usr/bin:/bin ;env[TMP] = /tmp ;env[TMPDIR] = /tmp ;env[TEMP] = /tmp ; Définitions php.ini supplémentaires, spécifiques à ce pool de travailleurs. Ces paramètres ; remplacent les valeurs précédemment définies dans le php.ini. Les directives sont les ; mêmes que le PHP SAPI : ; php_value/php_flag - vous pouvez définir des définitions ini classiques qui peuvent ; être remplacées par l'appel PHP 'ini_set'. ; php_admin_value/php_admin_flag - ces directives ne seront pas remplacées par ; l'appel PHP 'ini_set' ; Pour php_*flag, les valeurs valides sont on, off, 1, 0, true, false, yes ou no. ; Définir 'extension' chargera l'extension partagée correspondante depuis ; extension_dir. Définir 'disable_functions' ou 'disable_classes' ne ; remplacera pas les valeurs php.ini précédemment définies, mais ajoutera la nouvelle valeur ; à la place. ; Remarque : les options INI de chemin peuvent être relatives et seront étendues avec le préfixe ; (pool, global ou /usr) ; Valeur par défaut : rien n'est défini par défaut sauf les valeurs dans php.ini et ; spécifiées au démarrage avec l'argument -d ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f [email protected] ;php_flag[display_errors] = off ;php_admin_value[error_log] = /var/log/fpm-php.www.log ;php_admin_flag[log_errors] = on ;php_admin_value[memory_limit] = 32M |

Comme vous le voyez, ce pool écoute sur le port 9000 sur localhost (127.0.0.1), et il est exécuté en tant qu’utilisateur et groupe www-data.

Jetons un œil à la configuration PHP dans votre vhost :

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

La partie importante est la ligne fastcgi_pass 127.0.0.1:9000; - cela fait passer nginx les requêtes PHP au processus PHP-FPM écoutant sur le port 9000 sur localhost (127.0.0.1) - comme vous vous en souvenez, c’est notre pool défini dans /etc/php5/fpm/pool.d/www.conf ce qui signifie que les scripts PHP sont exécutés en tant qu’utilisateur et groupe www-data.

3 Définir un pool individuel pour chaque site web

Mon site example.com est détenu par l’utilisateur web1 et le groupe client0, donc je veux que mes scripts PHP soient exécutés en tant que cet utilisateur et ce groupe. Par conséquent, je définis un nouveau pool /etc/php5/fpm/pool.d/example.com.conf :

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Comme vous le voyez, je fais écouter ce pool sur le port 9001 au lieu de 9000, et je définis l’utilisateur comme web1 et le groupe comme client0. Vous pouvez définir autant de pools que vous le souhaitez, mais assurez-vous d’utiliser un port inutilisé pour chaque pool (9002, 9003, etc.).

Rechargez PHP-FPM :

/etc/init.d/php5-fpm reload

Maintenant, nous changeons notre configuration vhost pour utiliser le nouveau pool. Tout ce que vous devez changer est le port dans la ligne fastcgi_pass :

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9001; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Rechargez nginx ensuite :

/etc/init.d/nginx reload

C’est tout ! Les scripts PHP sont maintenant exécutés en tant qu’utilisateur web1 et groupe client0.

Vous pouvez rendre PHP encore plus sécurisé en changeant les paramètres PHP individuellement pour chaque vhost. Jetez un œil au bas de /etc/php5/fpm/pool.d/www.conf, il a quelques exemples de comment y parvenir.

Par exemple, vous pourriez définir open_basedir ou disable_functions dans le pool /etc/php5/fpm/pool.d/example.com.conf.

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / php_admin_value[open_basedir] = /var/www/www.example.com:/usr/share/php5:/tmp:/usr/share/phpmyadmin:/etc/phpmyadmin:/var/lib/phpmyadmin php_admin_value[disable_functions] = dl,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source |

Rechargez PHP-FPM :

/etc/init.d/php5-fpm reload

3.1 Utiliser des sockets au lieu de connexions TCP

Jusqu’à présent, nous avons utilisé des connexions TCP pour notre pool PHP-FPM (127.0.0.1:9000, 127.0.0.1:9001, etc.). Cela entraîne un certain surcoût. Heureusement, nous pouvons utiliser des sockets Unix au lieu de connexions TCP pour nos pools et nous débarrasser de ce surcoût. Par conséquent, les sockets Unix sont plus performants que les connexions TCP.

Je veux que les sockets soient créés dans le répertoire /var/run/php5-fpm, donc nous devons d’abord créer ce répertoire :

mkdir /var/run/php5-fpm

Pour utiliser un socket Unix, nous changeons simplement la ligne listen dans notre définition de pool, commentons ou supprimons la ligne listen.allowed_clients (n’a de sens que pour les connexions TCP), et ajoutons les lignes listen.owner (définit le propriétaire du socket), listen.group (définit le groupe du socket), et listen.mode (définit les permissions du socket) :

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = /var/run/php5-fpm/example.com.sock ;listen.allowed_clients = 127.0.0.1 listen.owner = web1 listen.group = client0 listen.mode = 0660 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Rechargez PHP-FPM ensuite :

/etc/init.d/php5-fpm reload

Jetez un œil au répertoire /var/run/php5-fpm :

ls -l /var/run/php5-fpm

Vous devriez trouver le socket example.com.sock là avec les permissions 0660, appartenant à l’utilisateur web1 et au groupe client0 :

root@server1:~# ls -l /var/run/php5-fpm  
 total 0  
 srw-rw---- 1 web1 client0 0 2011-09-21 11:08 example.com.sock  
 root@server1:~#

Enfin, nous devons changer la ligne fastcgi_pass dans notre vhost nginx en fastcgi_pass unix:/var/run/php5-fpm/example.com.sock; :

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php5-fpm/example.com.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Rechargez nginx ensuite :

/etc/init.d/nginx reload

C’est tout !

4 Liens

À propos de l’auteur

Falko Timme est le propriétaire de Timme Hosting (hébergement web nginx ultra-rapide). Il est le principal responsable de HowtoForge (depuis 2005) et l’un des développeurs principaux d’ISPConfig (depuis 2000). Il a également contribué au livre O’Reilly “Administration système Linux”.

Share: X/Twitter LinkedIn

Recevez de nouveaux articles dans votre boîte de réception.

Aucun spam. Désabonnez-vous à tout moment.