Erreurs de permissions de fichiers WordPress : ce que 755/644 doivent être et pourquoi

Cet article vous a aidé ?

Certaines pannes WordPress ne ressemblent pas à des pannes. La page d’accueil se charge, le marketing est content, puis un éditeur tente d’uploader une image et obtient un joyeux « Unable to create directory. ». Ou les mises à jour automatiques restent bloquées indéfiniment. Ou le site bascule en 403 après un « correctif de permissions rapide » exécuté à 2 h du matin.

Les permissions de fichiers sont un de ces problèmes qui commencent par une petite brûlure et finissent en post-mortem. C’est aussi l’un des rares sujets WordPress où vous pouvez être techniquement correct et casser la production. Faisons les parties ennuyeuses correctement : ce que 755/644 doivent être, pourquoi ces chiffres existent, et comment diagnostiquer rapidement les erreurs de permissions sans transformer votre racine web en bac à sable public modifiable.

Ce que signifient réellement 755/644 (et pourquoi les utilisateurs WordPress les répètent)

Quand on dit « mettre les répertoires en 755 et les fichiers en 644 », ce n’est pas une incantation. C’est une ligne de base sûre pour un serveur Linux typique où :

  • Les répertoires ont besoin du bit exécution pour être parcourables (oui, exécution pour un répertoire signifie « pouvoir y entrer »).
  • Les fichiers doivent généralement être lisibles par le serveur web mais pas modifiables par tout le monde.
  • Le propriétaire peut éditer le contenu ; le serveur web peut le lire ; les utilisateurs aléatoires sur la même machine ne peuvent pas le modifier.

Permissions en un paragraphe utilisable

Les permissions Unix sont trois ensembles de bits : propriétaire, groupe, autres. Chaque ensemble peut avoir r (lecture), w (écriture), x (exécution). La forme numérique est octale : r=4, w=2, x=1. Additionnez-les et vous obtenez un chiffre. Donc 7 signifie rwx, 6 signifie rw-, 5 signifie r-x, 4 signifie r–.

Pourquoi 755 pour les répertoires ?

755 signifie :

  • Propriétaire : 7 (rwx) – peut lister, créer, supprimer, traverser.
  • Groupe : 5 (r-x) – peut lister et traverser, mais pas modifier.
  • Autres : 5 (r-x) – idem.

Pour les répertoires, « lecture » permet de lister les noms, « exécution » permet d’y entrer (et d’accéder aux fichiers si vous connaissez les noms), et « écriture » permet de créer/supprimer/renommer des entrées. Un répertoire en 644 est généralement cassé car il manque le bit d’exécution, donc vous ne pouvez pas entrer dedans même si vous pouvez « lire » son listing.

Et pourquoi 644 pour les fichiers ?

644 signifie :

  • Propriétaire : 6 (rw-) – peut éditer.
  • Groupe : 4 (r–) – peut lire.
  • Autres : 4 (r–) – peut lire.

Cela est sensé pour les fichiers PHP, images, CSS, JS. Le serveur web doit les lire. Il n’a pas besoin de les modifier sur place.

La partie qu’on oublie : la propriété et l’utilisateur du processus

Les permissions ne sont qu’une moitié de l’histoire. L’autre moitié est : quel utilisateur Unix exécute le serveur web (ou PHP‑FPM) ? Si votre répertoire WordPress est possédé par deploy:deploy mais que PHP‑FPM s’exécute en tant que www-data, alors 755/644 ne permettront pas magiquement les uploads à moins que les chemins modifiables soient possédés ou aient l’écriture de groupe configurée de façon contrôlée.

Conseil orienté opinion : ne résolvez pas ça en mettant tout en 777. Ce n’est pas une correction ; c’est remplacer une serrure par un post‑it qui dit « please don’t steal ».

Blague n°1 : Mettre une racine web en 777, c’est comme cacher la clé de la maison sous le paillasson, puis tweeter les coordonnées GPS du paillasson.

Où WordPress a réellement besoin d’écrire (et où il ne devrait jamais écrire)

WordPress est une application qui veut se modifier elle-même. C’est pratique en hébergement mutualisé. En production, c’est une décision de risque.

Chemins que WordPress a souvent besoin d’écrire

  • wp-content/uploads/ – uploads médias, redimensionnement d’images, fichiers générés.
  • wp-content/cache/ (ou répertoires de cache spécifiques aux plugins) – selon les plugins de cache.
  • wp-content/upgrade/ – temporaire pendant les mises à jour.
  • wp-content/languages/ – packs de langues (parfois).
  • wp-content/plugins/ et wp-content/themes/ – uniquement si vous autorisez les installations/mises à jour in‑place depuis l’interface admin.

