WordPress ne peut pas télécharger d’images : dépannage rapide pour permissions, limites et bibliothèques

Cet article vous a aidé ?

Vous cliquez sur Ajouter dans la bibliothèque de médias. La roue tourne. Puis WordPress hausse les épaules : « erreur HTTP », « Impossible de créer le répertoire » ou un échec silencieux où rien ne se passe mais votre tension monte.

Ceci est un de ces problèmes qui ressemble à « WordPress fait son WordPress » jusqu’à ce que vous vous rappeliez que c’est en fait une pile : navigateur → CDN/WAF → serveur web → PHP-FPM → système de fichiers → bibliothèques d’images → base de données. L’astuce consiste à trouver la première couche qui vous ment.

Plan de diagnostic rapide (faire dans cet ordre)

1) Confirmer l’erreur exacte à la périphérie et à l’origine

  • Outils de développement du navigateur onglet Réseau : rechercher 413, 403, 500, 502, 504.
  • Journaux d’origine : logs Nginx/Apache + PHP-FPM au même horodatage.
  • Le message de WordPress est souvent vague. Vos logs ne le sont pas.

2) Prouver que le répertoire uploads est inscriptible par l’utilisateur d’exécution

  • Vérifier la propriété et le mode de wp-content/uploads.
  • Essayer de créer un fichier en tant qu’utilisateur du serveur web. Ne devinez pas.

3) Vérifier les limites de taille dans toute la chaîne (la plus petite l’emporte)

  • PHP : upload_max_filesize, post_max_size, memory_limit, max_execution_time.
  • Serveur web : Nginx client_max_body_size ; Apache LimitRequestBody.
  • Proxies/CDN/WAF : 413, limites de corps de requête, règles de sécurité.

4) Vérifier l’état du répertoire temporaire

  • Permissions de /tmp, espace, disponibilité des inodes, options de montage.
  • Le upload_tmp_dir de PHP et les chemins de session.

5) Vérifier les bibliothèques de traitement d’image

  • L’absence d’Imagick/GD peut transformer des uploads valides en « erreur HTTP » lors de la génération des vignettes.
  • Chercher des erreurs fatales dans les logs PHP.

6) Application des règles de sécurité (après les vérifications de base)

  • SELinux/AppArmor peut refuser l’accès alors que les permissions UNIX semblent correctes ; chmod 777 ne résoudra pas une politique.
  • ModSecurity peut bloquer les uploads multipart avec des règles génériques.

Règle générale : si les uploads échouent instantanément, suspectez les limites/WAF. Si ça attend puis échoue, suspectez le traitement PHP, l’espace temporaire, les bibliothèques, les timeouts.

Ce qui se passe réellement lorsque les uploads échouent

Les uploads WordPress ne se résument pas à « copier un fichier sur le disque ». Un parcours typique ressemble à ceci :

  1. Le navigateur envoie un POST multipart vers /wp-admin/async-upload.php (ou via les endpoints REST selon la version/le chemin de l’éditeur).
  2. La périphérie/CDN/WAF inspecte la taille de la requête et le type de contenu, et peut interrompre tôt.
  3. Le serveur web accepte le corps de la requête ; il peut appliquer sa propre limite de taille et des timeouts.
  4. PHP reçoit le fichier dans un emplacement temporaire ($_FILES), puis le déplace vers wp-content/uploads/YYYY/MM.
  5. WordPress crée les métadonnées d’attachement, génère des vignettes et peut exécuter des opérations Imagick/GD.
  6. La base de données reçoit les lignes pour le post pièce-jointe + metadata ; le système de fichiers reçoit les tailles originales et dérivées.

C’est beaucoup de pièces mobiles. Quand une casse, WordPress rapporte souvent ça comme un message de biscuit de la fortune : vague mais techniquement pas faux.

Deux observations pratiques :

  • Si le fichier n’atterrit jamais dans uploads, regardez en amont : limites, permissions, temporaire, WAF.
  • Si l’original atterrit mais que les vignettes ne viennent pas, regardez du côté d’Imagick/GD, des limites de mémoire ou des timeouts.

Une idée paraphrasée (état d’esprit fiabilité) : l’effet « efficacité déraisonnable » d’Eugene Wigner s’applique aussi à l’exploitation — l’instrumentation rend des systèmes désordonnés soudain compréhensibles (idée paraphrasée).

