Votre page se charge. Jusqu’au moment où elle ne se charge plus. Puis nginx renvoie un 502 et votre canal d’incident s’illumine comme un sapin de Noël fait de fatigue d’astreinte.
Vous redémarrez des choses. Parfois ça « marche ». Parfois non. Le load balancer continue de retenter, ce qui est sa manière de prolonger votre supplice.
Sur Debian 13, un mode de défaillance très spécifique revient souvent sur le terrain : PHP‑FPM est sain, nginx est sain, mais le socket Unix entre eux
ne l’est pas. Pas cassé. Pas manquant. Juste… inaccessible. Un correctif d’une ligne pour les permissions mettra fin à des heures de tâtonnements — si vous comprenez ce que vous corrigez vraiment.
Le dossier : ce que signifient vraiment les « permissions du socket »
Quand nginx parle à PHP‑FPM, il le fait généralement de deux manières : TCP (127.0.0.1:9000) ou un socket de domaine Unix
(quelque chose comme /run/php/php8.3-fpm.sock). Les sockets Unix sont plus rapides à joindre, évitent l’exposition de ports, et
sont d’une simplicité redoutable à raisonner sur un hôte unique. Jusqu’à ce qu’ils ne le soient plus.
Un socket Unix est un objet de système de fichiers. Cela signifie que les permissions Linux s’appliquent. Et parce qu’il se trouve sous /run,
il est sur tmpfs : recréé au démarrage, souvent recréé au démarrage du service, et parfois « utilement » recréé par des unités systemd.
Vous ne « chmodmez pas le socket une fois » et n’appelez pas cela fini. Vous devez vous arranger pour que le bon propriétaire/groupe/mode existe à chaque création du socket.
Le point douloureux récurrent sur Debian 13 : les paquets sont raisonnables, mais votre configuration locale (ou une vieille cookbook)
suppose que le socket est possédé par www-data:www-data avec le mode 0660. Pendant ce temps, votre worker nginx s’exécute en tant que
www-data mais PHP‑FPM peut être configuré différemment, ou les permissions du répertoire du socket n’autorisent pas la traversée,
ou systemd vous devance avec un comportement par défaut qui rétablit les permissions après des redémarrages.
Le résultat final est généralement l’un de ces cas :
connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied)connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory)upstream prematurely closed connection(souvent un problème différent, mais peut être adjacent aux permissions lors de churn)
Notre travail est de transformer ces chaînes en arbre de décision, pas en séance de spiritisme.
Playbook de diagnostic rapide (vérifier 1/2/3)
1) Confirmer que nginx échoue à se connecter à un socket Unix (et non à PHP lui‑même)
La première question est toujours : « Est‑ce un problème de transport ou un problème applicatif ? »
Si nginx ne peut pas se connecter au socket, PHP n’a même pas la chance de vous décevoir.
2) Inspecter l’objet socket et son répertoire parent
Si le socket existe, vérifiez la propriété/le mode. Ensuite vérifiez les permissions du répertoire. Linux a besoin du bit exécution (« traversée ») sur chaque
composant de répertoire parent, pas seulement des permissions de lecture sur le fichier socket.
3) Vérifier les réglages du pool PHP‑FPM qui contrôlent la création du socket
Les permissions du socket ne sont pas définies par magie. Elles proviennent de la configuration du pool : listen, listen.owner,
listen.group, listen.mode. Si ceux‑ci ne sont pas définis, des valeurs par défaut s’appliquent — parfois pas celles que vous imaginez.
4) Ensuite seulement : vérifier AppArmor/SELinux et systemd‑tmpfiles
En terre Debian, AppArmor est plus courant que SELinux. Mais ne commencez pas par là.
Commencez par le banal. La plupart des 502 sont ennuyeux, et l’ennuyeux se corrige.
Faits et contexte : pourquoi Debian 13 donne l’impression que c’est nouveau
Quelques faits concrets et points de contexte historique aident à expliquer pourquoi ce « petit correctif » continue de mordre les équipes lors des migrations et mises à niveau :
- Les sockets de domaine Unix sont antérieurs à la plupart des piles web. Ils proviennent des anciens mécanismes IPC d’Unix et se comportent comme des objets de système de fichiers, pas des ports réseau.
/runa remplacé/var/rundans les distributions modernes. C’est un tmpfs conçu pour l’état d’exécution. Pratique pour le nettoyage ; pénible pour les habitudes « j’ai chmodé une fois ».- Debian a standardisé les emplacements des sockets PHP‑FPM. Le chemin typique
/run/php/phpX.Y-fpm.sockest cohérent, mais la cohérence expose la dérive : d’anciennes configs nginx pointent encore vers des chemins obsolètes. - Les pools PHP‑FPM contrôlent le mode du socket au moment de la création. Une fois créé, les changements de permission externes sont éphémères : un redémarrage du service recrée le socket et vos modifications manuelles disparaissent.
- nginx et PHP‑FPM tournent souvent sous des utilisateurs différents pour de bonnes raisons. Vous pouvez exécuter nginx en
www-datamais les pools PHP‑FPM sous des utilisateurs par site pour l’isolation ; le socket a besoin d’un groupe partagé ou d’une stratégie ACL. - systemd a changé l’histoire de la « propriété au démarrage ». tmpfiles et le sandboxing des unités peuvent appliquer des règles de système de fichiers dont vous ignoriez l’existence, surtout en cas d’overrides.
- Les profils AppArmor sont devenus plus courants et plus stricts. Un socket peut être parfaitement typé côté système de fichiers et pourtant bloqué par une politique de confinement, surtout avec des chemins personnalisés.
- 502 est une erreur parapluie nginx. Elle ne signifie pas « PHP est en panne ». Elle signifie « l’upstream a échoué ». Parfois l’upstream, c’est juste « un socket que vous ne pouvez pas ouvrir ».
- Les erreurs de permission sont déterministes mais semblent intermittentes sous charge. Si vous avez plusieurs workers nginx, des redémarrages progressifs, ou plusieurs pools, vous pouvez obtenir du « ça marche parfois » alors que vous tapez en réalité différents upstreams.
Une idée paraphrasée de Gene Kim (auteur DevOps/fiabilité) : Améliorer le flux, c’est enlever de petites contraintes qui étouffent silencieusement tout le système.
Les permissions de socket sont une petite contrainte avec un grand rayon d’impact.
Blague #1 : Les sockets Unix sont comme des portes de bureau — si vous n’êtes pas sur la bonne liste de badge, vous serez « bad gateway » pour tout le monde à l’intérieur.
Comment les 502 liés aux sockets PHP‑FPM se manifestent réellement
Les problèmes de permission de socket n’apparaissent que rarement comme une bannière claire « permission denied » dans votre navigateur.
Ils apparaissent sous forme de 502, de pics aléatoires de latence (dus aux retries), et beaucoup de temps perdu à regarder du code PHP qui n’a jamais été exécuté.
Les trois formes courantes
-
Permission denied (errno 13) : nginx voit le chemin du socket, mais ne peut pas l’ouvrir. Typiquement propriétaire/groupe/mode incorrect,
ou permissions d’exécution manquantes sur le répertoire contenant le socket. -
No such file or directory (errno 2) : nginx pointe vers un chemin de socket qui n’existe pas (mauvaise version, mauvais nom de pool),
ou PHP‑FPM ne l’a pas créé (échec de démarrage,listenmal configuré, ou répertoire manquant). -
Connection refused / upstream timed out : peut arriver avec des backends TCP ou un PHP‑FPM surchargé. Avec les sockets, il s’agit plus souvent
d’une saturation du backlog ou de PHP‑FPM qui n’accepte pas les connexions assez rapidement.
Pourquoi « ça marchait hier »
Si votre socket est sous /run, un reboot le réinitialise. Un redémarrage de service le recrée. Une mise à jour de paquet peut recharger des unités.
Si votre « correctif » était manuel (chmod sur le socket), ce n’était jamais un correctif ; c’était une trêve temporaire.
Tâches sur le terrain : commandes, sorties et décisions (12+)
Voici les tâches que j’exécute quand quelqu’un dit « nginx 502 après mise à niveau Debian 13 » et que l’erreur sent le socket.
Chaque tâche inclut une commande, à quoi ressemble une sortie typique, et quelle décision en découle.
Task 1: Confirmer la chaîne d’erreur exacte nginx
cr0x@server:~$ sudo tail -n 30 /var/log/nginx/error.log
2025/12/30 09:21:44 [crit] 1842#1842: *912 connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 203.0.113.10, server: example.internal, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/run/php/php8.3-fpm.sock:", host: "example.internal"
Ce que ça signifie : nginx a atteint le chemin du système de fichiers, a essayé de se connecter, le noyau a répondu « non ».
Décision : arrêtez de regarder le code PHP ; inspectez la propriété du socket et les permissions du répertoire.
Task 2: Valider ce que nginx pense être l’upstream
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R --line-number -E 'fastcgi_pass|upstream' /etc/nginx | head
/etc/nginx/sites-enabled/app.conf:24: fastcgi_pass unix:/run/php/php8.3-fpm.sock;
Ce que ça signifie : La configuration active pointe vers ce socket.
Décision : si le chemin est incorrect (ancienne version de PHP, pool erroné), corrigez nginx d’abord. Si c’est le bon chemin, poursuivez.
Task 3: Vérifier que PHP‑FPM tourne et quelle unité est pertinente
cr0x@server:~$ systemctl status php8.3-fpm --no-pager
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
Active: active (running) since Tue 2025-12-30 09:10:03 UTC; 12min ago
Docs: man:php-fpm8.3(8)
Process: 1024 ExecStartPost=/usr/lib/php/php-fpm-socket-helper install /run/php/php8.3-fpm.sock /etc/php/8.3/fpm/pool.d/www.conf 83 (code=exited, status=0/SUCCESS)
Main PID: 1012 (php-fpm8.3)
Tasks: 8 (limit: 18925)
Memory: 44.2M
CPU: 2.021s
CGroup: /system.slice/php8.3-fpm.service
├─1012 "php-fpm: master process (/etc/php/8.3/fpm/php-fpm.conf)"
├─1018 "php-fpm: pool www"
└─1019 "php-fpm: pool www"
Ce que ça signifie : Le service est actif ; il a même exécuté un helper post‑start lié aux sockets.
Décision : l’échec d’upstream est probablement lié aux permissions/chemin, pas à « php‑fpm mort ».
Task 4: Inspecter l’objet socket lui‑même
cr0x@server:~$ sudo ls -l /run/php/php8.3-fpm.sock
srw-rw---- 1 root root 0 Dec 30 09:10 /run/php/php8.3-fpm.sock
Ce que ça signifie : Le socket est possédé par root:root avec le mode 660.
nginx s’exécutant en www-data ne peut pas se connecter sauf s’il est dans le groupe root (ce qui ne devrait pas être le cas).
Décision : définir correctement listen.owner/listen.group (ou ACL) pour que le socket soit accessible.
Task 5: Confirmer l’utilisateur des workers nginx
cr0x@server:~$ ps -o user,group,comm -C nginx
USER GROUP COMMAND
root root nginx
www-data www-data nginx
www-data www-data nginx
www-data www-data nginx
Ce que ça signifie : Les workers s’exécutent en www-data.
Décision : le socket doit être joignable par www-data (en tant qu’utilisateur ou groupe), ou vous devez changer volontairement l’utilisateur nginx.
Task 6: Vérifier les permissions du répertoire /run/php
cr0x@server:~$ sudo namei -l /run/php/php8.3-fpm.sock
f: /run/php/php8.3-fpm.sock
drwxr-xr-x root root /
drwxr-xr-x root root run
drwxr-x--- root www-data php
srw-rw---- root root php8.3-fpm.sock
Ce que ça signifie : nginx (www-data) peut traverser /run/php parce que le groupe est www-data et que le mode inclut x.
Mais le socket lui‑même est root:root, donc l’accès échoue toujours.
Décision : corriger la propriété/le mode du socket, pas le répertoire (ici le répertoire est déjà raisonnable).
Task 7: Localiser la configuration du pool PHP‑FPM qui définit les paramètres listen
cr0x@server:~$ sudo grep -R --line-number -E '^listen(\.| =)|^user =|^group =' /etc/php/8.3/fpm/pool.d
/etc/php/8.3/fpm/pool.d/www.conf:31:user = www-data
/etc/php/8.3/fpm/pool.d/www.conf:32:group = www-data
/etc/php/8.3/fpm/pool.d/www.conf:41:listen = /run/php/php8.3-fpm.sock
/etc/php/8.3/fpm/pool.d/www.conf:42:listen.owner = root
/etc/php/8.3/fpm/pool.d/www.conf:43:listen.group = root
/etc/php/8.3/fpm/pool.d/www.conf:44:listen.mode = 0660
Ce que ça signifie : Le pool crée explicitement un socket possédé par root. C’est le bug, pas un mystère.
Décision : définir listen.owner/listen.group vers un user/groupe que nginx peut utiliser, généralement www-data.
Task 8: Valider que la configuration PHP‑FPM se parse correctement
cr0x@server:~$ sudo php-fpm8.3 -t
[30-Dec-2025 09:24:10] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful
Ce que ça signifie : Pas d’erreurs de syntaxe.
Décision : sûr de redémarrer PHP‑FPM après changements ; si ça échoue, ne redémarrez pas en production tant que ce n’est pas corrigé.
Task 9: Redémarrer PHP‑FPM et vérifier que la propriété du socket a changé
cr0x@server:~$ sudo systemctl restart php8.3-fpm
cr0x@server:~$ sudo ls -l /run/php/php8.3-fpm.sock
srw-rw---- 1 www-data www-data 0 Dec 30 09:25 /run/php/php8.3-fpm.sock
Ce que ça signifie : Le socket est désormais possédé par www-data, mode 660.
Décision : nginx devrait pouvoir se connecter. Ensuite, validez avec une requête et les logs.
Task 10: Confirmer que la config nginx est valide et recharger
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
Ce que ça signifie : nginx a accepté la config et a rechargé sans couper les connexions.
Décision : si vous avez modifié des upstreams, c’est obligatoire ; sinon vous testez la mauvaise réalité.
Task 11: Reproduire et vérifier une requête FastCGI réussie
cr0x@server:~$ curl -sS -D- http://127.0.0.1/index.php -o /dev/null | head -n 8
HTTP/1.1 200 OK
Server: nginx
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Ce que ça signifie : nginx a atteint PHP‑FPM avec succès.
Décision : fermer l’incident, puis rendre le correctif durable (tmpfiles/overrides) pour qu’il survive aux redémarrages et reboots.
Task 12: Si ça échoue toujours, tester la connexion au socket en tant qu’utilisateur nginx
cr0x@server:~$ sudo -u www-data bash -lc 'php -v >/dev/null; test -S /run/php/php8.3-fpm.sock && echo "socket exists"; cat /run/php/php8.3-fpm.sock'
socket exists
cat: /run/php/php8.3-fpm.sock: No such device or address
Ce que ça signifie : L’utilisateur peut voir le socket. L’erreur cat est normale pour les sockets ; ce n’est pas un fichier que l’on lit.
Décision : la visibilité est OK ; si nginx échoue encore, regardez du côté d’AppArmor ou d’un chemin nginx incorrect (ou plusieurs pools).
Task 13: Chercher des refus AppArmor qui bloquent l’accès
cr0x@server:~$ sudo journalctl -k --since "30 min ago" | grep -i apparmor | tail -n 5
Dec 30 09:26:01 server kernel: audit: type=1400 audit(1767086761.123:81): apparmor="DENIED" operation="connect" profile="/usr/sbin/nginx" name="/run/php/php8.3-fpm.sock" pid=1842 comm="nginx" requested_mask="wr" denied_mask="wr" fsuid=33 ouid=33
Ce que ça signifie : Les permissions semblent correctes, mais la politique bloque nginx pour la connexion.
Décision : ajuster le profil AppArmor (ou utiliser les chemins par défaut de la distribution), pas les permissions du système de fichiers.
Task 14: Vérifier que php‑fpm écoute réellement sur le socket attendu
cr0x@server:~$ sudo ss -xlpn | grep php-fpm
u_str LISTEN 0 4096 /run/php/php8.3-fpm.sock 11159 * 0 users:(("php-fpm8.3",pid=1012,fd=8))
Ce que ça signifie : Le processus maître écoute sur ce socket.
Décision : si l’écoute se fait ailleurs, vous courez après le mauvais chemin dans nginx ou le mauvais pool.
Task 15: Confirmer que systemd ne recrée pas les répertoires avec des modes inattendus
cr0x@server:~$ systemd-tmpfiles --cat-config | grep -nE '/run/php|php-fpm' | head -n 20
219: d /run/php 0755 root root -
Ce que ça signifie : tmpfiles définit la création de /run/php au démarrage.
Décision : si cela ne correspond pas à la propriété/le mode souhaité, ajoutez un override dans /etc/tmpfiles.d/.
Le petit correctif qui tue les 502 (et pourquoi ça marche)
Le « petit correctif » se trouve presque toujours dans la configuration du pool PHP‑FPM, pas dans nginx. Vous voulez que PHP‑FPM crée le socket avec
un owner/groupe/mode qui correspondent au worker nginx. Sur Debian, nginx tourne typiquement en www-data. Faites donc pareil.
Corriger le pool : définir listen.owner, listen.group, listen.mode
Éditez le fichier de pool (souvent /etc/php/8.3/fpm/pool.d/www.conf, mais votre pool peut avoir un autre nom).
Ce sont les lignes qui comptent :
cr0x@server:~$ sudo sed -n '35,55p' /etc/php/8.3/fpm/pool.d/www.conf
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
Pourquoi ça marche : PHP‑FPM crée le socket. S’il le crée en root:root, nginx ne pourra pas se connecter.
S’il le crée en www-data:www-data avec 0660, nginx se connecte proprement et le problème est réglé.
Quand ne pas utiliser www-data
Si vous exécutez plusieurs sites et voulez de l’isolation, ne mettez pas tout dans www-data. Utilisez un groupe dédié
(par ex. nginx-php), gardez nginx en www-data, et ajoutez www-data à ce groupe,
pendant que les pools PHP‑FPM définissent listen.group = nginx-php. Cela vous donne une frontière partagée et contrôlée.
cr0x@server:~$ sudo groupadd --system nginx-php
cr0x@server:~$ sudo usermod -aG nginx-php www-data
cr0x@server:~$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data),990(nginx-php)
Décision : Si vous avez besoin de séparation par site, choisissez une stratégie de groupe partagé. Si c’est une machine mono‑app, www-data partout est acceptable.
Rendre le correctif durable : ne pas chmod le socket directement
Si vous faites ceci :
cr0x@server:~$ sudo chown www-data:www-data /run/php/php8.3-fpm.sock
cr0x@server:~$ sudo chmod 660 /run/php/php8.3-fpm.sock
Cela peut immédiatement stopper les 502. Cela disparaîtra aussi au prochain redémarrage. Ce n’est pas une solution d’ingénierie ; c’est un rituel d’apaisement.
Blague #2 : Chmodder un socket runtime à la main, c’est comme réparer un toit qui fuit avec un post‑it — réconfortant, bref, et fondamentalement irrespectueux envers la physique.
systemd‑tmpfiles : la pièce manquante pour les sockets sous /run
Voici la subtilité qui mord les gens : même si PHP‑FPM crée correctement le socket, il peut échouer à le créer si le répertoire
n’existe pas ou n’est pas traversable. Et /run est éphémère. Vous avez besoin que le répertoire /run/php soit créé au démarrage,
avec une propriété et un mode sensés. Debian le fait généralement pour vous, mais un durcissement personnalisé ou des jobs de nettoyage peuvent le casser.
Vérifier l’état du répertoire /run/php
cr0x@server:~$ sudo ls -ld /run/php
drwxr-x--- 2 root www-data 80 Dec 30 09:25 /run/php
Ce que ça signifie : Le groupe est www-data et le bit exécution est présent. nginx peut traverser ; bien.
Décision : si c’était drwx------ root root, nginx échouerait même si le socket était parfait.
Créer une règle tmpfiles (quand les valeurs par défaut ne suffisent pas)
Si vous avez un groupe non standard (comme nginx-php) ou que vous avez changé les conventions de propriété, créez votre propre entrée tmpfiles.
Cela évite de dépendre des valeurs par défaut des paquets qui peuvent ne pas correspondre à votre environnement.
cr0x@server:~$ printf '%s\n' 'd /run/php 0750 root nginx-php -' | sudo tee /etc/tmpfiles.d/php-sockets.conf
d /run/php 0750 root nginx-php -
Appliquez‑la immédiatement :
cr0x@server:~$ sudo systemd-tmpfiles --create /etc/tmpfiles.d/php-sockets.conf
cr0x@server:~$ sudo ls -ld /run/php
drwxr-x--- 2 root nginx-php 80 Dec 30 09:28 /run/php
Décision : si vous gérez des flottes, c’est la différence entre « marche sur ma machine » et « reste fonctionnel après reboot ».
N’oubliez pas de redémarrer les services qui ont mis en cache les appartenances à des groupes
Quand vous ajoutez www-data à un nouveau groupe, les processus déjà en cours peuvent ne pas le prendre en compte. nginx recharge ses workers, mais certains gestionnaires de services
font des choses étranges selon votre configuration. Vérifiez avec ps et les logs, ou redémarrez nginx si nécessaire.
cr0x@server:~$ sudo systemctl restart nginx
cr0x@server:~$ ps -o pid,user,group,comm -C nginx
PID USER GROUP COMMAND
2101 root root nginx
2102 www-data www-data nginx
2103 www-data www-data nginx
Ce que ça signifie : Le groupe principal affiche toujours www-data (c’est normal). Les groupes supplémentaires ne sont pas affichés ici.
Décision : utilisez /proc status pour confirmer les groupes supplémentaires si besoin.
cr0x@server:~$ sudo awk '/Groups:/{print}' /proc/2102/status
Groups: 33 990
Ce que ça signifie : Le worker a à la fois www-data et nginx-php.
Décision : l’accès au socket basé sur les groupes fonctionnera désormais de manière fiable.
Configuration nginx upstream : ne vous sabotez pas
Les erreurs de configuration nginx peuvent se déguiser en erreurs de permission de socket. Ou se combiner avec elles pour une panne à deux facettes.
Traitez nginx comme un instrument précis, pas comme un fichier texte que vous touchez jusqu’à ce qu’il arrête de crier.
Utiliser la syntaxe fastcgi_pass correcte
Sur Debian, vous verrez couramment :
cr0x@server:~$ sudo awk 'NR>=1 && NR<=60 {print NR ":" $0}' /etc/nginx/sites-enabled/app.conf | sed -n '15,40p'
15:location ~ \.php$ {
16: include snippets/fastcgi-php.conf;
17: fastcgi_pass unix:/run/php/php8.3-fpm.sock;
18:}
Décision : restez simple. N’inventez pas un include personnalisé sauf si vous avez un besoin spécifique et une couverture de test.
Attention aux chemins obsolètes après les mises à jour PHP
Les mises à niveau vers Debian 13 coïncident souvent avec des montées de version PHP. Si vous êtes passé d’une version précédente, vous pouvez avoir un site nginx
pointant encore vers /run/php/php8.2-fpm.sock alors que PHP 8.3 est installé. Cela produit « No such file ».
Votre correctif n’est alors pas de permissions ; c’est de l’exactitude.
cr0x@server:~$ sudo ls -1 /run/php
php8.3-fpm.sock
Décision : si un seul socket existe, alignez nginx sur ce socket ou créez un pool/socket qui correspond au chemin voulu.
Plusieurs pools : nommez‑les et pointez nginx explicitement
Si vous avez plusieurs applications, ne pointez pas tout vers « www » par habitude.
Créez des fichiers de pool séparés et des sockets séparés. Puis utilisez un bloc upstream par application si cela améliore la lisibilité.
Journalisation qui mérite l’espace disque
Les problèmes de permission de socket sont faciles à repérer si vous avez le bon niveau de logs. Sinon, c’est un coûteux jeu de devinettes.
Les logs d’erreur nginx suffisent souvent, mais les logs PHP‑FPM peuvent confirmer le comportement au niveau du pool (surtout en multi‑pool).
Vérifier les logs PHP‑FPM autour des start/restart
cr0x@server:~$ sudo journalctl -u php8.3-fpm --since "30 min ago" --no-pager | tail -n 30
Dec 30 09:25:03 server php-fpm8.3[1012]: NOTICE: fpm is running, pid 1012
Dec 30 09:25:03 server php-fpm8.3[1012]: NOTICE: ready to handle connections
Décision : si vous voyez des échecs de bind/listen ici, corrigez la config du pool PHP‑FPM ou la création du répertoire. Si les logs sont propres, concentrez‑vous sur l’accès nginx → socket.
Activer un niveau de détail nginx utile pendant la réponse à incident
Augmentez temporairement le niveau des logs d’erreur nginx si vous êtes bloqué. Puis remettez‑le. La journalisation, c’est comme la caféine : utile en cas de besoin, mauvaise habitude au quotidien.
Trois micro-histoires d’entreprise sorties des tranchées
1) Incident causé par une fausse hypothèse : « root le possède, donc c’est sécurisé »
Une entreprise de taille moyenne a migré un monolithe PHP d’une vieille VM vers Debian 13. Les ingénieurs ont fait les bonnes choses — images immuables,
pipeline de déploiement, configuration dans Git. Ils ont aussi porté un ancien snippet de « durcissement » qui mettait
listen.owner = root et listen.group = root « pour empêcher l’accès ».
L’hypothèse était que nginx, étant « le serveur web », devait être suffisamment privilégié pour se connecter de toute façon. Dans leur tête,
nginx était un processus unique avec autorité. En réalité, nginx a un master privilégié et des workers non privilégiés,
et ce sont les workers qui ont besoin de se connecter aux upstreams. Les workers étaient www-data.
La bascule a semblé correcte au début parce que l’ancien environnement utilisait TCP, pas de sockets. Le nouvel environnement utilisait des sockets Unix pour « la performance ».
Immédiatement après la bascule, des 502 sont apparus — seulement sur les routes PHP. Les fichiers statiques étaient corrects, ce qui a donné l’impression d’une régression PHP.
Un développeur a rollbacké du code. Aucun changement. Quelqu’un a redémarré php‑fpm. Aucun changement. Quelqu’un a redémarré nginx. Soulagement bref, puis nouvel échec.
Le tournant a été une seule ligne dans les logs d’erreur nginx : errno 13. Une fois qu’ils ont vu le socket en root:root 0660, c’était réglé.
Ils ont changé listen.group vers un groupe partagé, redémarré PHP‑FPM, et le trafic s’est stabilisé. Le postmortem était sans ambiguïté :
les modèles de permission ne sont pas des vibes. Si vous ne savez pas quel processus a besoin d’accès, vous configurez sur la superstition.
2) Optimisation qui s’est retournée contre eux : « les sockets Unix sont plus rapides, on active tout »
Une grande organisation avait une équipe plateforme standardisant les piles web. Ils avaient beaucoup de paires nginx+PHP‑FPM.
Quelqu’un a constaté que les sockets Unix évitent le surcoût TCP et a décidé de standardiser sur les sockets à travers la flotte.
Le changement a été déployé comme une « optimisation sûre » derrière un feature flag.
Ça a fonctionné en staging. Bien sûr. Staging était un hôte unique avec un pool unique, des permissions vanilla,
et personne n’avait touché à la configuration tmpfiles depuis la nuit des temps.
La production était désordonnée. Certains hôtes avaient plusieurs pools avec des users par app. Certains avaient nginx en container.
Certains avaient des profils AppArmor personnalisés. Certains avaient des jobs de nettoyage qui supprimaient les répertoires runtime pendant la « maintenance »
parce que quelqu’un avait confondu /run avec « cache ».
Le rollout n’a pas causé une panne nette. Il a causé une hémorragie lente : un petit pourcentage de requêtes échouait avec 502,
corrélé à des nœuds spécifiques. C’est pire. Une panne nette attire l’attention ; une défaillance partielle est imputée à « l’app ».
Le correctif n’était pas « revenir au TCP ». Il s’agissait de traiter les sockets comme de l’infrastructure : définir explicitement la création des répertoires,
définir la propriété des sockets via la config du pool, et documenter la stratégie de groupe. L’optimisation a cessé de se retourner contre eux quand elle a cessé d’être « juste un toggle ».
3) Pratique ennuyeuse mais correcte qui a sauvé la mise : « nginx -T et namei avant de paniquer »
Une équipe de services financiers appliquait un processus de changement strict et se faisait chambrer pour ça. Mais leurs rotations d’astreinte étaient plus calmes.
Pendant un déploiement Debian 13, un nœud a commencé à renvoyer des 502 pour les routes PHP. L’ingénieur d’astreinte n’a rien redémarré au départ.
Il a suivi un court runbook : capturer l’erreur nginx, imprimer la config nginx active, inspecter le socket et le répertoire avec namei.
En cinq minutes ils ont identifié le coupable : nginx pointait vers un chemin de socket d’un ancien nom de pool. Le nouveau fichier de pool existait,
php‑fpm tournait, et le bon socket existait — juste pas au chemin que nginx utilisait. Ce n’était pas des permissions.
C’était une incohérence de configuration.
Ils ont mis à jour le fichier site nginx, lancé nginx -t, rechargé nginx, et les 502 ont cessé. Aucun redémarrage de PHP‑FPM nécessaire.
Ça compte dans des environnements où redémarrer php‑fpm peut faire chuter des requêtes en vol ou déclencher des temps de warmup lents.
La pratique qui a sauvé la mise était ennuyeuse : toujours vérifier ce qui tourne vraiment (nginx -T) et toujours vérifier
la traversée du système de fichiers (namei -l). L’ennuyeux est fiable. Le fiable est rentable.
Erreurs courantes : symptôme → cause racine → correctif
1) Symptom: nginx indique « Permission denied (13) » sur le socket
Cause racine : Socket possédé par le mauvais utilisateur/groupe, ou mode trop restrictif.
Correctif : Définir dans le pool PHP‑FPM :
listen.owner, listen.group, listen.mode = 0660. Redémarrer PHP‑FPM, vérifier avec ls -l.
2) Symptom: « No such file or directory (2) » pour le chemin du socket
Cause racine : nginx pointe vers un mauvais chemin de socket (bump de version PHP, pool renommé), ou PHP‑FPM n’a pas créé le socket.
Correctif : Vérifier la config active nginx avec nginx -T. Vérifier le listen = du pool PHP‑FPM. Vérifier le socket dans /run/php.
3) Symptom: Ça marche après chmod, casse après reboot
Cause racine : Le changement manuel des permissions du socket est perdu quand PHP‑FPM recrée le socket ou quand /run se réinitialise.
Correctif : Modifier la configuration du pool (permissions à la création) et s’assurer que /run/php est créé via tmpfiles si nécessaire.
4) Symptom: Les permissions ont l’air correctes, mais toujours « Permission denied »
Cause racine : La politique AppArmor bloque nginx pour se connecter à ce chemin de socket.
Correctif : Confirmer avec les logs d’audit du noyau. Ajuster le profil AppArmor ou revenir à des chemins standard couverts par les profils existants.
5) Symptom: 502 aléatoires pendant les déploiements, surtout avec des reloads
Cause racine : Le chemin du socket change entre versions/pools, ou plusieurs pools sont redémarrés dans le mauvais ordre, laissant nginx pointant vers un socket temporairement manquant.
Correctif : Stabiliser le chemin du socket, coordonner les redémarrages, et éviter de changer les noms de socket lors des déploiements courants.
6) Symptom: Seuls certains vhosts échouent ; d’autres vont bien
Cause racine : Un site pointe vers le mauvais pool/socket ; les autres sites sont corrects.
Correctif : Auditer chaque valeur fastcgi_pass par vhost ; ne les supposez pas cohérentes. Utiliser des sockets de pool explicites par site.
7) Symptom: PHP‑FPM est actif, mais le socket n’apparaît jamais
Cause racine : Pool mal configuré, répertoire manquant, ou échec de permission lors de la création du répertoire.
Correctif : Vérifier journalctl -u php8.3-fpm. S’assurer que /run/php existe avec le bon mode/propriétaire. Valider la config avec php-fpm8.3 -t.
Listes de contrôle / plan étape par étape
Checklist réponse à incident (15 minutes, hôte unique)
- Récupérer la ligne d’erreur nginx pour une requête échouée depuis
/var/log/nginx/error.log. - Confirmer le chemin du socket upstream depuis la config active avec
nginx -T. - Vérifier la santé du service PHP‑FPM avec
systemctl status php8.3-fpm. - Inspecter le socket avec
ls -let la traversée du chemin avecnamei -l. - Confirmer l’utilisateur des workers nginx avec
ps. - Comparer la config du pool :
listen,listen.owner,listen.group,listen.mode. - Valider la config :
php-fpm8.3 -t. - Redémarrer PHP‑FPM (après validation de la config), puis revérifier la propriété/le mode du socket.
- Recharger nginx (après
nginx -t) et tester aveccurl. - Si toujours bloqué, vérifier les refus AppArmor via les logs d’audit du noyau.
Checklist durcissement (rendre durable sur reboots)
- Choisir un modèle d’accès au socket : utilisateur unique (
www-data) ou groupe partagé (recommandé pour multi‑user pools). - S’assurer que le répertoire
/run/phpa la propriété/le mode correspondant au modèle. - Si vous divergez des valeurs par défaut de la distribution, ajouter une règle tmpfiles sous
/etc/tmpfiles.d/. - Garder les chemins de socket stables ; n’encodez pas les versions mineures de PHP dans des dizaines de vhosts sans automatisation.
- Documenter le mapping pool→vhost : quel socket chaque server block utilise.
- Intégrer la recherche de
Permission denieddans vos validations après modifications.
Plan de changement (déploiement sûr pour une flotte)
- Inventorier toutes les valeurs fastcgi_pass dans les configs nginx.
- Inventorier tous les pools PHP‑FPM et leurs paramètres listen.
- Choisir une norme : socket par site avec groupe partagé, ou pool unique partagé.
- Tester le changement : ajuster d’abord les configs des pools, puis nginx, puis recharger/redémarrer dans un ordre contrôlé.
- Automatiser la vérification : vérifier l’existence du socket, la propriété, le mode, et que nginx puisse servir un endpoint de santé PHP.
- Avancer avec des canaris. Ne « flashez pas tout » à moins d’aimer découvrir des inconnues à 2h du matin.
FAQ
1) Pourquoi nginx renvoie‑t‑il un 502 au lieu d’une erreur de permissions plus explicite ?
nginx est un reverse proxy. Quand il ne peut pas parler à l’upstream, il renvoie « Bad Gateway ». La raison détaillée se trouve dans le log d’erreur nginx,
pas dans la réponse HTTP.
2) Dois‑je utiliser TCP au lieu des sockets Unix pour éviter ça ?
TCP évite les permissions de système de fichiers et les règles AppArmor liées aux chemins, mais introduit la gestion de ports et une exposition potentiellement plus large.
Les sockets Unix conviennent — configurez simplement correctement la propriété/le mode et gardez le chemin stable.
3) Le chmod 777 sur le socket est‑il acceptable parfois ?
Non. Ça « marche » en supprimant le contrôle d’accès voulu. Cela habitue aussi votre équipe à résoudre les incidents en élargissant le rayon d’impact.
Utilisez le propriétaire/groupe/mode correct ou un groupe partagé dédié.
4) Quel est le mode de permission le plus sûr pour un socket que nginx utilise ?
Typiquement 0660 avec propriétaire php-fpm-user:shared-group, et nginx dans ce groupe partagé.
Évitez les permissions mondiales. Évitez root:root sauf si nginx est privilégié (ce qui ne devrait pas être le cas).
5) Pourquoi changer les permissions du fichier socket ne persiste pas ?
PHP‑FPM crée le socket au démarrage du pool. Au redémarrage, il le supprime et le recrée, en appliquant les paramètres du pool.
Aussi, /run est un tmpfs et se réinitialise au reboot.
6) J’ai défini listen.owner et listen.group, mais il crée encore des sockets root‑owned. Pourquoi ?
Vous modifiez peut‑être le mauvais fichier de pool, ou le service utilise un autre répertoire de config, ou vous avez plusieurs pools qui créent différents sockets.
Confirmez avec grep sous /etc/php/8.3/fpm/pool.d et vérifiez le chemin du socket que nginx utilise.
7) Les permissions du répertoire peuvent‑elles casser ça même si le socket semble correct ?
Oui. nginx doit pouvoir traverser chaque répertoire parent pour atteindre le socket. Utilisez namei -l pour voir où la traversée échoue.
8) Comment AppArmor se manifeste‑t‑il différemment des permissions filesystem ?
Les permissions filesystem produisent typiquement des logs d’erreur nginx avec errno 13 sans ligne d’audit du noyau.
AppArmor émettra souvent une entrée d’audit du noyau « DENIED » nommant /usr/sbin/nginx et le chemin du socket.
9) J’exécute des pools PHP‑FPM en utilisateurs par application. Quelle est la stratégie de socket la plus propre ?
Donnez à chaque pool son propre chemin de socket et définissez listen.group sur un groupe partagé auquel nginx appartient.
Gardez listen.mode = 0660. Cela maintient le contrôle d’accès tout en évitant le « tout en www-data ».
10) Comment empêcher de futures mises à jour de casser les chemins de socket ?
Ne cochez pas des versions mineures de PHP dans des dizaines de vhosts sans automatisation. Utilisez des noms de socket stables (par pool),
ou gérez les configs nginx via un système qui les met à jour pendant la montée de version.
Conclusion : prochaines étapes à faire aujourd’hui
Debian 13 n’a pas inventé les problèmes de sockets PHP‑FPM. Il les a juste rendus plus faciles à rencontrer : nouvelles installations, mises à niveau,
intégration systemd plus poussée, et la dérive silencieuse « ce serveur est spécial » qui s’accumule jusqu’à l’explosion.
Si vous ne retenez rien d’autre : arrêtez de chmodder les sockets runtime à la main. Corrigez la création du socket à la source — le pool PHP‑FPM — et assurez‑vous que
/run/php existe avec des permissions prévisibles sur les reboots. Puis vérifiez avec les logs et une requête propre.
Prochaines étapes pratiques :
- Lancer
nginx -Tet consigner le chemin du socket actif pour chaque vhost. - Inspecter la propriété/le mode du socket avec
ls -let la traversée avecnamei -l. - Définir
listen.owner,listen.group,listen.modedans le fichier de pool correct. - Si vous utilisez des groupes ou chemins personnalisés, ajoutez une règle tmpfiles pour rendre
/run/phpdéterministe. - Retester avec
curlet surveiller les logs d’erreur nginx qui se taisent — le meilleur silence qui soit.