Chemins où WordPress ne devrait pas pouvoir écrire dans une configuration bien gérée

  • Fichiers cœur comme wp-admin/, wp-includes/.
  • Configuration serveur comme les configs Nginx/Apache (évident, mais je l’ai déjà vu).
  • Tout ce qui est en dehors de la racine du site (sauf si vous l’avez prévu explicitement).

Reality check sur « FS_METHOD »

Si WordPress ne peut pas écrire là où il le souhaite, il peut demander des identifiants FTP ou échouer les mises à jour. C’est WordPress qui essaie de compenser des environnements d’hébergement. Sur un serveur correctement géré, vous choisissez généralement un de ces modes d’exploitation :

  • Code immuable + pipeline de déploiement : WordPress ne peut pas mettre à jour core/plugins/themes ; les mises à jour passent par CI/CD (meilleur pour la fiabilité).
  • wp-content modifiable uniquement : laissez les admins gérer plugins/themes ; gardez le cœur en lecture seule pour l’utilisateur web (compromis raisonnable).
  • Tout modifiable par l’utilisateur web : chemin le plus rapide vers les regrets (à éviter).

Une citation que les ops gardent pour eux

« L’espoir n’est pas une stratégie. » — idée paraphrasée couramment attribuée aux cercles fiabilité et opérations

Les permissions sont l’endroit où l’espoir meurt. Modélisez les utilisateurs, modélisez les écritures, puis appliquez le moindre privilège qui laisse le business fonctionner.

Playbook de diagnostic rapide (vérifier 1/2/3)

Si vous êtes en plein incident, ne commencez pas par des chmod récursifs. Commencez par l’ensemble minimal de vérifications qui identifient l’acteur (utilisateur/processus) en échec et le chemin en échec.

1) Identifier le chemin exact en échec et le symptôme au niveau syscall

  • Recherchez « Permission denied », « Operation not permitted », « Read-only file system », « No such file or directory », « SELinux is preventing », ou « open_basedir restriction ». Ce sont des problèmes différents portant le même chapeau.
  • Consultez les logs du serveur web et de PHP pour le chemin.

2) Identifier quel utilisateur Unix effectue l’écriture

  • Utilisateur du pool PHP‑FPM ? Module Apache ? Worker Nginx ? Utilisateur CLI exécutant WP‑CLI ?
  • Confirmez avec la configuration du service et la liste des processus en direct.

3) Vérifier ownership + mode + flags de montage + contrôles MAC

  • Ownership et mode : namei -l et stat sur le chemin complet.
  • Flags de montage : ro, noexec, nosuid, et particularités des overlays en conteneur.
  • SELinux/AppArmor : si activés, les permissions DAC peuvent sembler correctes mais échouer quand même.

Si vous faites ces trois vérifications, vous arrivez généralement à une vraie correction au lieu d’un jeu de whack-a-mole sur les permissions.

Tâches pratiques : commandes, sorties, décisions (12+)

Chaque tâche ci‑dessous inclut : une commande, ce que signifie une sortie typique, et la décision à prendre. Elles sont rédigées pour des systèmes Debian/Ubuntu utilisant www-data ; adaptez selon votre distro (apache, nginx, etc.).

Task 1: Find the web/PHP user actually serving WordPress

cr0x@server:~$ ps -eo user,comm | egrep 'php-fpm|apache2|httpd|nginx' | head
root     nginx
www-data nginx
root     php-fpm8.2
www-data php-fpm8.2

Ce que ça signifie : Les workers Nginx et PHP‑FPM s’exécutent en tant que www-data (bien ; cohérent).

Décision : Les fichiers/répertoires devant être modifiables par l’application doivent l’être pour www-data (via ownership ou stratégie de groupe). Si vous voyez des utilisateurs mélangés (ex. Nginx en nginx, PHP‑FPM en www-data), choisissez un modèle et alignez‑le.

Task 2: Confirm PHP-FPM pool user and group (the source of truth)

cr0x@server:~$ sudo grep -R "^\s*user\s*=" /etc/php/*/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:user = www-data
cr0x@server:~$ sudo grep -R "^\s*group\s*=" /etc/php/*/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:group = www-data

Ce que ça signifie : Les scripts PHP s’exécutent en tant que www-data:www-data.

Décision : Tout répertoire que WordPress doit écrire doit être modifiable par www-data, directement ou via permissions de groupe/ACL.

Task 3: Verify which filesystem path WordPress is using (docroot)

cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -R "root " -n | head
cr0x@server:~$ sudo grep -R "root " -n /etc/nginx/sites-enabled | head
/etc/nginx/sites-enabled/example.conf:12:    root /var/www/example/current/public;