Blague n°1 : Le message d’erreur de la bibliothèque de médias ressemble à une alerte de pager qui dit « quelque chose est arrivé ». Merci, WordPress. Très exploitable.

Faits & contexte intéressants (ce qui explique les bizarreries)

  • Le pipeline d’upload de PHP est ancien : les uploads sont d’abord mis en scène dans un fichier temporaire, puis déplacés. L’espace temporaire est une dépendance réelle, pas un détail d’implémentation.
  • « Erreur HTTP » est devenu un fourre-tout : historiquement, WordPress remontait parfois des échecs génériques d’upload alors que l’exception réelle se situait dans la génération des vignettes, pas au niveau HTTP.
  • Les valeurs par défaut d’Imagick diffèrent selon la distro : les fichiers de politique ImageMagick peuvent restreindre la mémoire, le disque ou certains formats ; la même installation WordPress peut se comporter différemment selon l’hébergeur.
  • GD vs Imagick n’est pas qu’une préférence : GD peut être plus rapide pour de petites opérations, Imagick gère souvent plus de formats et une meilleure qualité mais peut être plus lourd et sensible aux limites de politique.
  • La limite de taille de Nginx est explicite : contrairement à certains systèmes, client_max_body_size de Nginx est un verrou dur qui peut échouer rapidement avec 413.
  • Apache peut cacher la limite à plusieurs endroits : LimitRequestBody peut se trouver dans le virtual host, le contexte directory ou la config globale ; « mais je l’ai changé » n’est pas une preuve.
  • Les systèmes de fichiers cloud et conteneurs changent les modes d’échec : les systèmes de fichiers en overlay et les disques éphémères rendent « les uploads marchent jusqu’au redeploy » un incident étonnamment courant.
  • L’orientation EXIF a tout changé : certains workflows tournent les images lors du traitement ; l’absence de support EXIF peut mener à des incohérences « ça marche sur mon laptop ».

Tâches pratiques : commandes, sorties, décisions

Ce sont des tâches « faites ceci maintenant ». Chacune inclut : une commande, une sortie réaliste, et la décision à prendre. Exécutez-les sur l’hôte WordPress (ou le conteneur) qui gère réellement PHP.

Task 1: Identify the web server and PHP runtime user

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

Ce que ça signifie : Nginx et PHP-FPM tournent, et l’utilisateur worker est www-data.

Décision : tous les tests d’écriture sur le système de fichiers doivent être effectués en tant que www-data. Si c’est un hôte cPanel vous pourriez voir nobody ou des utilisateurs par vhost ; n’assumez pas.

Task 2: Confirm WordPress path and locate uploads directory

cr0x@server:~$ sudo find /var/www -maxdepth 4 -type f -name wp-config.php 2>/dev/null
/var/www/site/wp-config.php

Ce que ça signifie : la racine WordPress est probablement /var/www/site.

Décision : concentrez les vérifications sur /var/www/site/wp-content/uploads sauf si une constante UPLOADS personnalisée est définie.

Task 3: Check ownership and permissions on uploads

cr0x@server:~$ ls -ld /var/www/site/wp-content/uploads
drwxr-xr-x 12 root root 4096 Dec 27 09:40 /var/www/site/wp-content/uploads

Ce que ça signifie : uploads est possédé par root:root. C’est un classique « ça fonctionnait pendant le déploiement en root, puis ça échoue en production ».

Décision : changer la propriété vers l’utilisateur/groupe d’exécution (ou vers un groupe de déploiement qui l’inclut). Évitez 777 comme « correctif ».

Task 4: Prove writability by creating a file as the runtime user

cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/site/wp-content/uploads/.writetest && echo ok'
touch: cannot touch '/var/www/site/wp-content/uploads/.writetest': Permission denied

Ce que ça signifie : PHP ne peut pas écrire non plus.

Décision : corriger la propriété/ACL ; ne touchez pas encore aux limites PHP.

Task 5: Fix ownership safely (one-liner, then re-test)

cr0x@server:~$ sudo chown -R www-data:www-data /var/www/site/wp-content/uploads
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/site/wp-content/uploads/.writetest && echo ok'
ok

Ce que ça signifie : l’exécution peut désormais écrire.

Décision : retenter l’upload dans WordPress. Si ça échoue encore, passez aux limites/logs.

Task 6: Check disk space (yes, really)

