Les mises à jour WordPress sont censées être ennuyeuses. Cliquez sur « Mettre à jour », attendez quelques secondes, et continuez votre vie.
Puis la production décide de vous rappeler qu’elle repose sur des systèmes de fichiers, des permissions, et sur la décision charmante que vous avez prise il y a six mois à 2 h du matin.
Si vous voyez « Update failed », « Could not create directory », « Permission denied », « PCLZIP_ERR », « Disk full », ou un site bloqué en mode maintenance,
voici la marche à suivre pratique pour en sortir. Pas le chemin « chmod 777 jusqu’à ce que ça marche ». Le bon chemin.
Feuille de diagnostic rapide
Quand les mises à jour échouent, ne commencez pas par modifier les permissions. Commencez par identifier le goulot d’étranglement.
WordPress vous signale « update failed », mais le système vous dit pourquoi. Votre travail est d’écouter le système.
Première étape : déterminer quel type d’échec il s’agit
- Le système de fichiers est-il inscriptible pour l’utilisateur web/PHP ? Sinon, vous aurez « Permission denied » ou « Could not create directory ».
- L’espace disque ou les inodes sont-ils épuisés ? Les mises à jour écrivent des fichiers temporaires, décompressent des archives et font des renommages atomiques. Elles ont besoin d’espace libre.
- Le répertoire temporaire est-il inscriptible et suffisamment grand ? PHP utilise
/tmp(souvent tmpfs) ou un chemin temporaire configuré. - WordPress est-il coincé en mode maintenance ? Il a peut‑être réussi à mi‑parcours et laissé
.maintenancederrière lui. - Le processus de mise à jour dépasse‑t‑il le délai imparti ? Les gros plugins/thèmes peuvent déclencher des timeouts PHP ou des limites de mémoire.
Deuxième étape : vérifier l’erreur au bon niveau
- Interface WordPress / journaux : Les messages de l’interface admin sont souvent vagues. Mieux que rien, mais insuffisant.
- Journaux du serveur web / PHP-FPM : Contiennent souvent le vrai chemin rejeté par « Permission denied » ou « No space left on device ».
- Niveau système :
df,mount,ls -l,namei,getfacl. C’est là que se trouve la vérité.
Troisième étape : appliquer la correction la moins risquée en premier
- Espace disque/inodes : Facile à vérifier et explique souvent immédiatement des échecs « mystérieux ».
- Incohérences de propriété : Corrigez le décalage pour que l’utilisateur prévu puisse écrire. Évitez l’écriture mondiale.
- Bits de permission/ACL : Corrigez au minimum nécessaire. Gardez
wp-config.phpserré. - Délai/mémoire : Si ça échoue pendant la décompression ou le téléchargement, ajustez les limites PHP ou utilisez WP‑CLI.
Un principe directeur : si votre correctif est « rendre tout 777 », vous n’avez pas corrigé le problème. Vous avez juste déplacé le champ d’explosion vers la sécurité.
Comment fonctionnent réellement les mises à jour WordPress (et pourquoi elles échouent)
Les mises à jour WordPress sont des opérations sur fichiers déguisées en bouton. WordPress (via PHP) télécharge un zip, l’écrit dans une zone temporaire,
le décompresse, puis remplace les fichiers en place. Les plugins et thèmes suivent des logiques similaires, ciblant d’autres répertoires.
Les chemins d’update courants
- Mises à jour du cœur : écrivent dans la racine WordPress et
wp-includes/wp-admin. Elles écriront aussi.maintenance. - Mises à jour de plugins : écrivent dans
wp-content/pluginset peuvent remplacer un répertoire de plugin entièrement. - Mises à jour de thèmes : écrivent dans
wp-content/themes. - Paquets de langue : écrivent dans
wp-content/languages.
Pourquoi ça échoue
Les mises à jour échouent quand PHP ne peut pas écrire les fichiers nécessaires, ne peut pas créer de fichiers temporaires, ne peut pas renommer des répertoires,
ou manque de ressources en cours d’opération. Le message d’erreur dépend de la fonction qui a échoué : initialisation du système de fichiers, unzip, copie, renommage ou nettoyage.
La plus grande erreur conceptuelle : penser que « WordPress » possède ces fichiers. Ce n’est pas le cas. Le système d’exploitation les possède. L’utilisateur du serveur web les possède.
Votre processus de déploiement y participe. WordPress n’est qu’un processus PHP qui demande au système d’exploitation la permission d’effectuer des opérations sur fichiers.
Faits et histoire intéressants (court, utile, un peu geek)
- Les mises à jour automatiques en arrière‑plan du cœur sont devenues courantes autour de la version 3.7, déplaçant les modes d’échec de « l’humain a oublié » à « la machine a essayé et a heurté des permissions ».
- WordPress utilise une couche d’abstraction du système de fichiers qui peut choisir entre des écritures « directes » et des méthodes FTP/SSH selon ce qu’il détecte.
- Beaucoup de problèmes de « permissions WordPress » sont en réalité des problèmes de traversal des répertoires parents ; un bit non‑exécutable (
x) sur un répertoire peut bloquer tout ce qui est en dessous. - Les inodes comptent autant que les octets ; vous pouvez avoir des gigaoctets libres et échouer si vous avez utilisé tous les inodes (fréquent sur des systèmes de petits blocs avec beaucoup de fichiers cache).
- Les renommages atomiques sont une astuce classique de déploiement, mais ils nécessitent la permission d’écriture sur le répertoire parent, pas seulement sur la cible.
- tmpfs pour /tmp est courant sur les Linux modernes ; c’est rapide, mais dimensionné à partir de la RAM et peut se remplir lors d’opérations d’unzip.
- La décompression zip en PHP a historiquement reposé sur des outils externes ou des bibliothèques ; l’absence du support ZipArchive apparaît encore comme des erreurs d’unzip sur certaines distributions.
- « WordPress bloqué en mode maintenance » est généralement un fichier restant nommé
.maintenance, créé durant les mises à jour et supprimé après le succès.
Tâches pratiques : commandes, sorties, décisions (12+)
Voici les vérifications que j’exécute en production, dans l’ordre où elles rapportent le plus. Chaque tâche inclut :
la commande, ce que signifie la sortie typique, et la décision à prendre en conséquence.
Task 1: Confirm disk space on the relevant mount
cr0x@server:~$ df -hT /var/www/html
Filesystem Type Size Used Avail Use% Mounted on
/dev/vda1 ext4 40G 39G 520M 99% /
Sens : 99% utilisé. Les mises à jour ont besoin d’espace pour les téléchargements, l’extraction et parfois des écritures doubles.
Décision : Résoudre l’espace disque avant de toucher aux permissions. Nettoyez les logs, anciennes sauvegardes, caches, ou étendez le système de fichiers. Si vous faites « chmod » maintenant, vous polissez un navire qui coule.
Task 2: Check inode exhaustion (yes, it happens)
cr0x@server:~$ df -i /var/www/html
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/vda1 2621440 2621400 40 100% /
Sens : Les inodes sont épuisés. Créer un fichier de plus échoue avec « No space left on device » même si des octets sont disponibles.
Décision : Identifiez les répertoires avec un grand nombre de fichiers (dossiers de cache, répertoires de session) et purgez‑les. Ne redémarrez pas en espérant que cela supprime des fichiers.
Task 3: Identify the PHP execution user (the one that actually writes)
cr0x@server:~$ ps -eo user,comm,args | egrep 'php-fpm|apache2|httpd' | head
www-data php-fpm8.2 php-fpm: pool www
www-data php-fpm8.2 php-fpm: pool www
root nginx nginx: master process /usr/sbin/nginx -g daemon on;
Sens : Les workers PHP‑FPM tournent sous www-data. Le master nginx est root, mais les workers proxent vers PHP‑FPM.
Décision : Les permissions d’écriture doivent fonctionner pour www-data, pas pour votre utilisateur SSH. Si vous corrigez la propriété pour vous, les mises à jour échoueront toujours.
Task 4: Validate the WordPress root and wp-content ownership quickly
cr0x@server:~$ ls -ld /var/www/html /var/www/html/wp-content
drwxr-xr-x 5 root root 4096 Dec 27 08:10 /var/www/html
drwxr-xr-x 10 root root 4096 Dec 27 08:10 /var/www/html/wp-content
Sens : Root possède tout ; le groupe n’est pas inscriptible ; l’utilisateur web ne peut pas écrire. Les mises à jour de plugins/thèmes échoueront.
Décision : Choisissez votre modèle de propriété (voir plus loin). Pour la plupart des installations mono‑serveur : faites www-data propriétaire des chemins inscriptibles, ou définissez une propriété de groupe partagée.
Task 5: Confirm the exact failing path from logs
cr0x@server:~$ sudo tail -n 50 /var/log/php8.2-fpm.log
[27-Dec-2025 08:12:19] WARNING: [pool www] child 22190 said into stderr: "PHP Warning: copy(/var/www/html/wp-content/plugins/akismet/.htaccess): failed to open stream: Permission denied in /var/www/html/wp-admin/includes/class-wp-filesystem-direct.php on line 309"
Sens : Ce n’est pas un vague « update failed ». C’est un chemin de fichier spécifique et une fonction. L’OS a refusé l’écriture.
Décision : Corrigez les permissions/propriété pour ce sous‑arbre de répertoire. Si le chemin est sous wp-content, c’est votre zone inscriptible.
Task 6: Verify directory traversal permissions with namei
cr0x@server:~$ namei -l /var/www/html/wp-content/plugins
f: /var/www/html/wp-content/plugins
drwxr-xr-x root root /
drwxr-xr-x root root var
drwxr-xr-x root root www
drwxr-xr-x root root html
drwxr-xr-x root root wp-content
drwxr-xr-x root root plugins
Sens : Même si plugins semble correct, vous devez avoir l’exécutable (x) sur chaque répertoire du chemin pour le traverser.
Décision : Si un répertoire parent n’a pas le bit x pour l’utilisateur web (ou le groupe), corrigez‑le. « Mais wp-content est inscriptible » n’aide pas si /var/www bloque le parcours.
Task 7: Check ACLs if permissions look “right” but still fail
cr0x@server:~$ getfacl -p /var/www/html/wp-content | sed -n '1,20p'
# file: /var/www/html/wp-content
# owner: root
# group: root
user::rwx
group::r-x
other::r-x
Sens : Pas d’entrées ACL ici ; ce sont des bits classiques. Si vous voyez une ACL refusant l’accès, elle peut annuler vos attentes.
Décision : Si des ACL existent et sont incorrectes, corrigez‑les intentionnellement. Ne les supprimez pas au hasard sauf si vous savez pourquoi elles avaient été ajoutées.
Task 8: Confirm the mount is not read-only
cr0x@server:~$ mount | grep ' / '
/dev/vda1 on / type ext4 (rw,relatime,errors=remount-ro)
Sens : Monté en lecture‑écriture maintenant, mais errors=remount-ro signifie qu’une erreur du système de fichiers peut le basculer en lecture seule.
Décision : Si vous voyez (ro,...), stoppez. Corrigez le problème disque/système de fichiers sous‑jacent d’abord (dmesg, fsck, santé du stockage). Les mises à jour ne peuvent pas continuer.
Task 9: Verify PHP temp directory and its free space
cr0x@server:~$ php -i | egrep 'upload_tmp_dir|sys_temp_dir|temporary' | head
upload_tmp_dir => no value => no value
sys_temp_dir => no value => no value
cr0x@server:~$ df -hT /tmp
Filesystem Type Size Used Avail Use% Mounted on
tmpfs tmpfs 1.0G 980M 44M 96% /tmp
Sens : PHP utilise par défaut le temp système (souvent /tmp). Si /tmp est petit ou plein, unzip/téléchargement échoue.
Décision : Libérez de l’espace dans /tmp ou définissez un répertoire temporaire dédié sur disque avec les permissions correctes pour PHP‑FPM.
Task 10: Confirm ZipArchive availability
cr0x@server:~$ php -m | grep -i zip
zip
Sens : Le support zip est chargé. Si absent, WordPress peut retomber sur des méthodes plus lentes ou échouer selon l’environnement.
Décision : Si non présent, installez/activez l’extension PHP zip et redémarrez PHP‑FPM. Les erreurs d’unzip sont souvent une « dépendance manquante », pas « WordPress cassé ».
Task 11: Check for the maintenance mode file
cr0x@server:~$ ls -la /var/www/html/.maintenance
-rw-r--r-- 1 root root 58 Dec 27 08:12 /var/www/html/.maintenance
Sens : WordPress pense qu’une mise à jour est en cours. Si une mise à jour a planté, ce fichier peut persister et bloquer le site.
Décision : Supprimez‑le seulement après avoir confirmé qu’aucune mise à jour n’est activement en cours. Relancez ensuite la mise à jour.
Task 12: Use WP-CLI to reproduce the failure with clearer output
cr0x@server:~$ cd /var/www/html
cr0x@server:~$ sudo -u www-data wp plugin update --all
Downloading update from https://downloads.wordpress.org/plugin/example.1.2.3.zip...
Unpacking the update...
Error: Could not create directory '/var/www/html/wp-content/upgrade'.
Sens : L’échec est cohérent et pointe vers le répertoire de travail d’upgrade.
Décision : Assurez‑vous que wp-content est inscriptible et autorisez spécifiquement la création de wp-content/upgrade.
Task 13: Verify ownership and permissions on the specific failing directory
cr0x@server:~$ ls -ld /var/www/html/wp-content /var/www/html/wp-content/upgrade
drwxr-xr-x 10 root root 4096 Dec 27 08:10 /var/www/html/wp-content
ls: cannot access '/var/www/html/wp-content/upgrade': No such file or directory
Sens : Il ne peut pas créer upgrade parce que wp-content n’est pas inscriptible par PHP.
Décision : Corrigez la propriété/groupe inscriptible pour wp-content (pas nécessairement toute la racine WordPress).
Task 14: Spot “permission denied” caused by immutable attribute
cr0x@server:~$ lsattr -d /var/www/html/wp-content
-------------e---- /var/www/html/wp-content
Sens : Pas de drapeau immutable ici. Si vous voyez i, le fichier/répertoire est immuable et ne peut pas être modifié même par root.
Décision : Si l’attribut immutable est défini, retirez‑le délibérément sur les chemins affectés. Généralement c’était une tentative de « durcissement » ou un script de sauvegarde mal orienté.
Corriger permissions et propriété des fichiers sans compromettre la sécurité
Vous avez besoin d’un modèle. « Rendre inscriptible simplement » n’est pas un modèle. Un modèle vous dit qui est autorisé à écrire quels répertoires, et pourquoi.
Les trois modèles de propriété sensés
Modèle A : l’utilisateur web possède le contenu inscriptible (commun, simple, plus risqué)
Dans ce modèle, www-data (ou l’utilisateur PHP en cours) possède wp-content et peut écrire les uploads et mises à jour de plugins/thèmes.
Les fichiers du cœur peuvent rester propriété de root pour réduire le risque de manipulation du code cœur.
À utiliser quand : serveur unique, petite équipe, mises à jour via l’interface admin attendues, vous acceptez le compromis de sécurité.
À éviter quand : contraintes de conformité, hébergement mutualisé avec plusieurs sites, ou si vous voulez l’immutabilité du code.
Modèle B : propriété de groupe partagée + répertoires setgid (mon choix par défaut pour les équipes)
Créez un groupe (par ex. wp), ajoutez l’utilisateur de déploiement et l’utilisateur web comme membres, et définissez la propriété de groupe sur les zones inscriptibles.
Utilisez setgid sur les répertoires pour que les nouveaux fichiers héritent du groupe.
À utiliser quand : vous déployez via SSH/CI et souhaitez toujours que WordPress écrive dans wp-content.
Modèle C : l’utilisateur web n’écrit rien ; déploiements et mises à jour via CI uniquement (le plus sécurisé, le plus strict)
Les mises à jour WordPress se font via des pipelines CI ou des fenêtres de maintenance. L’utilisateur web ne peut pas écrire dans le code.
Les uploads (médias) peuvent résider sur un montage inscriptible séparé ou dans un stockage objet intégré.
À utiliser quand : environnements réglementés, fort trafic, instances multiples, ou si vous avez déjà subi des incidents.
Choisissez-en un. Les mélanges faits au hasard sont la façon dont vous vous retrouvez avec un système de fichiers qui ne fonctionne que quand vous êtes connecté en tant que « la bonne » personne le mardi.
Objectifs de permission pratiques et sûrs
- Répertoires : typiquement
755(ou775si vous comptez sur l’écriture groupe). - Fichiers : typiquement
644(ou664avec écriture groupe). - wp-config.php : généralement
640ou600, selon la stratégie de groupe.
WordPress doit pouvoir écrire dans wp-content (plugins/themes/languages/uploads) pour les mises à jour via l’admin.
Il n’a pas besoin d’écrire partout ailleurs, et vous ne devriez pas le lui permettre sauf raison.
Implémenter le Modèle B (groupe partagé) sans drame
cr0x@server:~$ sudo groupadd -f wp
cr0x@server:~$ sudo usermod -aG wp www-data
cr0x@server:~$ sudo usermod -aG wp deploy
cr0x@server:~$ sudo chgrp -R wp /var/www/html/wp-content
cr0x@server:~$ sudo find /var/www/html/wp-content -type d -exec chmod 2775 {} \;
cr0x@server:~$ sudo find /var/www/html/wp-content -type f -exec chmod 664 {} \;
Sens : Le groupe est wp. Les répertoires sont setgid (2 dans 2775) pour que les nouveaux fichiers gardent la propriété de groupe.
Décision : Si vous voyez de nouveaux fichiers venant avec le mauvais groupe plus tard, votre umask ou la configuration du service vous contrarie. Corrigez‑ça ensuite.
Définir un umask prévisible pour PHP-FPM (optionnel mais souvent nécessaire)
Si PHP‑FPM crée des fichiers avec des permissions restrictives, les mises à jour peuvent réussir partiellement puis échouer quand un autre processus tente de les modifier.
La correction consiste à assurer une cohérence des permissions de création.
cr0x@server:~$ sudo grep -R "umask" -n /etc/php/8.2/fpm/pool.d
/etc/php/8.2/fpm/pool.d/www.conf:404:;php_admin_value[error_log] = /var/log/php8.2-fpm.log
Sens : Pas d’umask explicite dans la config du pool (classique). Les modes de création dépendent des valeurs par défaut.
Décision : Si vous avez besoin d’artefacts inscriptibles par le groupe, définissez l’umask au niveau du service ou imposez‑le avec des ACL (ci‑dessous).
Utiliser des ACL par défaut pour maintenir l’écriture groupe cohérente (puissant, facile à oublier)
cr0x@server:~$ sudo setfacl -R -m g:wp:rwx /var/www/html/wp-content
cr0x@server:~$ sudo setfacl -R -d -m g:wp:rwx /var/www/html/wp-content
cr0x@server:~$ getfacl -p /var/www/html/wp-content | sed -n '1,25p'
# file: /var/www/html/wp-content
# owner: root
# group: wp
user::rwx
group::r-x
group:wp:rwx
mask::rwx
other::r-x
default:user::rwx
default:group::r-x
default:group:wp:rwx
default:mask::rwx
default:other::r-x
Sens : Tous les nouveaux fichiers/répertoires créés sous wp-content obtiennent le groupe wp avec rwx (sous réserve du mask).
Décision : Si votre environnement gère bien les ACL, cela évite le problème « créé en 640, maintenant la mise à jour ne peut pas écraser ». Si vos sauvegardes ou outils de synchronisation ne préservent pas les ACL, documentez‑le.
Blague #1 : Si votre plan d’incident est « chmod -R 777 », félicitations — vous avez inventé un générateur distribué de faille de sécurité.
Résoudre les problèmes d’espace disque et d’inodes (les tueurs silencieux)
Les mises à jour WordPress sont sournoisement gourmandes en espace. Le processus a souvent besoin :
d’espace pour télécharger le zip, d’espace pour extraire les fichiers, plus les fichiers finaux installés.
En pratique, prévoyez au moins quelques centaines de mégaoctets libres pour de petites mises à jour et davantage pour de gros plugins/thèmes.
Trouver rapidement ce qui utilise l’espace
cr0x@server:~$ sudo du -xhd1 /var | sort -h
8.0M /var/cache
120M /var/log
4.2G /var/lib
12G /var/www
Sens : /var/www est énorme. Ça peut être des uploads, des sauvegardes ou des caches.
Décision : Approfondissez le plus gros répertoire. Ne supprimez pas aveuglément. Si ce sont des uploads, envisagez de déplacer les médias hors du disque racine ou de purger les anciennes sauvegardes.
Trouver les répertoires très consommateurs d’inodes (beaucoup de petits fichiers)
cr0x@server:~$ sudo find /var/www/html/wp-content -xdev -type f | wc -l
412356
Sens : Des centaines de milliers de fichiers. Les plugins de cache peuvent provoquer cela. Chaque fichier consomme un inode.
Décision : Identifiez les répertoires de cache et configurez le cache pour utiliser moins de fichiers ou un autre stockage. Si c’est un cache, il est acceptable de le détruire.
Localiser les « grosseurs d’espace » WordPress courantes
cr0x@server:~$ sudo du -sh /var/www/html/wp-content/* | sort -h | tail -n 10
120M /var/www/html/wp-content/languages
1.3G /var/www/html/wp-content/plugins
3.8G /var/www/html/wp-content/uploads
9.1G /var/www/html/wp-content/cache
Sens : Le cache est énorme. Souvent sûr à purger pendant un incident (avec des conséquences de performance).
Décision : Videz le cache pour récupérer de l’espace, puis corrigez la cause racine (configuration du cache, rotation des logs, externalisation des uploads, dimensionnement du disque).
Vider un répertoire de cache en toute sécurité (exemple)
cr0x@server:~$ sudo systemctl stop php8.2-fpm
cr0x@server:~$ sudo rm -rf /var/www/html/wp-content/cache/*
cr0x@server:~$ sudo systemctl start php8.2-fpm
Sens : Vous évitez les écritures concurrentes pendant le nettoyage. Pas toujours nécessaire, mais réduit les douleurs d’effets de bord.
Décision : Si arrêter PHP‑FPM est trop perturbant, supprimez plus prudemment (ou videz via l’UI du plugin). Mais en urgence, l’espace prime sur le cache.
Répertoires temporaires, échecs d’unzip et limites PHP
Les erreurs d’unzip sont souvent imputées à WordPress parce que c’est la partie visible. Mais l’échec vient souvent de :
stockage temporaire plein, permissions incorrectes, ZipArchive manquant, ou mémoire PHP insuffisante en cours d’extraction.
Confirmer où PHP écrit les fichiers temporaires
cr0x@server:~$ php -r 'echo sys_get_temp_dir().PHP_EOL;'
/tmp
Sens : PHP utilise /tmp.
Décision : Assurez‑vous que /tmp est inscriptible par l’utilisateur PHP‑FPM et qu’il y a de l’espace. Si /tmp est tmpfs et se remplit fréquemment, déplacez le temp ailleurs.
Définir un répertoire temporaire dédié pour les mises à jour WordPress
Approche pragmatique : créez /var/tmp/wp-tmp, faites‑le appartenir à l’utilisateur PHP, et pointez WordPress dessus via WP_TEMP_DIR.
cr0x@server:~$ sudo install -d -o www-data -g www-data -m 1770 /var/tmp/wp-tmp
cr0x@server:~$ sudo grep -n "WP_TEMP_DIR" /var/www/html/wp-config.php || true
cr0x@server:~$ sudo sh -c "printf '\ndefine(\"WP_TEMP_DIR\", \"/var/tmp/wp-tmp\");\n' >> /var/www/html/wp-config.php"
cr0x@server:~$ sudo tail -n 3 /var/www/html/wp-config.php
define("WP_TEMP_DIR", "/var/tmp/wp-tmp");
Sens : WordPress utilise maintenant un répertoire temporaire contrôlé. 1770 donne un comportement « sticky » via la séparation de groupe ; adaptez selon votre modèle.
Décision : Si plusieurs pools/utilisateurs utilisent le même répertoire, séparez‑les. Un temp partagé devient un bourbier de permissions.
Vérifier la limite mémoire PHP et le temps d’exécution maximal (courant pour gros plugins)
cr0x@server:~$ php -i | egrep 'memory_limit|max_execution_time' | head -n 2
memory_limit => 128M => 128M
max_execution_time => 30 => 30
Sens : 128MB et 30s conviennent aux petits sites, mais peuvent échouer pour de grosses mises à jour, disques lents, ou serveurs surchargés.
Décision : Si les mises à jour expirent, augmentez ces valeurs pour les opérations d’admin/update ou exécutez les mises à jour avec WP‑CLI dans un shell contrôlé (utilise toujours PHP, mais moins de contraintes navigateur).
Apache, Nginx, PHP-FPM : modèles utilisateur qui changent tout
Les permissions ne sont pas « une chose WordPress ». C’est une question de « quel utilisateur Unix écrit les fichiers ». Diffentes piles choisissent différents utilisateurs.
Apache avec mod_php
PHP s’exécute dans les processus workers Apache. L’utilisateur Apache (souvent www-data ou apache) est l’écrivain de fichiers.
Si Apache écrit en tant que www-data mais vos déploiements écrivent en tant que deploy avec umask 077, vous aurez des propriétaires mélangés et des échecs futurs.
Nginx + PHP-FPM
Nginx sert les fichiers statiques et passe PHP à PHP‑FPM. L’utilisateur qui écrit est l’utilisateur du pool PHP‑FPM, pas Nginx.
C’est bien : PHP s’exécute sous une identité plus contrôlée. Cela signifie aussi que « Nginx a accès » est sans rapport si PHP‑FPM ne l’a pas.
Plusieurs sites sur un même hôte
Si vous exécutez plusieurs sites WordPress sous le même www-data, vous avez créé un environnement à destin partagé.
Une compromission sur un site peut souvent écrire dans les répertoires d’un autre site si les permissions le permettent.
Si vous prenez la sécurité au sérieux, utilisez des utilisateurs/pools par site et isolez au niveau système de fichiers.
Une citation opérationnelle (idée paraphrasée)
« L’espoir n’est pas une stratégie. » — idée paraphrasée couramment attribuée au leadership en ingénierie et opérations.
Trois mini-histoires d’entreprise tirées de schémas réels
Mini‑histoire 1 : L’incident causé par une mauvaise hypothèse
Une entreprise de taille moyenne exploitait WordPress pour le marketing, mais le système vivait sur une VM de production « réelle » avec d’autres services.
Leur hypothèse interne était simple : « Si le site fonctionne, les mises à jour sont sûres à faire à tout moment. »
Cette hypothèse est ce qui vous fait recevoir une page.
L’équipe marketing a cliqué sur la mise à jour d’un plugin pendant les heures ouvrables. La mise à jour a échoué, laissant .maintenance derrière elle.
Le site a commencé à renvoyer des réponses de maintenance. Les ventes ont escaladé car la page de campagne était hors service.
Le premier intervenant a fait ce que beaucoup font sous pression : redémarrer les services. Cela n’a rien résolu. Ça a juste rendu les logs plus difficiles à corréler.
La cause racine était la pression disque. Pas assez pour déclencher la supervision, mais assez pour que l’extraction d’un zip dans /tmp échoue.
/tmp était un tmpfs dimensionné en RAM et déjà encombré par des processus non liés.
WordPress a mis le site en maintenance, a tenté d’écrire les fichiers d’upgrade, a échoué, et n’a jamais nettoyé.
La correction a été ennuyeuse : libérer de l’espace, déplacer le temp vers le disque, supprimer .maintenance, relancer la mise à jour via WP‑CLI.
La leçon n’était pas « ne pas mettre à jour pendant les heures ouvrables » (bien que peut‑être). La leçon était : les mises à jour sont des opérations de stockage ; traitez‑les comme des déploiements.
Mini‑histoire 2 : L’optimisation qui a mal tourné
Une autre organisation voulait « plus de performance ». Quelqu’un a décidé de monter tout le répertoire WordPress sur un système de fichiers réseau
pour que plusieurs serveurs applicatifs partagent le même code et les mêmes uploads. L’idée sonnait bien en réunion.
Le système de fichiers n’avait pas assisté à la réunion.
Les mises à jour ont commencé à échouer de façon intermittente avec des erreurs d’unzip étranges et des répertoires de plugin incomplets.
Parfois l’UI admin disait succès, mais le chargement suivant crashait avec des fichiers PHP manquants.
Les ingénieurs ont couru après les permissions, les versions PHP, même « peut‑être WordPress est corrompu ».
Le vrai problème était la sémantique et la latence : le comportement du système de fichiers réseau concernant les verrous et les renommages atomiques ne correspondait pas aux attentes du processus de mise à jour.
Le processus d’update repose sur des opérations de fichiers fiables et rapides, en particulier lors d’échanges de répertoire.
Sous charge, les opérations s’attardaient, expiraient, ou retournaient des listings incohérents à différents nœuds.
Ils ont résolu en séparant les responsabilités : le code déployé comme artefacts immuables par nœud, les uploads stockés séparément avec une conception adaptée aux forces du stockage.
Les mises à jour sont passées au CI, pas à l’UI admin. Les performances se sont améliorées et le processus d’update a cessé de jouer à la roulette.
Mini‑histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une grande entreprise avait l’habitude, qui semblait démodée : chaque mise à jour WordPress était exécutée via WP‑CLI dans une fenêtre de maintenance,
précédée d’un snapshot système et suivie d’une vérification d’intégrité rapide.
Personne n’en faisait l’éloge. C’était juste « le runbook. »
Un jour, une mise à jour de plugin a introduit une erreur fatale due à une dépendance PHP inattendue.
L’UI admin n’était pas accessible à cause de l’erreur fatale, mais le site servait encore des pages mises en cache — jusqu’à l’expiration du cache.
L’incident aurait pu dégénérer en panne complète.
Parce que les mises à jour étaient exécutées via WP‑CLI avec des logs capturés, ils ont su exactement quel plugin a changé et quand.
Parce que le système de fichiers avait été snapshoté, le rollback a pris quelques secondes.
Ils ont restauré le répertoire du plugin, rétabli le service, puis testé la mise à jour en staging avec le module PHP manquant installé.
Rien d’héroïque. C’est le point. Les pratiques ennuyeuses sont ce qui vous permet d’être ennuyeux au milieu d’un incident.
Erreurs courantes : symptômes → cause racine → fix
Voici les schémas que je vois régulièrement. Le symptôme est ce que vous remarquerez. La cause racine est ce qui est réellement cassé. Le correctif est précis.
1) « Could not create directory » pendant la mise à jour d’un plugin/thème
- Symptôme : La mise à jour échoue ; WP‑CLI affiche qu’il ne peut pas créer
wp-content/upgradeou un répertoire de plugin. - Cause racine :
wp-contentnon inscriptible par l’utilisateur PHP ; ou répertoires parents sans bit exécutable. - Correctif : Ajustez propriété/groupe et permissions pour
wp-contentuniquement ; vérifiez avecnamei -let relancez la mise à jour.
2) « Update failed » sans détails ; les logs montrent « No space left on device »
- Symptôme : L’interface est vague ; les logs PHP montrent ENOSPC lors de la copie/décompression.
- Cause racine : Disque plein ou inodes épuisés ; parfois
/tmpplein sur tmpfs. - Correctif : Vérifiez
df -hT,df -i, etdf -h /tmp. Libérez de l’espace ou déplacez le temp. Puis relancez.
3) Site coincé en mode maintenance après une mise à jour ratée
- Symptôme : Le frontend affiche le message de maintenance indéfiniment.
- Cause racine :
.maintenancelaissé derrière à cause d’un crash/timeout/permission. - Correctif : Vérifiez qu’aucun processus de mise à jour n’est en cours ; supprimez
.maintenance; corrigez l’erreur sous‑jacente ; relancez la mise à jour via WP‑CLI.
4) Les mises à jour fonctionnent en root via SSH mais échouent dans l’UI admin
- Symptôme : Les modifications manuelles réussissent ; les mises à jour via l’UI échouent.
- Cause racine : Mauvaise hypothèse sur l’écrivain de fichiers ; PHP s’exécute comme utilisateur non privilégié, pas root.
- Correctif : Alignez les permissions du système de fichiers avec l’utilisateur PHP (ou utilisez le modèle de groupe partagé). Arrêtez de « corriger » en root et d’appeler ça fini.
5) Certains fichiers sont possédés par l’utilisateur de déploiement, d’autres par www-data ; les mises à jour échouent aléatoirement
- Symptôme : La mise à jour d’aujourd’hui réussit ; celle de la semaine suivante échoue. Propriété inconsistante à travers l’arbre.
- Cause racine : Voies de mise à jour mixtes : parfois via UI admin (www-data), parfois via SSH/CI (deploy), avec umask incompatible.
- Correctif : Choisissez un modèle. Utilisez groupe partagé + setgid + (optionnel) ACL par défaut. Imposer un umask cohérent dans votre processus de déploiement.
6) « PCLZIP_ERR » ou erreur d’unzip sur gros plugins/thèmes
- Symptôme : Erreurs d’extraction, répertoires incomplets, ou timeouts.
- Cause racine : Peu d’espace temp, ZipArchive manquant, limites mémoire/temps atteintes, ou stockage lent.
- Correctif : Assurez‑vous que l’extension zip est présente, allouez de l’espace temp, ajustez les limites PHP. Préférez WP‑CLI pour des mises à jour contrôlées.
7) Les mises à jour échouent seulement sur un nœud du cluster
- Symptôme : Un nœud signale des fichiers de plugin manquants ou des versions différentes.
- Cause racine : Disques locaux divergents ; sémantique du stockage partagé incompatible ; déploiements non coordonnés.
- Correctif : Arrêtez de mettre à jour via l’UI admin sur les configurations en cluster. Utilisez un déploiement CI et gardez le code immuable sur tous les nœuds.
Blague #2 : Le processus de mise à jour est comme un chat — si vous ne contrôlez pas où il écrit, il choisira l’endroit le moins pratique disponible.
Listes de contrôle / plan pas à pas
Checklist incident : remettre le site et terminer la mise à jour
- Vérifiez disque et inodes :
df -hT,df -i, etdf -h /tmp. Libérez de l’espace d’abord. - Vérifiez le mode maintenance : supprimez
.maintenanceuniquement après avoir confirmé qu’aucune mise à jour n’est en cours. - Lisez les logs : journaux PHP‑FPM et du serveur web pour le chemin précis qui échoue.
- Confirmez l’utilisateur PHP : identifiez si les écritures se font en
www-data,apacheou un utilisateur de pool. - Corrigez l’inscriptibilité de wp-content : propriété/groupe/ACL selon le modèle choisi. Vérifiez avec
namei -l. - Réessayez via WP‑CLI : c’est plus clair, scriptable, et moins soumis aux timeouts navigateur.
- Validez : chargez la page d’accueil, l’admin, et quelques pages qui sollicitent des plugins (formulaires, checkout, etc.).
Checklist durcissement : éviter les incidents répétés
- Choisissez votre modèle de propriété : web‑owned, groupe partagé, ou CI‑only. Documentez‑le. Faites‑le respecter.
- Séparez les zones inscriptibles : gardez
wp-content/uploadsinscriptible ; envisagez de rendre le cœur en lecture seule. - Définissez des permissions prévisibles : setgid sur les répertoires et/ou ACL par défaut pour l’écriture groupe.
- Dimensionnez le stockage temporaire : répertoire temp dédié si
/tmpest trop petit ou partagé. - Surveillez les bonnes choses : octets disque, inodes, et utilisation de
/tmp. Les alertes doivent se déclencher avant 99%. - Privilégiez WP‑CLI pour les mises à jour en production : capturez les logs ; exécutez en fenêtre de maintenance ; automatisez les étapes de rollback.
Pas à pas : runbook sûr « corriger les permissions » (modèle groupe partagé)
Voici le plan qui fonctionne dans les organisations réelles où humains et automatisation touchent le système de fichiers.
- Créez le groupe
wp; ajoutezwww-dataet votre utilisateur de déploiement. - Définissez la propriété de groupe sur
wp-contentet setgid sur ses répertoires. - Définissez les modes de fichiers et répertoires pour permettre l’écriture du groupe, mais pas l’écriture mondiale.
- Optionnellement définissez les ACL par défaut pour garder le comportement cohérent malgré les umask variables.
- Vérifiez en créant un fichier test en tant que
www-dataet en tant que deploy, confirmez groupe et permissions.
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/html/wp-content/.permtest && ls -l /var/www/html/wp-content/.permtest'
-rw-rw-r-- 1 www-data wp 0 Dec 27 08:40 /var/www/html/wp-content/.permtest
cr0x@server:~$ sudo -u deploy bash -lc 'echo ok >> /var/www/html/wp-content/.permtest && tail -n 1 /var/www/html/wp-content/.permtest'
ok
Sens : Les deux identités peuvent écrire. La propriété et le groupe sont cohérents.
Décision : Si l’un des utilisateurs ne peut pas écrire, corrigez l’adhésion au groupe, les permissions du répertoire, ou les masques ACL avant de tenter à nouveau les mises à jour.
FAQ
1) Dois‑je jamais utiliser chmod -R 777 pour corriger les mises à jour WordPress ?
Non. Ça « fonctionne » en autorisant n’importe qui à tout écrire, ce qui est une défaillance de sécurité déguisée en progrès.
Corrigez la propriété et les permissions de groupe à la place, idéalement limitées à wp-content.
2) Dois‑je rendre toute la racine WordPress inscriptible ?
Pas habituellement. Pour les mises à jour UI de plugins/thèmes, wp-content doit être inscriptible.
Les mises à jour du cœur peuvent nécessiter l’écriture dans la racine et les répertoires du cœur ; beaucoup d’équipes évitent cela en faisant les mises à jour via CI/WP‑CLI avec des permissions contrôlées.
3) Pourquoi WordPress demande‑t‑il des identifiants FTP lors d’une mise à jour ?
WordPress fait cela quand il pense qu’il ne peut pas écrire directement sur le système de fichiers. Il essaie des méthodes alternatives.
Sur des serveurs Linux modernes, vous voulez généralement des écritures directes vers wp-content (si vous autorisez les mises à jour UI) et des permissions correctes, pas du FTP.
4) Mon update échoue avec « No such file or directory » lors d’un rename. Pourquoi ?
Souvent c’est une mise à jour partielle : l’extraction a réussi, puis un renommage ou nettoyage a échoué à cause des permissions ou d’un manque d’accès en écriture sur le répertoire parent.
Vérifiez le chemin en échec dans les logs, confirmez la permission d’écriture sur le répertoire où le renommage a lieu, et relancez via WP‑CLI après nettoyage.
5) Quelle est la façon la plus sûre de gérer les mises à jour en multi‑nœuds ?
Ne mettez pas à jour via l’UI admin. Utilisez une pipeline de déploiement pour que chaque nœud reçoive le même artefact versionné.
Gardez le code immuable ; stockez les uploads dans un magasin partagé dédié si nécessaire.
6) Si wp-content est inscriptible, pourquoi les mises à jour échouent encore ?
Parce que le chemin vers lui peut ne pas être traversable (bit x manquant), /tmp peut être plein, vous pouvez manquer d’inodes, des ACL peuvent refuser l’accès,
ou PHP s’exécute sous un utilisateur différent de celui que vous pensez. Utilisez namei -l, df -i et consultez les logs.
7) Est‑il acceptable que www-data possède les répertoires de plugins ?
C’est courant et fonctionnel. C’est aussi un compromis de sécurité : si WordPress ou un plugin est compromis, des répertoires de plugin inscriptibles sont un mécanisme persistant pratique.
Si possible, préférez le modèle groupe partagé ou des mises à jour CI‑only pour la production.
8) Comment corriger « coincé en mode maintenance » en toute sécurité ?
Confirmez qu’aucune mise à jour n’est en cours (vérifiez les processus et les logs récents), puis supprimez le fichier .maintenance à la racine WordPress.
Si la cause sous‑jacente était une permission ou l’espace disque, corrigez‑la d’abord ou cela se reproduira à la prochaine tentative.
9) Pourquoi les mises à jour échouent après une restauration depuis une sauvegarde ?
Les restaurations changent souvent les propriétaires, permissions, ou suppriment les ACL. Soudain PHP ne peut plus écrire dans wp-content.
Après toute restauration, exécutez une validation de permissions/propriété et corrigez‑la selon le modèle choisi.
10) Quelle est la surveillance minimale pour éviter des échecs de mise à jour surprises ?
Alertez sur l’utilisation disque et l’utilisation d’inodes pour le montage contenant WordPress, et séparément pour /tmp si c’est un tmpfs.
Alertez aussi sur les erreurs PHP‑FPM contenant « Permission denied » et « No space left on device » pour repérer des tendances avant la prochaine fenêtre de mise à jour.
Prochaines étapes à réaliser aujourd’hui
Si votre mise à jour WordPress a échoué, ne la traitez pas comme un mystère WordPress. C’est presque toujours l’une des trois choses :
le processus ne peut pas écrire, le disque est plein (ou plus d’inodes), ou les ressources temporaires/unzip sont contraintes.
- Exécutez le diagnostic rapide : disque/inodes,
/tmp, utilisateur PHP, logs. - Corrigez la plus petite surface possible : rendez
wp-contentinscriptible pour l’identité correcte ; évitez de rendre tout l’arbre inscriptible. - Standardisez votre modèle : web‑owned, groupe partagé, ou CI‑only. La propriété mixte est des pannes futures en manteau.
- Opérationnalisez les mises à jour : WP‑CLI dans des fenêtres de maintenance, logs capturés, et un plan de rollback qui n’implique pas la panique.