Ce que ça signifie : Votre docroot réel est /var/www/example/current/public, pas ce dont vous vous souvenez vaguement d’il y a six mois.

Décision : Exécutez toutes les vérifications de permissions contre le chemin réel, pas contre une ancienne cible de symlink ou un répertoire de staging.

Task 4: Inspect permissions on the failing directory (quick glance)

cr0x@server:~$ ls -ld /var/www/example/current/public/wp-content/uploads
drwxr-xr-x 2 deploy deploy 4096 Dec 27 09:14 /var/www/example/current/public/wp-content/uploads

Ce que ça signifie : Le répertoire est en 755 mais appartient à deploy:deploy. L’utilisateur web www-data est « other » ici, donc il ne peut pas écrire.

Décision : Changez la propriété ou adoptez une stratégie de groupe/ACL pour que www-data puisse écrire uniquement là où c’est nécessaire.

Task 5: Check the full path traversal permissions with namei (catches the parent directory gotcha)

cr0x@server:~$ namei -l /var/www/example/current/public/wp-content/uploads
f: /var/www/example/current/public/wp-content/uploads
drwxr-xr-x root   root   /
drwxr-xr-x root   root   var
drwxr-xr-x root   root   www
drwxr-x--- deploy deploy example
lrwxrwxrwx deploy deploy current -> releases/2025-12-27_0910
drwxr-xr-x deploy deploy releases
drwxr-xr-x deploy deploy 2025-12-27_0910
drwxr-xr-x deploy deploy public
drwxr-xr-x deploy deploy wp-content
drwxr-xr-x deploy deploy uploads

Ce que ça signifie : Le répertoire /var/www/example est en 750 (drwxr-x---) possédé par deploy:deploy. Même si vous avez corrigé uploads, www-data pourrait ne pas pouvoir traverser l’arborescence.

Décision : Assurez‑vous que chaque répertoire parent est traversable par l’utilisateur qui a besoin d’accès (souvent via l’exécution de groupe). C’est la cause cachée du « mais uploads est en 777 et ça échoue encore ».

Task 6: Check file mode and ownership precisely with stat

cr0x@server:~$ stat -c '%A %a %U:%G %n' /var/www/example/current/public/wp-config.php
-rw-r----- 640 deploy deploy /var/www/example/current/public/wp-config.php

Ce que ça signifie : wp-config.php est en 640 et n’est pas lisible par « autres ». Si PHP s’exécute en tant que www-data et n’est pas dans le groupe deploy, WordPress peut échouer avec un 500.

Décision : Soit le rendre lisible par le groupe et mettre l’utilisateur PHP dans ce groupe, soit changer la propriété de façon appropriée. Ne faites pas 644 aveuglément si vous pouvez le garder plus strict avec le bon groupe.

Task 7: Confirm group memberships (does www-data belong to the deploy group?)

cr0x@server:~$ id www-data
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Ce que ça signifie : www-data n’est pas dans deploy. Les lectures/écritures basées sur le groupe ne fonctionneront pas tant que vous n’ajusterez pas les groupes ou utiliserez des ACL.

Décision : Choisissez : ajouter www-data à un groupe partagé (courant sur des serveurs mono‑locataire), ou utiliser des ACL pour des chemins spécifiques (plus propre en multi‑tenant).

Task 8: Fix writable path ownership safely (uploads only)

cr0x@server:~$ sudo chown -R www-data:www-data /var/www/example/current/public/wp-content/uploads
cr0x@server:~$ ls -ld /var/www/example/current/public/wp-content/uploads
drwxr-xr-x 2 www-data www-data 4096 Dec 27 09:14 /var/www/example/current/public/wp-content/uploads

Ce que ça signifie : Maintenant l’utilisateur PHP peut écrire dans uploads sans rendre tout votre codebase modifiable.

Décision : Si vous utilisez des outils de déploiement qui font tourner des releases, envisagez un répertoire uploads persistant partagé et un symlink, sinon le prochain déploiement peut réinitialiser la propriété.

Task 9: Set baseline perms (dirs 755, files 644) without clobbering special cases

cr0x@server:~$ sudo find /var/www/example/current/public -type d -print0 | sudo xargs -0 chmod 755
cr0x@server:~$ sudo find /var/www/example/current/public -type f -print0 | sudo xargs -0 chmod 644

Ce que ça signifie : Standardise les modes. Si cela « répare » le site, vous aviez probablement quelqu’un qui a exécuté un chmod récursif sur la mauvaise valeur comme 600 ou 700.