cr0x@server:~$ df -h /var/www/site/wp-content/uploads /tmp
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2   40G   39G  220M  99% /
tmpfs           2.0G  1.9G  120M  95% /tmp

Ce que ça signifie : le système de fichiers racine et /tmp sont presque pleins. Les uploads peuvent échouer à la mise en scène ou à la finalisation.

Décision : libérer de l’espace immédiatement ou étendre les volumes ; nettoyer aussi les accumulations temporaires. Si vous ne corrigez que les perms du répertoire uploads, ça échouera toujours de manière intermittente.

Task 7: Check inode exhaustion (the “disk is full but df lies” cousin)

cr0x@server:~$ df -i /var/www/site/wp-content/uploads /tmp
Filesystem       Inodes  IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2  2621440 2621201     239  100% /
tmpfs            524288  523901     387  100% /tmp

Ce que ça signifie : vous êtes à court d’inodes. Les rafales de petits fichiers (cache, sessions, fichiers temporaires) causent cela.

Décision : nettoyer les répertoires à fort churn (cache/session/tmp). À long terme : déplacer les caches hors du FS racine, ajuster la rétention, ou redimensionner le système de fichiers avec plus d’inodes.

Task 8: Inspect PHP limits actually in effect

cr0x@server:~$ php -i | egrep 'upload_max_filesize|post_max_size|memory_limit|max_execution_time|upload_tmp_dir'
upload_max_filesize => 2M => 2M
post_max_size => 8M => 8M
memory_limit => 128M => 128M
max_execution_time => 30 => 30
upload_tmp_dir => no value => no value

Ce que ça signifie : les uploads supérieurs à 2 MB échoueront ; WordPress a aussi souvent besoin de plus de mémoire pour traiter de grands JPEG, et le répertoire temporaire par défaut est utilisé.

Décision : définir upload_max_filesize et post_max_size selon vos besoins, et envisager d’augmenter memory_limit si le traitement d’images échoue.

Task 9: Verify Nginx request size limit

cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'client_max_body_size'
45:    client_max_body_size 2m;

Ce que ça signifie : Nginx bloque les corps supérieurs à 2 MB avant que PHP ne les voie.

Décision : augmenter client_max_body_size dans le bon contexte (http/server/location) et recharger Nginx.

Task 10: Reload Nginx and confirm configuration is valid

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 : les changements sont actifs sans couper les connexions.

Décision : retenter l’upload avec un fichier légèrement au-dessus de la limite précédente pour confirmer que le seuil a bougé.

Task 11: Check PHP-FPM pool config for per-pool overrides

cr0x@server:~$ sudo egrep -R 'php_admin_value\[upload_max_filesize\]|php_admin_value\[post_max_size\]|php_admin_value\[memory_limit\]' /etc/php/8.2/fpm/pool.d
/etc/php/8.2/fpm/pool.d/www.conf:php_admin_value[upload_max_filesize] = 2M
/etc/php/8.2/fpm/pool.d/www.conf:php_admin_value[post_max_size] = 8M

Ce que ça signifie : vous pouvez éditer php.ini pour toujours sans que rien ne change parce que le pool l’outrepasse.

Décision : mettre à jour les paramètres du pool et redémarrer PHP-FPM.

Task 12: Restart PHP-FPM and watch for errors

cr0x@server:~$ sudo systemctl restart php8.2-fpm
cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager -l | sed -n '1,12p'
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled)
     Active: active (running) since Fri 2025-12-27 09:52:21 UTC; 2s ago
       Docs: man:php-fpm8.2(8)

Ce que ça signifie : le service est sain après le changement.

Décision : si le service ne démarre pas, votre changement de config est erroné ; revenir en arrière et réappliquer prudemment.

Task 13: Tail web server error logs while reproducing the issue

cr0x@server:~$ sudo tail -f /var/log/nginx/error.log
2025/12/27 09:54:10 [error] 1241#1241: *392 client intended to send too large body: 7340032 bytes, client: 203.0.113.10, server: site.example, request: "POST /wp-admin/async-upload.php HTTP/1.1", host: "site.example"

Ce que ça signifie : c’est définitivement la taille de requête de Nginx, pas WordPress.

Décision : augmenter client_max_body_size et confirmer que vous avez édité le bon bloc serveur (pas un défaut que vous n’utilisez pas).

Task 14: Tail PHP-FPM logs for fatal processing failures

cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[27-Dec-2025 09:55:37] WARNING: [pool www] child 2219 said into stderr: "PHP Fatal error:  Uncaught Error: Call to undefined function imagecreatefromjpeg() in /var/www/site/wp-includes/class-wp-image-editor-gd.php:92"

Ce que ça signifie : les fonctions GD sont manquantes ; la génération de vignettes échoue après l’upload, souvent affiché comme « erreur HTTP ».

Décision : installer/activer l’extension PHP pertinente (comme php-gd) et redémarrer PHP-FPM.

Task 15: Verify installed PHP extensions (GD, Imagick)

cr0x@server:~$ php -m | egrep -i 'gd|imagick|exif'
exif

Ce que ça signifie : EXIF est présent ; GD et Imagick ne le sont pas.

Décision : installer au moins une bibliothèque d’images (GD est la plus simple ; Imagick est puissante mais nécessite ImageMagick aussi). Ne pas courir sans l’une d’elles.

Task 16: Check SELinux enforcement and recent denies

cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ sudo ausearch -m avc -ts recent | tail -n 3
type=AVC msg=audit(1735293382.612:1187): avc:  denied  { write } for  pid=2311 comm="php-fpm" name="uploads" dev="dm-0" ino=420112 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=dir permissive=0

Ce que ça signifie : SELinux a bloqué l’écriture ; les permissions peuvent être correctes, mais les labels ne le sont pas.

Décision : corriger les contextes (par ex. définir le bon httpd_sys_rw_content_t sur uploads) au lieu d’affaiblir SELinux.

Permissions et propriété : la cause n°1

Si vous ne faites qu’une vérification, faites celle-ci : l’utilisateur d’exécution PHP peut-il créer des fichiers dans wp-content/uploads ?

À quoi ressemble le « correct »

  • Le répertoire uploads appartient à l’utilisateur/groupe d’exécution web (www-data sur Debian/Ubuntu ; apache sur de nombreux systèmes RHEL), ou est inscriptible via groupe/ACL.
  • Les permissions de répertoire typiques sont 0755 ou 0775. Les fichiers typiques 0644.
  • Si vous utilisez un utilisateur de déploiement et gardez l’exécution en lecture seule pour le code, alors uploads devrait être un chemin persistant séparé et inscriptible avec des permissions explicites.

Ce qu’il ne faut pas faire

Ne « corrigez » pas avec chmod -R 777 wp-content/uploads. Vous échangez une panne contre un incident de sécurité. En hébergement multi-tenant, c’est un beau cadeau pour un mouvement latéral.

Quand la propriété repasse à root

Cela arrive quand :

  • Les scripts de déploiement s’exécutent en root et copient un arbre uploads avec propriété root.
  • Les restaurations de sauvegarde rétablissent avec un mauvais mapping uid/gid (surtout entre hôtes).
  • Les conteneurs reconstruisent des volumes et changent les permissions sur le mount.

Recommandation de politique : traitez uploads comme des données persistantes, pas comme du code. Gérez-les comme des données. Sauvegardez-les, montez-les, étiquetez-les, surveillez-les.

Limites de taille : PHP, serveur web, proxies et CDNs

Les uploads échouent à la plus petite limite de la chaîne. Il suffit d’un composant mesquin pour gâcher la journée de tout le monde.

Limites PHP qui comptent

  • upload_max_filesize : taille maximale d’un fichier uploadé.
  • post_max_size : taille maximale du corps POST complet (doit être >= upload_max_filesize, et en pratique plus grand pour l’overhead multipart).
  • memory_limit : le traitement d’images peut nécessiter plusieurs fois la taille compressée du fichier. Un JPEG de 12 MB peut s’étendre massivement en mémoire.
  • max_execution_time : un stockage lent ou des transformations CPU-bound peuvent atteindre des timeouts.
  • max_input_time : peut compter pour des uploads lents.

Portes Nginx et Apache

  • Nginx client_max_body_size est la cause la plus courante de 413.
  • Apache LimitRequestBody et les réglages de proxy peuvent imposer des limites ; vérifiez aussi les timeouts quand PHP est derrière un module proxy.

Proxies et CDNs

Même si votre origine est parfaite, un CDN/WAF peut rejeter les POST volumineux ou les corps multipart, ou appliquer des limites liées au plan. Si les uploads échouent uniquement via le nom de domaine public mais réussissent en touchant l’origine directement (test depuis un réseau privé), votre périphérie est coupable.