Décision : Resserrez immédiatement les fichiers sensibles (comme wp-config.php) après le baseline, et assurez-vous que les répertoires modifiables restent modifiables pour le bon utilisateur. Les baselines sont des échafaudages, pas une architecture.

Task 10: Detect world-writable files and directories (security tripwire)

cr0x@server:~$ sudo find /var/www/example/current/public -perm -0002 -ls | head
  412310    4 drwxrwxrwx   2 www-data www-data     4096 Dec 27 09:15 /var/www/example/current/public/wp-content/cache

Ce que ça signifie : Quelque chose est world-writable (...rwx pour les autres). Parfois des plugins le font. Parfois des humains en panique le font.

Décision : Supprimez le world-writable sauf si vous avez une raison explicite. Préférez 775 avec une appartenance de groupe contrôlée ou des ACL.

Task 11: Check for immutable bit (the “chmod did nothing” mystery)

cr0x@server:~$ lsattr -d /var/www/example/current/public/wp-content/uploads
-------------e------- /var/www/example/current/public/wp-content/uploads

Ce que ça signifie : Pas de flag immuable. Si vous voyez un i dans les attributs, le kernel bloquera les changements même pour root.

Décision : Si l’attribut immuable est défini par erreur, retirez‑le pour le chemin spécifique (avec prudence), pas pour tout l’arbre.

Task 12: Confirm the filesystem isn’t mounted read-only

cr0x@server:~$ findmnt -no TARGET,OPTIONS /var/www
/var/www rw,relatime

Ce que ça signifie : Le montage est en lecture‑écriture. S’il était ro, WordPress ne pourrait jamais uploader quelle que soit la valeur de chmod/chown.

Décision : Si vous voyez ro, arrêtez de toucher aux permissions. Corrigez le problème de stockage : remontez, résolvez les erreurs de système de fichiers, vérifiez l’overlay du conteneur, ou investiguez pourquoi l’hôte a remonté en lecture seule.

Task 13: Check SELinux status (permissions can be “correct” and still fail)

cr0x@server:~$ sestatus
SELinux status:                 disabled

Ce que ça signifie : SELinux est désactivé ; vous pouvez ignorer les contexts.

Décision : Si SELinux est en mode enforcing, il faut vérifier les contexts sur les répertoires modifiables (et les corriger) plutôt que d’élargir sauvagement les modes Unix.

Task 14: AppArmor status (common on Ubuntu)

cr0x@server:~$ sudo aa-status | head
apparmor module is loaded.
10 profiles are loaded.

Ce que ça signifie : AppArmor est actif. Un profil peut refuser l’écriture PHP même si les permissions Unix l’autorisent.

Décision : Si vous suspectez AppArmor, consultez les logs de refus et ajustez le profil pour les chemins d’écriture spécifiques. Ne « corrigez » pas ça avec du 777.

Task 15: Reproduce the write as the PHP user (tests the real permission boundary)

cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/example/current/public/wp-content/uploads/.permtest && echo ok'
ok

Ce que ça signifie : L’utilisateur de l’appli peut écrire. Si cela échoue avec « Permission denied », votre problème est l’ownership/mode/ACL/SELinux/flags de montage.

Décision : Utilisez ceci pour valider votre correction avant de demander aux éditeurs de retenter les uploads.

Task 16: Inspect ACLs (when permissions look fine but aren’t)

cr0x@server:~$ getfacl -p /var/www/example/current/public/wp-content/uploads | sed -n '1,12p'
# file: /var/www/example/current/public/wp-content/uploads
# owner: www-data
# group: www-data
user::rwx
group::r-x
other::r-x

Ce que ça signifie : Pas de règles ACL supplémentaires. Si vous voyez des entrées ACL, elles peuvent accorder ou restreindre l’accès au-delà des bits de mode de base.

Décision : En environnements multi‑utilisateurs, les ACL sont souvent la façon la plus propre d’accorder l’écriture à www-data sans lui donner la propriété.

Task 17: Find recently changed permissions (the “who touched this?” question)

cr0x@server:~$ sudo find /var/www/example/current/public -printf '%TY-%Tm-%Td %TH:%TM %m %u:%g %p\n' | sort -r | head
2025-12-27 09:16 755 www-data:www-data /var/www/example/current/public/wp-content/uploads
2025-12-27 09:12 640 deploy:deploy /var/www/example/current/public/wp-config.php

Ce que ça signifie : Vous pouvez voir ce qui a changé récemment, ce qui aide à corréler avec des déploiements ou des « correctifs rapides ».

Décision : Si un déploiement a changé la propriété des répertoires partagés, mettez à jour vos scripts de déploiement. La dérive des permissions est un bug d’automatisation, pas un problème de formation humaine.

Erreurs courantes : symptôme → cause racine → correctif

1) « Unable to create directory wp-content/uploads/… »

Symptôme : Les uploads médias échouent ; le tableau de bord WordPress se plaint de la création du répertoire.

Cause racine : uploads ou un répertoire parent n’est pas modifiable/traversable par l’utilisateur PHP ; parfois le parent est en 750 et bloque la traversée.

Correctif : Vérifiez avec namei -l. Assurez‑vous que le chemin complet est traversable (bit x) et que le répertoire uploads est modifiable par l’utilisateur PHP via ownership, groupe, ou ACL. Évitez le world‑writable.

2) Installation de plugin/thème demande des identifiants FTP

Symptôme : WordPress demande FTP alors que vous êtes sur votre propre serveur.

Cause racine : WordPress ne peut pas écrire dans wp-content/plugins ou wp-content/themes, donc il retombe sur des méthodes de type FTP.

Correctif : Décidez de votre modèle d’exploitation. Si vous autorisez les installations depuis l’interface, rendez ces répertoires modifiables par l’utilisateur PHP (idéalement via groupe/ACL, pas 777). Si vous ne le souhaitez pas, désactivez les écritures en place et mettez à jour via le pipeline de déploiement.

3) 403 Forbidden après un « resserrage des permissions »

Symptôme : Le site entier renvoie 403 ; les assets statiques peuvent aussi échouer.

Cause racine : Des répertoires ont été mis en 644 ou 600, supprimant le bit d’exécution, donc le serveur web ne peut pas traverser.

Correctif : Les répertoires devraient généralement être en 755 (ou 750 si vous contrôlez explicitement l’accès par groupe). Utilisez find -type d -exec chmod 755 et validez avec namei -l.

4) 500 Internal Server Error, logs montrent « failed to open stream: Permission denied » pour wp-config.php

Symptôme : PHP ne peut pas lire la config ; site en écran blanc.

Cause racine : wp-config.php est en 600 ou 640 avec le mauvais groupe ; l’utilisateur PHP n’a pas la permission de lecture.

Correctif : Rendez‑le lisible pour l’utilisateur PHP via stratégie de groupe ou changement de propriétaire. Évitez de le rendre lisible par tous si possible ; 640 avec le bon groupe est correct.

5) « Operation not permitted » lors d’un chown/chmod

Symptôme : Même root ne peut pas changer la propriété, ou les changements reviennent en arrière.

Cause racine : Bit immuable défini, ou chemin sur un filesystem qui interdit chown (certains montages réseau), ou vous êtes dans un conteneur avec des IDs mappés.

Correctif : Vérifiez lsattr, le type de montage, et le mapping UID du runtime du conteneur. Corrigez la contrainte sous‑jacente ; ne forcez pas aveuglément.

6) Tout a l’air correct, mais les écritures échouent toujours

Symptôme : Modes/ownership semblent corrects ; touch en tant que root fonctionne ; WordPress ne peut toujours pas écrire.

Cause racine : Refus SELinux/AppArmor, remontage en lecture seule, ou pool PHP‑FPM exécuté sous un autre utilisateur que prévu.

Correctif : Confirmez l’utilisateur du pool, vérifiez l’état/logs du système MAC, vérifiez les options de montage. Testez avec sudo -u www-data touch ... pour valider l’acteur réel.

Trois mini-récits du monde d’entreprise depuis les tranchées des permissions

Mini-récit 1 : L’incident causé par une mauvaise hypothèse

Ils avaient hérité d’une installation WordPress qui « marchait toujours ». Un nouvel ingénieur a reçu la tâche de la migrer sur une VM fraîche. Nginx devant, PHP‑FPM derrière, TLS moderne. La checklist de migration était propre : rsync des fichiers, import de la base, mise à jour du DNS. Le lancement était dans les temps.

Deux heures plus tard l’équipe contenu a commencé à uploader des images pour une campagne. Tous les uploads ont échoué. L’ingénieur d’astreinte a fait l’habituel : chmod -R 755 sur les répertoires, chmod -R 644 sur les fichiers, puis a retenté. Toujours cassé. Quelqu’un a proposé l’option nucléaire : chmod -R 777 wp-content. Les uploads ont fonctionné instantanément. Tout le monde a soufflé.