Blague n°2 : Quelque part dans la pile, il y a toujours une limite de 2 MB datant de 2009, vivant discrètement sa meilleure vie.

Répertoires temporaires et épuisement inode/disque

Les uploads vont d’abord dans un répertoire temporaire. Si le tmp est plein, mal permissé, monté noexec de façon étrange, ou est un tmpfs sous-dimensionné, vous verrez des échecs qui ressemblent à des erreurs de permissions, à des erreurs HTTP ou à des timeouts.

Signatures d’échec typiques

  • Les petits fichiers réussissent, les gros échouent : l’espace temporaire (ou la limite) est serré.
  • Échecs intermittents en période de charge : épuisement d’inodes temporaires, chemin de session ou pics de concurrence.
  • Les uploads réussissent mais le traitement échoue : le tmp est OK ; ce sont les bibliothèques d’images ou la mémoire qui posent problème.

Conseils pratiques

  • Ne montez pas /tmp en petit tmpfs à moins de l’avoir dimensionné pour les uploads et les rafales.
  • Si vous êtes en conteneurs, soyez explicite : montez un volume persistant pour uploads et un espace scratch suffisamment dimensionné pour le temporaire.
  • Surveillez l’utilisation des inodes. C’est le tueur silencieux sur les sites à cache intensif.

Bibliothèques d’images : Imagick vs GD, et pourquoi « erreur HTTP » est parfois un mensonge

WordPress tente généralement de créer plusieurs tailles dérivées à l’upload. Cela signifie qu’il a besoin d’un backend d’édition d’images fonctionnel. Si ni Imagick ni GD ne sont utilisables, les uploads peuvent « échouer » après le transfert, durant la génération des métadonnées.

Comment choisir en production

  • GD : chaîne de dépendances la plus simple, bon baseline. Installer l’extension PHP suffit dans la plupart des cas.
  • Imagick : plus capable, gère souvent mieux certains formats et opérations, mais nécessite ImageMagick plus des politiques et limites de ressources qui peuvent surprendre.

Modes d’échec courants des bibliothèques

  • Extension manquante : GD/Imagick non installé pour la version PHP qui sert réellement les requêtes.
  • Mauvais SAPI PHP : vous avez installé php-gd pour le CLI mais FPM utilise une version différente ; les tests CLI passent, le web échoue.
  • Restrictions de politique ImageMagick : opérations refusées, mémoire/disque restreints, format bloqué.
  • Épuisement mémoire : erreurs fatales lors de la création de grandes vignettes.

En cas de doute, taillez les logs PHP-FPM pendant une tentative d’upload reproductible. Si vous voyez des erreurs fatales dans les classes d’édition d’images, cessez de blâmer WordPress et corrigez l’environnement d’exécution.

Couches de sécurité : SELinux/AppArmor, règles WAF, ModSecurity

Les systèmes de sécurité échouent « correctement », ce qui signifie qu’ils échouent de façons qui donnent l’impression que votre application est cassée. C’est leur boulot. Le vôtre est de le confirmer rapidement.

SELinux : des permissions qui ne sont pas des permissions

Sur les systèmes SELinux, les contextes de fichier comptent autant que les modes UNIX. Vous pouvez mettre 0777 et être quand même refusé. Si vous voyez des AVC denies concernant uploads, corrigez les labels sur le répertoire contenu et gardez SELinux en mode enforcing.

AppArmor

AppArmor peut restreindre PHP-FPM ou le serveur web d’écrire vers des chemins spécifiques. Le symptôme peut être identique à une mauvaise propriété. Vérifiez les profils et les logs si vous êtes sur Ubuntu avec AppArmor activé.

ModSecurity et règles WAF

Les requêtes multipart sont une cible favorite des règles génériques. Les faux positifs arrivent. Si vous obtenez un 403 sur les uploads alors que les pages admin normales fonctionnent, vérifiez les logs WAF pour des règles bloquées sur async-upload.php ou les endpoints REST.

Erreurs courantes : symptôme → cause racine → correction