Deux jours plus tard, un scan de sécurité a signalé l’hôte compromis. Le post‑mortem a été désagréable mais instructif. La mauvaise hypothèse n’était pas « 755/644 suffit ». C’était « l’utilisateur de l’app peut traverser le chemin ». La docroot vivait sous /srv/sites/clientA qui était en 750 et possédé par deploy:deploy. Donc l’utilisateur PHP ne pouvait pas atteindre wp-content de façon fiable. Le changement en 777 a masqué le problème de traversée et transformé le reste de l’arbre en terrain de jeu modifiable.

Ils ont corrigé proprement en définissant un groupe partagé pour le site, en rendant seulement les sous‑répertoires nécessaires modifiables par le groupe, et en validant la traversée avec namei -l. Le compromis était simple : laisser WordPress écrire dans uploads et cache, mais garder le code immuable. L’équipe sécurité a arrêté d’appeler. L’équipe marketing n’a jamais su ce qui a failli arriver, et c’est bien comme ça.

Mini-récit 2 : L’optimisation qui s’est retournée contre eux

Une autre société voulait « des déploiements plus rapides ». Leur solution a été de monter wp-content/uploads sur un système de fichiers réseau pour que plusieurs serveurs applicatifs partagent les médias. Objectif raisonnable, implémentation risquée. Ils ont utilisé un montage qui ne se comportait pas comme un ext4 local : le mapping des propriétaires était incohérent, et les sémantiques chmod/chown n’étaient pas celles attendues par l’équipe.

Le premier signe de problème : le redimensionnement d’images échouait de façon intermittente. Des vignettes apparaissaient pour certains uploads mais pas pour d’autres. Les éditeurs blâmaient WordPress. Les ingénieurs blâmaient le plugin de cache. Tout le monde se blâmait mutuellement. Les logs montraient des Permission denied sur des fichiers qui semblaient modifiables quand on les vérifiait sur une hôte, mais pas sur une autre.

L’« optimisation » était aussi une régression de fiabilité. Leur configuration de FS réseau avait des différences subtiles de traduction des permissions entre nœuds, et un déploiement créait parfois des répertoires avec un umask par défaut les rendant non modifiables par l’utilisateur PHP sur la moitié de la flotte. Ce n’était pas une panne constante. C’était pire : c’était intermittent.

La correction finale n’a pas été « chmod plus fort ». Ils ont standardisé les options de montage, forcé la création de répertoires avec setgid sur le répertoire partagé (pour que les nouveaux fichiers héritent du groupe correct), et ajouté une vérification de santé qui réalise un test d’écriture en tant qu’utilisateur PHP sur chaque nœud. Ils ont aussi documenté le modèle opérationnel : si vous partagez les uploads, traitez‑les comme un système de stockage, pas comme un dossier.

Mini-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise

Une organisation retail exécutait WordPress dans une stack plus large. Rien de spécial. L’équipe a fait une chose profondément peu sexy : rendre le code WordPress déployé en lecture seule, et stocker les uploads en dehors du répertoire de release sur un chemin persistant. Les déploiements créaient un symlink de public/wp-content/uploads vers /srv/wordpress-uploads/site1.

Puis une vulnérabilité de plugin a fait les gros titres. Pas une théorique : le genre où des bots balayent Internet à la recherche de fichiers PHP modifiables. Ils étaient déjà patchés en staging, mais le patch de production devait attendre une fenêtre de maintenance parce que l’équipe marketing était en pleine campagne (c’est toujours en pleine campagne).

Pendant cette fenêtre, les bots ont fait leur travail. Ils ont trouvé la page de connexion et ont essayé. Ils ont trouvé des endpoints de plugins et ont essayé. Ils ont réussi à atteindre le chemin vulnérable mais n’ont pas pu persister un web shell là où ils le souhaitaient parce que les répertoires de code n’étaient pas modifiables par l’utilisateur PHP. Les uploads étaient modifiables, mais l’exécution de PHP depuis uploads était bloquée au niveau du serveur web.

La réponse à l’incident a été calme. Pas de course pour diff des milliers de fichiers, pas d’« est‑ce que ce fichier est censé exister ? » archéologie. Ils ont patché, fait tourner les secrets, et ont poursuivi. La pratique ennuyeuse — séparer mutable et immutable — a transformé une compromission potentielle en bruit de fond.

Blague n°2 : Les permissions sont comme un code vestimentaire — trop strict et personne ne travaille, trop lâche et soudain c’est « vendredi spear‑phishing décontracté ».

Faits et contexte historique intéressants (parce qu’Unix est vieux et pointilleux)

  1. Le bit d’exécution sur les répertoires date d’avant la plupart des logiciels web. Il signifie « search » (traverser), pas « exécuter ». Confus volontairement, apparemment.
  2. La notation octale est devenue le raccourci humain parce que les bits de permission sont naturellement groupés par trois. Ce n’est pas arbitraire ; c’est le système qui correspond aux données.
  3. Le « sticky bit » sur les répertoires (comme /tmp) existe parce que les systèmes multi‑utilisateurs avaient besoin d’espaces partagés modifiables sans permettre aux utilisateurs de supprimer les fichiers des autres.
  4. Le setgid sur les répertoires est une astuce classique de collaboration : les nouveaux fichiers héritent du groupe du répertoire. C’est plus ancien que la plupart des pipelines CI/CD et souvent plus fiable.
  5. Umask explique pourquoi deux serveurs avec les mêmes commandes chmod peuvent produire des permissions différentes pour les fichiers nouvellement créés. C’est un masque par processus, pas une propriété du système de fichiers.
  6. Le modèle historique d’Apache avec de nombreux processus a influencé beaucoup de conseils legacy du type « chown tout à www-data ». PHP‑FPM et les runtimes conteneur ont changé la nature du problème.
  7. NFS et sémantiques de permissions ont brûlé d’innombrables équipes. Certains montages mappent root à nobody (root‑squash), et certains ne supportent pas la propriété Unix comme vous l’imaginez.
  8. SELinux a été conçu parce que le contrôle d’accès discrétionnaire (permissions Unix) ne peut pas exprimer « ce processus peut écrire seulement dans ces répertoires labellisés ». WordPress déclenche souvent ces règles.
  9. Le comportement de mise à jour automatique de WordPress est un produit de ses racines en hébergement mutualisé. La pratique de production moderne désactive souvent la mutation in‑place et traite le code comme des artefacts.

Listes de contrôle / plan étape par étape

Étape par étape : corriger les erreurs de permissions sans tout rendre modifiable

  1. Confirmer l’acteur : déterminer l’utilisateur runtime (www-data, nginx, etc.) via ps et la config du pool PHP‑FPM.
  2. Trouver le chemin en échec : à partir de l’erreur WordPress, des logs PHP ou des logs du serveur web. Ne devinez pas.
  3. Valider la traversée : lancez namei -l sur le chemin en échec et cherchez un répertoire parent qui bloque le bit d’exécution.
  4. Décidez votre modèle :
    • Code immuable + CI/CD, ou
    • wp-content modifiable (ou parties de celui‑ci), ou
    • Entièrement mutable (ne le faites pas).
  5. Corrigez la propriété pour les chemins modifiables seulement : typiquement wp-content/uploads et peut‑être des répertoires de cache. Utilisez chown -R www-data:www-data sur ces chemins.
  6. Appliquez les modes de base : répertoires 755, fichiers 644 pour l’arbre de code. Resserrez ensuite les fichiers spéciaux (wp-config.php).
  7. Exécutez un test d’écriture en tant qu’utilisateur PHP : sudo -u www-data touch ... dans le répertoire qui a échoué.
  8. Vérifiez montage/MAC : assurez‑vous que le filesystem est en rw ; vérifiez SELinux/AppArmor si applicable.
  9. Prévenez la dérive : intégrez la propriété/modes voulus dans l’automatisation de déploiement. Si un déploiement réinitialise les perms, vous reviendrez ici la semaine suivante.

Checklist : permissions WordPress durcies mais fonctionnelles (VM mono‑site typique)

  • Répertoires de code : possédés par deploy (ou root), lisibles par l’utilisateur web, non modifiables par l’utilisateur web.
  • wp-content/uploads : modifiable par l’utilisateur web ; de préférence un chemin persistant séparé avec permissions contrôlées.
  • Pas de répertoires world‑writable dans la docroot.
  • Optionnellement bloquer l’exécution PHP dans uploads au niveau du serveur web (ce n’est pas un bit de permission, mais ça compte).
  • Utilisez une stratégie de groupe ou des ACL plutôt que « tout possédé par www-data » si des humains doivent éditer des fichiers.

Checklist : multi‑serveur ou stockage partagé (là où ça devient épicé)

  • Standardisez l’utilisateur runtime sur les nœuds.
  • Utilisez setgid sur les répertoires partagés pour préserver le groupe.
  • Validez les options de montage et le mapping de propriétaire.
  • Ajoutez un test de santé de nœud qui écrit dans uploads.
  • Privilégiez le stockage d’objets pour les uploads si votre architecture le permet ; les montages POSIX partagés sont coûteux en exploitation.

FAQ

1) 755 et 644 sont‑ils toujours corrects pour WordPress ?

Non. Ce sont une base sûre pour la lisibilité et la traversée. La correction dépend de la propriété, de l’utilisateur PHP, et des répertoires qui doivent être modifiables. Dans certains environnements, 750/640 avec des groupes appropriés est préférable.