« Impossible de créer le répertoire wp-content/uploads/2025/12. Est-ce que son parent est inscriptible par le serveur ? »

  • Cause racine : mauvaise propriété sur wp-content/uploads, bit d’exécution manquant sur un répertoire parent, ou refus SELinux.
  • Correction : définir la bonne propriété/mode ; vérifier en écrivant en tant qu’utilisateur d’exécution ; sous SELinux, définir le contexte correct sur uploads.

« Erreur HTTP » immédiatement à l’upload

  • Cause racine : 413 requête trop grande depuis Nginx/Apache/CDN, ou rejet WAF.
  • Correction : augmenter les limites de corps de requête bout en bout ; confirmer avec les logs ; contourner temporairement la périphérie pour isoler.

L’upload se termine mais l’image n’apparaît jamais dans la bibliothèque

  • Cause racine : fatal PHP durant la génération des métadonnées ; échec d’écriture en base de données ; permissions empêchant le mouvement final depuis le temporaire.
  • Correction : taillez les logs PHP ; vérifiez la connectivité base de données ; vérifiez le tmp et uploads ; vérifiez les extensions PHP.

Seules les grandes images échouent, les petites passent

  • Cause racine : upload_max_filesize, post_max_size, client_max_body_size, ou espace tmp/mémoire insuffisants.
  • Correction : augmenter les limites ; vérifier le disque temporaire ; augmenter la mémoire pour le traitement.

Les uploads fonctionnent sur un serveur mais pas sur un autre (même code)

  • Cause racine : GD/Imagick manquant, politique ImageMagick différente, posture SELinux/AppArmor différente, ou config proxy différente.
  • Correction : comparer php -m, valeurs php.ini, dumps de config Nginx/Apache, et paramètres d’application de sécurité.

Les uploads échouent parfois après un déploiement

  • Cause racine : l’étape de déploiement réinitialise les permissions des uploads ou remplace les mounts de volume ; les conteneurs perdent les uploads éphémères.
  • Correction : traiter uploads comme un volume persistant ; appliquer la propriété dans le déploiement ; ajouter un check d’intégrité qui écrit dans uploads.

Les uploads échouent avec 500 ou 502 après une longue attente

  • Cause racine : timeout PHP-FPM, timeout upstream, épuisement mémoire, stockage lent, ou opérations Imagick lourdes.
  • Correction : vérifier les timeouts upstream ; augmenter le temps d’exécution PHP ; profiler l’IO ; ajuster les tailles d’images ; envisager d’externaliser le traitement d’images.

Listes de vérification / plan pas à pas

Checklist A : « Il me faut ça réglé en 10 minutes »

  1. Reproduire une fois avec les devtools ouverts. Noter le code d’état et la réponse.
  2. Taillez les logs d’erreur Nginx/Apache ; taillez les logs PHP-FPM en même temps.
  3. Vérifier la propriété de wp-content/uploads et faire un test d’écriture en tant qu’utilisateur d’exécution.
  4. Vérifier l’usage disque et inode pour uploads et temporaire.
  5. Vérifier les limites actives PHP + serveur web sur le corps des requêtes.
  6. Confirmer que GD ou Imagick est installé pour la version PHP qui sert les requêtes.
  7. Si SELinux est en enforcing, vérifier les AVC denies avant de faire quoi que ce soit d’imprudent.

Checklist B : « Empêcher que ça ne se reproduise »

  1. Surveillance : espace disque, usage inode et utilisation de /tmp avec alertes.
  2. Hygiène de déploiement : s’assurer que votre pipeline ne change jamais la propriété des uploads persistants de façon inattendue.
  3. Configuration as code : garder les overrides de PHP-FPM et les limites Nginx en contrôle de version.
  4. Parité des bibliothèques : standardiser les ensembles d’extensions entre environnements ; les inclure dans les images.
  5. Politique edge : documenter et tester les limites de taille WAF/CDN ; inclure les endpoints d’upload dans des listes blanches si pertinent.
  6. Test opérationnel : ajouter un test synthétique qui téléverse une petite image et vérifie que les dérivés sont créés (dans un chemin de test sûr).

Checklist C : contrôles sanity ingénieur stockage

  1. Confirmer que les uploads résident sur un stockage durable, pas sur le root éphémère des conteneurs.
  2. Confirmer que le système de fichiers supporte votre charge : beaucoup de petits fichiers, créations fréquentes de répertoires, écritures occasionnelles volumineuses.
  3. Confirmer que la sauvegarde/restauration conserve le mapping uid/gid (ou que vous avez un fixup post-restore).
  4. Confirmer la latence IO sous charge ; les disques lents causent des timeouts qui ressemblent à des « erreurs WordPress ».

Trois mini-histoires d’entreprise issues de la production

1) L’incident causé par une mauvaise hypothèse

La migration semblait simple : déplacer un site WordPress d’une VM vieillissante vers un hôte plus récent, garder la même arborescence, restaurer une sauvegarde, basculer le DNS. L’équipe a supposé que si les pages s’affichaient, la partie difficile était terminée.

En quelques minutes, l’équipe marketing a tenté de téléverser des images produits pour un lancement. Les uploads ont échoué avec « Impossible de créer le répertoire ». Quelqu’un a fait le rituel habituel : redémarrer PHP-FPM, vider les caches, resauvegarder les permaliens. Rien. Le site « fonctionnait », donc tout le monde a supposé que le problème venait de WordPress.

Le vrai problème était ennuyeux : la restauration de la sauvegarde avait été effectuée en root, créant wp-content/uploads en root:root. Sur l’ancien hôte, une configuration différente masquait cela avec des ACL permissives. Sur le nouvel hôte, l’exécution était www-data et elle pouvait lire mais pas écrire.

Ça a pris plus de temps que nécessaire parce que la première réponse a été de tuner les limites PHP. C’est l’hypothèse erronée en action : « uploads échouent signifie taille ». La correction a été un chown en une ligne, suivi d’une étape post-restore qui définit explicitement la propriété sur les répertoires inscriptibles et vérifie en écrivant en tant qu’utilisateur d’exécution.

La leçon retenue : ne pas considérer que « le site s’affiche » est un critère de réussite. Pour WordPress, « pouvoir téléverser des médias » est une capacité de production essentielle, pas un bonus.

2) L’optimisation qui s’est retournée contre eux

Une équipe orientée performance voulait tout accélérer. Ils ont déplacé /tmp vers tmpfs pour réduire l’IO disque et accélérer les sessions PHP et les uploads. Sur le papier c’était élégant : la mémoire est plus rapide que le disque, et ce sont des fichiers temporaires de toute façon.

Pendant un temps, ça a bien paru. Les temps de chargement se sont stabilisés et les graphiques de latence disque se sont améliorés. Puis une équipe contenu a téléversé un lot d’images haute résolution — exactement le genre de scénario qui arrive quand quelqu’un prépare une campagne et n’a pas de patience.

Les uploads ont commencé à échouer aléatoirement. Pas tous. Juste assez pour être exaspérant. Les logs PHP affichaient sporadiquement « failed to write file to disk », alors que l’OS semblait « OK » au premier coup d’œil parce que le disque global avait de l’espace.

Le retour de bâton était simple : le tmpfs avait été dimensionné prudemment, et les uploads concurrents plus la génération de vignettes ont créé des rafales d’utilisation temporaire. Le tmpfs s’est rempli, les inodes sont partis, et la mise en scène des uploads par PHP a échoué. La correction n’a pas été d’abandonner tmpfs ; c’était de le dimensionner avec de la marge, de le surveiller, et de déplacer les workflows volumineux vers un volume scratch dédié quand nécessaire.

Les optimisations qui éliminent des goulots sont excellentes. Celles qui créent des goulots cachés expliquent ensuite pourquoi « plus rapide » a rendu le site inutilisable.

3) La pratique ennuyeuse mais correcte qui a sauvé la journée

Une autre organisation faisait tourner WordPress à grande échelle avec des mises à jour fréquentes de contenu. Ce n’étaient pas des gens excitants. C’étaient les gens qui étiquettent les câbles et apprécient les fenêtres de maintenance. Magnifique.

Ils avaient un « health check uploads » standard dans leur pipeline de déploiement. Il faisait deux choses : vérifier que l’utilisateur d’exécution pouvait écrire dans le chemin uploads, et exécuter un petit test d’upload via PHP qui confirmait que la génération des métadonnées se faisait sans erreurs fatales. Ça prenait quelques secondes.

Un jour, un patch OS a introduit un changement subtil : PHP-FPM a redémarré et chargé un ordre de répertoire ini différent, supprimant effectivement l’extension GD dans cet environnement. Le site servait encore les pages. Les logins admin fonctionnaient. Mais les uploads d’images auraient échoué quand l’équipe contenu serait arrivée.