2) Pourquoi les répertoires ont‑ils besoin du bit d’exécution ?

Parce que l’exécution sur un répertoire signifie « peut traverser/le chercher ». Sans cela, le processus ne peut pas accéder aux fichiers à l’intérieur, même s’il peut lister le répertoire.

3) wp-config.php doit‑il être en 644 ?

Cela fonctionne souvent, mais ce n’est pas le meilleur défaut. Préférez 640 avec le groupe correct pour que seuls le propriétaire et le groupe web/PHP puissent le lire. Si votre serveur est mono‑locataire et que vous faites plus confiance au processus web qu’aux utilisateurs locaux, gardez‑le plus strict.

4) Est‑ce sûr de chown tout à www-data ?

C’est fonctionnel et courant, et c’est aussi un moyen simple pour un processus web compromis de modifier votre code applicatif. Si vous tenez à la confinement, gardez le code possédé par un utilisateur de déploiement et accordez seulement l’accès en écriture aux uploads/cache.

5) Les mises à jour WordPress échouent sauf si je rends plugins/themes modifiables. Que faire ?

Choisissez une politique. Si vous souhaitez des mises à jour par clic, vous devez autoriser l’écriture dans les répertoires plugins/themes (idéalement via groupe/ACL, pas 777). Si vous voulez un système plus sûr, désactivez les mises à jour in‑place et déployez les changements via CI/CD.

6) Quelle est la différence entre « Permission denied » et « Operation not permitted » ?

« Permission denied » signifie généralement que l’utilisateur courant manque de droits (mode/ACL/MAC). « Operation not permitted » indique souvent une restriction plus forte : attribut immuable, règles du filesystem, ou contraintes de namespace utilisateur dans un conteneur.

7) Mes perms sont correctes mais les uploads échouent toujours. Que peut‑il y avoir d’autre ?

Coupables courants : filesystem monté en lecture seule, refus SELinux/AppArmor, mauvais utilisateur du pool PHP‑FPM, disque plein (les écritures échouent), ou un répertoire parent bloquant la traversée. Vérifiez cela avant de re‑changer les modes.

8) Ai‑je besoin de 775 au lieu de 755 ?

Seulement si vous utilisez intentionnellement la collaboration basée sur le groupe (ex. deploy et PHP partagent un groupe). 775 accorde l’écriture au groupe. C’est acceptable lorsque l’appartenance au groupe est contrôlée et intentionnelle.

9) Qu’en est‑il de 700 pour les répertoires ?

700 est excellent pour les répertoires privés que seul le propriétaire doit traverser. C’est catastrophique pour une racine web sauf si le processus web en est propriétaire. Utilisez 750 si vous voulez « propriétaire+groupe seulement ».

10) Comment arrêter la dérive des permissions après les déploiements ?

Faites‑en une responsabilité de l’automatisation. Assurez‑vous que les scripts de déploiement définissent la propriété/les modes lors de la création de la release, préservent les répertoires persistants (uploads), et vérifient avec un test d’écriture en tant qu’utilisateur runtime.

Conclusion : prochaines étapes qui ne vous réveilleront pas à 3 h du matin

La plupart des incidents de permissions WordPress ne concernent pas les chiffres magiques. Ils concernent le décalage entre qui écrit et vous avez accidentellement bloqué la traversée ou la propriété. 755/644 sont de bonnes valeurs par défaut, mais ce n’est pas une stratégie.

Faites ceci ensuite

  1. Consignez votre modèle d’exploitation : WordPress peut‑il se mettre à jour seul ou non ? Décidez, puis faites respecter avec les permissions.
  2. Séparez mutable et immutable : gardez les uploads (et peut‑être le cache) modifiables ; gardez le cœur et la plupart du code en lecture seule pour l’utilisateur web.
  3. Automatisez l’état désiré : les scripts de déploiement doivent définir la propriété/le mode et les vérifier. Le chmod manuel est la source du folklore des permissions.
  4. Ajoutez une vérification de santé : un simple test d’écriture en tant qu’utilisateur PHP vers le répertoire uploads détecte la dérive avant que les éditeurs ne s’en aperçoivent.

Si vous suivez cela, 755/644 redeviendra ce qu’ils auraient toujours dû être : une base que vous comprenez, pas un rituel que vous répétez.

← Précédent
Dnodes et métadonnées ZFS : pourquoi les métadonnées peuvent être votre vrai goulot d’étranglement
Suivant →
Ubuntu 24.04 « No route to host » : checklist ARP/passarelle/ACL qui met fin aux conjectures (cas n°32)

Laisser un commentaire