Le pipeline l’a détecté immédiatement et a bloqué le déploiement. Les ingénieurs ont installé l’extension manquante pour la bonne version PHP, redémarré le service et relancé le health check. Pas de panne. Pas de panique. Pas de « chmod 777 » d’urgence.

C’est la magie de la correction ennuyeuse : elle n’empêche pas toutes les pannes, mais elle évite les humiliactions.

FAQ

1) Pourquoi WordPress dit « erreur HTTP » quand les permissions de fichier sont incorrectes ?

Parce que le endpoint d’upload peut échouer à différentes étapes et WordPress sometimes condense tout en un message générique. Consultez les logs du serveur web et de PHP pour la vraie cause.

2) J’ai augmenté upload_max_filesize mais les uploads échouent toujours. Qu’ai-je manqué ?

Généralement l’un des éléments suivants : post_max_size est plus petit, client_max_body_size de Nginx est plus petit, un override dans le pool PHP-FPM impose des valeurs plus petites, ou le CDN/WAF a une limite inférieure.

3) Ai-je besoin de GD et Imagick tous les deux ?

Non. Vous avez besoin d’au moins un backend d’édition d’images fonctionnel. GD est plus simple à maintenir de façon cohérente. Imagick convient si vous gérez la politique ImageMagick et les limites de ressources.

4) L’upload fonctionne mais les vignettes ne se génèrent pas. Que vérifier ?

Les bibliothèques d’images et les limites de mémoire/temps. Taillez les logs PHP-FPM pendant un upload. L’absence de gd/imagick ou l’épuisement mémoire est courant.

5) Un problème de « disque plein » peut-il se présenter comme une erreur de permissions ?

Oui. PHP peut échouer à écrire des fichiers temporaires et WordPress peut mal le rapporter. Vérifiez df -h et df -i pour les chemins uploads et tmp.

6) Quel est le modèle de permissions le plus sûr pour uploads ?

Gardez le code en lecture seule pour l’exécution si possible, et rendez wp-content/uploads inscriptible par l’utilisateur/groupe d’exécution (souvent via la propriété de groupe et 0775, ou des ACLs). Évitez l’écriture globale.

7) Pourquoi ça échoue seulement via le domaine public, alors que ça marche en test local ?

Votre couche edge (CDN/WAF/load balancer) impose des limites ou bloque les requêtes multipart. Comparez les codes d’état et les logs. Testez en contournant l’edge pour isoler.

8) Comment savoir si SELinux est en cause ?

Si SELinux est en enforcing et que vous voyez des AVC denies impliquant php-fpm ou le serveur web écrivant dans uploads, c’est SELinux. Corrigez les contextes ; ne « résolvez » pas en désactivant SELinux.

9) Pourquoi les uploads dans WordPress disparaissent après un redeploy en conteneur ?

Parce que les uploads sont écrits dans le système de fichiers du conteneur, qui est éphémère. Montez un volume persistant pour wp-content/uploads et traitez-le comme des données.

Conclusion : étapes suivantes pour éviter la récurrence

Si votre WordPress ne peut pas télécharger d’images, la voie la plus rapide est le scepticisme discipliné. Ignorez le message de l’UI. Faites confiance aux logs. Prouvez la capacité d’écriture. Ensuite chassez les limites. Puis chassez les bibliothèques. Ce n’est qu’après cela que vous blâmerez la politique de sécurité.

Faites ceci ensuite

  1. Ajouter un health check uploads (test d’écriture + vérification de génération de dérivés) aux déploiements.
  2. Standardiser la configuration runtime (limites PHP, limites Nginx/Apache, overrides PHP-FPM) et la garder en contrôle de version.
  3. Surveiller le stockage sérieusement : espace disque, usage inode et utilisation de /tmp avec alertes.
  4. Choisir une stratégie de bibliothèque (GD en baseline ou Imagick avec politique explicite) et la rendre cohérente entre environnements.
  5. Documenter les limites edge pour que « ça marche en staging » n’étonne plus quand le CDN intervient.

Les uploads sont une chaîne critique en production. Traitez-les comme telle, et la Bibliothèque de Médias cessera de manipuler votre rotation d’astreinte.

← Précédent
10 mythes sur les GPU qui refusent de disparaître
Suivant →
CSS Grid vs Flexbox : règles de décision et recettes de mise en page robustes en production

Laisser un commentaire