WordPress : Nettoyage de la bibliothèque médias sans casser les URL

Cet article vous a aidé ?

Rien ne fait paniquer une équipe marketing comme une page d’accueil pleine d’images cassées. Rien ne fait paniquer un SRE comme un « petit nettoyage » en production qui se transforme silencieusement en générateur distribué de 404.

Vous pouvez tout à fait réduire une bibliothèque médias WordPress gonflée, diminuer les factures de stockage et accélérer les sauvegardes — sans casser les URL. Mais il faut l’aborder comme un changement en production : mesurer d’abord, supprimer en dernier, et garder une trappe de secours.

Ce que vous nettoyez réellement (et pourquoi les URL cassent)

« Nettoyage des médias » sonne comme la suppression d’un tas de JPEG. Dans WordPress, c’est plus compliqué :

  • Objets système de fichiers : généralement sous wp-content/uploads/YYYY/MM/, plus les tailles générées (vignettes) et parfois des variantes « -scaled ».
  • Lignes en base de données : les attachments sont des posts dans wp_posts avec post_type='attachment'.
  • Métadonnées : les métadonnées d’attachement dans wp_postmeta (notamment _wp_attached_file et _wp_attachment_metadata) indiquent à WordPress quels fichiers existent et quelles tailles ont été générées.
  • Références : les articles/pages stockent des références d’image dans le HTML, le JSON des blocs, les shortcodes, les options du thème, les widgets, et parfois dans des tables de plugins.
  • CDN/couches d’externalisation : vos fichiers « réels » peuvent être sur S3/Cloud Storage, avec des copies locales optionnelles.

Les URL cassent pour trois raisons principales :

  1. Vous avez supprimé un fichier toujours référencé (dans du contenu, un constructeur de pages, une option de thème, une carte sociale ou une table de plugin).
  2. Vous avez modifié le mappage d’URL (changement du site URL, modification des paramètres d’un plugin d’offload, bucket/chemin différent, ou réécriture du chemin des uploads).
  3. Vous avez changé le nom de fichier (déduplication/renommage/optimisation qui a « aidé » en ré-encodant, ré-enregistrant ou déplaçant les images).

Le nettoyage des médias qui ne casse pas les URL n’est pas d’abord un problème de suppression. C’est un problème d’intégrité des références.

Vérité opérationnelle : WordPress ne maintient pas d’index complet « cette image est utilisée par ces posts ». Vous construisez cette carte vous-même, ou vous acceptez le risque. En production, acceptez le moins possible.

Blague n°1 : Supprimer des médias sur un site WordPress en production sans plan, c’est comme élaguer un bonsaï à la tronçonneuse — techniquement un outil, émotionnellement une erreur.

Faits intéressants & contexte historique (court, utile)

  1. Les attachments sont des posts. WordPress stocke les médias comme des lignes dans wp_posts depuis les premières versions, c’est pourquoi vous pouvez « éditer » une image comme un post.
  2. Les vignettes ont changé la donne. Les tailles intermédiaires automatiques existaient tôt, mais le comportement d’images responsive moderne (comme srcset) a rendu « un upload → plusieurs fichiers » la norme.
  3. Le suffixe « -scaled » est un artefact relativement moderne. WordPress a commencé à générer des images « scaled » pour les très gros uploads afin d’éviter des originaux gigantesques qui cassent les mises en page et consomment beaucoup de mémoire.
  4. La gestion EXIF a été une source constante d’écueils. Les métadonnées d’orientation peuvent rendre une photo « tournée de travers » selon la manière dont elle est traitée et régénérée.
  5. Le chemin uploads est configurable. WordPress peut stocker des fichiers en dehors du répertoire uploads par défaut, mais beaucoup de plugins supposent quand même le chemin par défaut.
  6. Les CDN ont rendu la permanence des URL importante. Dès qu’un email de campagne part, ces URL médias deviennent des points d’API publics quasi-immuables.
  7. Les constructeurs de pages n’enregistrent pas toujours du HTML simple. Les page builders sérialisent souvent le contenu en blobs JSON-ish ; un simple grep rate des usages.
  8. Certains plugins d’offload traitent le bucket comme source de vérité. Le nettoyage local peut être sûr — ou catastrophique — selon que l’offload fait une copie ou un déplacement.
  9. WP-CLI est devenu la supervision adulte. Opérationnellement, WP-CLI fait la différence entre une maintenance reproductible et un « j’ai cliqué et j’ai espéré ».

Principes non négociables pour un nettoyage sûr des médias

1) Les URL sont des contrats

Le marketing voit les URL d’images comme des « assets ». L’ingénierie doit les voir comme des interfaces publiques. Une fois qu’une URL est publiée — pages web, emails, PDFs, aperçus sociaux — la changer est une modification cassante.

2) Faites un plan de suppression qui part du principe que vous avez tort

Vous manquerez une référence au premier passage. Le plan doit inclure :

  • Une étape réversible (quarantaine/mouvement au lieu de suppression).
  • Une fenêtre de surveillance (surveiller les 404, les fetchs d’origine et les taux d’erreur).
  • Un rollback (remettre les fichiers, restaurer des lignes DB, revenir sur des règles de redirection).

3) Séparez « orphelin dans la BD » de « inutilisé dans le contenu »

Un attachment peut être absent de l’UI de la Bibliothèque Médias (ou ne pas sembler référencé) et être pourtant utilisé :

  • Dans les options du thème (customizer, logo de l’en-tête, image Open Graph).
  • Dans les widgets/menus.
  • Dans des images de fond CSS.
  • Dans les tables de plugins de builders.

4) Ne confondez pas « octets dupliqués » et « safe à dédupliquer »

Deux fichiers peuvent être identiques mais avoir des URL différentes toutes deux utilisées. Dédupliquer sans redirections, c’est une panne lente et programmée.

5) Votre stratégie de sauvegarde fait partie de la stratégie de nettoyage

Si vos sauvegardes sont lentes, coûteuses ou peu fiables, vous serez tenté de « simplement supprimer ». Corrigez le pipeline de sauvegarde et vous prendrez de meilleures décisions. Aussi : testez les restaurations. Une sauvegarde que vous n’avez pas restaurée est juste une dépense qui rassure à tort.

6) Confirmez ce qui sert réellement les médias

Est-ce le disque local ? Un NFS/EFS monté ? Un store d’objets via un plugin ? Un CDN qui pull depuis l’origine ? Si vous ne connaissez pas le chemin de service, vous ne pouvez pas prédire l’étendue des dégâts.

Une citation à garder sur un post-it :

« L’espoir n’est pas une stratégie. » — General Gordon R. Sullivan

Mode d’emploi pour un diagnostic rapide

Quand quelqu’un dit « la Bibliothèque Médias est énorme et le site est lent », ne sortez pas immédiatement des scripts de suppression. D’abord, identifiez le type de douleur : pression de stockage, problèmes de sauvegarde, lenteur de l’admin, lenteur frontale, ou churn CDN/origine.

Première étape : confirmez que le symptôme est réel et actuel

  • Stockage : utilisation du système de fichiers et épuisement des inodes.
  • Sauvegardes : durée, comportement incrémental, nombre d’objets.
  • Frontend : 404 sur les uploads, pics de bande passante à l’origine, taux de cache manqué.
  • Admin : grille médias lente ou recherche pénible due à la base ou à des métadonnées énormes.

Deuxième étape : trouvez le domaine du goulot

  • Limité par le disque : I/O lent, trop de petits fichiers, système de fichiers réseau lent.
  • Limité par la BD : requêtes lentes sur wp_posts/wp_postmeta, pas d’index pour vos requêtes, autoload bloat qui interfère avec l’admin.
  • Limité par le réseau : cache CDN manqué, origine inaccessible, mauvais en-têtes de cache, mauvaise configuration d’un plugin d’offload.

Troisième étape : choisissez le levier le moins risqué

  • Si le problème est la durée des sauvegardes, implémentez des sauvegardes incrémentales ou excluez les caches avant de supprimer des médias.
  • Si le problème est la bande passante frontale, corrigez le cache et le dimensionnement des images avant le « nettoyage ».
  • Si le problème est la pression de stockage, mettez d’abord en quarantaine les médias anciens ; ne supprimez pas à l’aveugle.

Tâches pratiques (commandes + sortie + décisions)

Ce sont des tâches de qualité production : chacune inclut une commande, une sortie d’exemple, ce que la sortie signifie, et la décision à prendre. Exécutez-les d’abord sur un clone de staging. Puis en production avec une fenêtre de changement et un plan de rollback.

Task 1: Check disk usage and inode pressure (the “are we actually full?” test)

cr0x@server:~$ df -h /var/www/html/wp-content/uploads
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  200G  176G   24G  89% /

Sens : 89% utilisé est inconfortable mais pas une urgence. Si c’est 95%+, vous êtes en territoire « les incidents arrivent ici ».

Décision : Si >90%, priorisez la quarantaine sûre + plan d’extension ; évitez les scripts longue durée qui génèrent des fichiers temporaires.

cr0x@server:~$ df -i /var/www/html/wp-content/uploads
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/nvme0n1p2 13107200 8123456 4983744   62% /

Sens : L’usage des inodes est correct. Si l’usage des inodes atteint ~90% avec beaucoup de Go libres, vous souffrez de « trop de fichiers », pas de « trop de données ».

Décision : Si lié aux inodes, concentrez-vous sur la prolifération des vignettes et les répertoires cache, pas seulement sur les gros originaux.

Task 2: Measure uploads footprint by year/month (find the “why is 2019 enormous?” clue)

cr0x@server:~$ du -h --max-depth=2 /var/www/html/wp-content/uploads | sort -h | tail -n 10
4.2G    /var/www/html/wp-content/uploads/2022
6.8G    /var/www/html/wp-content/uploads/2023
7.1G    /var/www/html/wp-content/uploads/2021
9.6G    /var/www/html/wp-content/uploads/2020
31G     /var/www/html/wp-content/uploads/2019
58G     /var/www/html/wp-content/uploads

Sens : Une année domine. Cela correspond souvent à une campagne, une migration ou un plugin qui a généré des tonnes de tailles.

Décision : Ciblez l’analyse sur l’année outlier d’abord. C’est là que vous aurez le meilleur retour sur investissement en toute sécurité.

Task 3: Find the biggest files (storage wins without touching URLs)

cr0x@server:~$ find /var/www/html/wp-content/uploads -type f -printf '%s %p\n' | sort -nr | head -n 5
52428800 /var/www/html/wp-content/uploads/2019/07/booth-video-poster.png
41943040 /var/www/html/wp-content/uploads/2019/08/trade-show-wall.jpg
39845888 /var/www/html/wp-content/uploads/2020/01/hero-background.tif
36700160 /var/www/html/wp-content/uploads/2021/11/webinar-slide-01.png
33554432 /var/www/html/wp-content/uploads/2019/09/product-shot-raw.jpg

Sens : Vous avez probablement des formats « hostiles au web » (TIFF, JPG presque raw, PNG énormes) qui n’auraient jamais dû être uploadés.

Décision : Privilégiez le remplacement + redirections (conserver l’URL originale vivante) ou laissez les originaux et corrigez le rendu des tailles. Ne supprimez pas en masse.

Task 4: Confirm WordPress thinks uploads live where you think they do

cr0x@server:~$ wp option get upload_path

Sens : Une sortie vide signifie généralement « chemin uploads par défaut ». Si ça affiche un chemin personnalisé, vos scripts de nettoyage doivent le suivre.

Décision : Si un chemin personnalisé existe, auditez les plugins et les règles Nginx/Apache qui supposent le chemin par défaut.

cr0x@server:~$ wp option get upload_url_path

Sens : Une sortie vide signifie que WordPress construit les URL à partir du site URL + chemin uploads.

Décision : Si ceci est défini (ou un plugin d’offload le remplace), le mappage d’URL peut différer de la disposition du système de fichiers.

Task 5: Inventory attachment counts and file types (scope the work)

cr0x@server:~$ wp db query "SELECT post_mime_type, COUNT(*) AS c FROM wp_posts WHERE post_type='attachment' GROUP BY post_mime_type ORDER BY c DESC LIMIT 10;"
+----------------+--------+
| post_mime_type | c      |
+----------------+--------+
| image/jpeg     | 48210  |
| image/png      | 12340  |
| image/webp     | 3210   |
| application/pdf| 2890   |
| image/gif      | 740    |
+----------------+--------+

Sens : Vous avez une grande bibliothèque d’images et un nombre non négligeable de PDFs. Les PDFs sont souvent intégrés dans des téléchargements et emails ; considérez-les comme permanents.

Décision : Mettez en place des politiques différentes : les images peuvent être optimisées ; les PDFs sont rarement à supprimer sauf preuve d’absence d’usage.

Task 6: Identify attachments missing files (DB points to non-existent disk objects)

cr0x@server:~$ wp eval '
global $wpdb;
$ids=$wpdb->get_col("SELECT ID FROM {$wpdb->posts} WHERE post_type=\"attachment\" LIMIT 2000");
$missing=0;
foreach ($ids as $id){
  $file=get_attached_file($id);
  if ($file && !file_exists($file)) { $missing++; }
}
echo "checked=".count($ids)." missing=$missing\n";
'
checked=2000 missing=37

Sens : Certains attachments pointent vers des fichiers qui ne sont pas sur le disque (peut-être offloadés, peut-être supprimés, peut-être chemin changé).

Décision : Avant de supprimer quoi que ce soit d’autre, corrigez les problèmes de fichiers manquants : ils polluent votre analyse et peuvent indiquer un décalage d’offload.

Task 7: Check whether media is offloaded (don’t delete your origin by accident)

cr0x@server:~$ wp plugin list --status=active
+---------------------------+----------+--------+---------+
| name                      | status   | update | version |
+---------------------------+----------+--------+---------+
| amazon-s3-and-cloudfront  | active   | none   | 3.2.2   |
| wordpress-seo             | active   | none   | 22.4    |
| woocommerce               | active   | none   | 8.6.1   |
+---------------------------+----------+--------+---------+

Sens : Un plugin d’offload est actif. Le système de fichiers peut ne pas être le stockage faisant autorité.

Décision : Vérifiez le mode d’offload (copy vs move). S’il « déplace » vers le stockage d’objets, les fichiers locaux peuvent déjà être absents, et supprimer des « orphelins » localement ne sert à rien.

Task 8: Find references to a specific media URL in post content (spot-check methodology)

cr0x@server:~$ wp db query "SELECT ID, post_title FROM wp_posts WHERE post_type IN ('post','page') AND post_status IN ('publish','draft') AND post_content LIKE '%/wp-content/uploads/2019/07/trade-show-wall.jpg%';"
+-----+--------------------------+
| ID  | post_title               |
+-----+--------------------------+
| 912 | Summer trade show recap  |
+-----+--------------------------+

Sens : Au moins un post référence directement l’URL. La supprimer créera une rupture visible.

Décision : Si référencée, conservez le fichier ou remplacez-le tout en préservant l’URL (voir stratégie de redirection plus loin).

Task 9: Find attachments that are not attached to a parent post (not the same as unused)

cr0x@server:~$ wp db query "SELECT COUNT(*) AS unattached FROM wp_posts WHERE post_type='attachment' AND post_parent=0;"
+------------+
| unattached |
+------------+
| 39122      |
+------------+

Sens : Beaucoup d’éléments médias sont « détachés ». C’est normal pour les éditeurs modernes et les builders ; cela ne prouve pas qu’ils sont inutilisés.

Décision : N’utilisez pas post_parent=0 comme filtre de suppression. Utilisez le balayage de références + les logs d’accès.

Task 10: Validate thumbnails explosion (how many files per attachment)

cr0x@server:~$ wp eval '
$id=12345;
$meta=wp_get_attachment_metadata($id);
echo "file=".$meta["file"]."\n";
echo "sizes=".count($meta["sizes"])."\n";
'
file=2019/07/trade-show-wall.jpg
sizes=18

Sens : Un upload a généré 18 dérivés. Multipliez par 50k images et votre nombre d’inodes raconte une histoire.

Décision : Si les tailles sont excessives, réduisez les tailles d’images enregistrées (thème/plugins) avant de régénérer quoi que ce soit.

Task 11: Use access logs to find hot missing media (what users are actually seeing)

cr0x@server:~$ sudo awk '$9==404 && $7 ~ /\/wp-content\/uploads\// {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -n 10
52 /wp-content/uploads/2019/07/trade-show-wall.jpg
31 /wp-content/uploads/2020/01/hero-background.tif
18 /wp-content/uploads/2018/03/old-logo.png
11 /wp-content/uploads/2019/07/trade-show-wall-1024x768.jpg
9 /wp-content/uploads/2017/12/banner-winter.jpg

Sens : Ce sont les URL qui échouent actuellement en production. Certaines sont des originaux ; d’autres sont des tailles générées. C’est important.

Décision : Corrigez d’abord les plus gros fautifs. Si les 404 concernent surtout des tailles intermédiaires, la régénération ou des redirections peuvent être nécessaires.

Task 12: Confirm whether a “missing” file exists on disk (avoid chasing ghosts)

cr0x@server:~$ ls -la /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
-rw-r--r-- 1 www-data www-data 41943040 Jul 12  2019 /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg

Sens : Le fichier existe. Si les utilisateurs obtiennent quand même une 404, le problème vient probablement du routage, des permissions, d’un mismatch CDN/origine ou des règles d’offload.

Décision : Ne supprimez rien. Diagnostiquez le chemin de service web (alias Nginx, comportement CDN, permissions fichier).

Task 13: Check permissions and ownership in uploads (classic silent break)

cr0x@server:~$ namei -l /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
f: /var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg
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 www-data www-data wp-content
drwxr-x--- www-data www-data uploads
drwxr-x--- www-data www-data 2019
drwxr-x--- www-data www-data 07
-rw-r----- www-data www-data trade-show-wall.jpg

Sens : Les répertoires sont 750 et le fichier est 640. Si Nginx s’exécute en tant que www-data, c’est bon. S’il s’exécute sous un autre utilisateur, vous aurez des 403/404 selon la configuration.

Décision : Corrigez les permissions avant le nettoyage. Sinon vous risquez de classer à tort « inaccessible » comme « inutilisé ».

Task 14: Quarantine suspected orphans safely (move, don’t delete)

cr0x@server:~$ mkdir -p /var/www/html/wp-content/uploads-quarantine
cr0x@server:~$ rsync -a --remove-source-files /var/www/html/wp-content/uploads/2017/ /var/www/html/wp-content/uploads-quarantine/2017/
cr0x@server:~$ find /var/www/html/wp-content/uploads/2017 -type f | head

Sens : La dernière commande n’imprime rien parce que les fichiers ont été déplacés. C’est réversible : vous pouvez les remettre rapidement.

Décision : Mettez en quarantaine les médias anciens par morceaux (par année/mois). Puis surveillez les 404. Si rien n’augmente, supprimez la quarantaine plus tard.

Task 15: Add a temporary Nginx fallback for quarantined files (URL safety net)

cr0x@server:~$ sudo nginx -T | sed -n '1,120p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
# ...snip...

Sens : La config est valide. Vous pouvez maintenant ajouter en toute sécurité un bloc location (pendant une fenêtre de changement) qui essaie le chemin normal, puis le chemin de quarantaine.

Décision : Pour une période de migration/quarantaine, vous pouvez servir depuis la quarantaine pour garder les URL fonctionnelles pendant que vous confirmez le statut d’inutilisation.

Exemple de logique de location (conceptuelle ; implémentez dans le bloc serveur approprié et testez) : try normal path first, then quarantine.

Task 16: Track media-related DB bloat (attachment meta can be huge)

cr0x@server:~$ wp db query "SELECT ROUND(SUM(LENGTH(meta_value))/1024/1024,1) AS mb FROM wp_postmeta WHERE meta_key='_wp_attachment_metadata';"
+------+
| mb   |
+------+
| 892.4|
+------+

Sens : Près d’un gigaoctet de métadonnées d’attachement. Cela peut impacter les sauvegardes et les performances DB.

Décision : N’« optimisez » pas en supprimant les métadonnées. Réduisez plutôt les tailles dérivées à l’avenir et régénérez sélectivement.

Task 17: Spot-check that “regeneration” won’t alter URLs (it can, indirectly)

cr0x@server:~$ wp eval '
$id=12345;
echo get_attached_file($id)."\n";
'
/var/www/html/wp-content/uploads/2019/07/trade-show-wall.jpg

Sens : Le chemin du fichier original est stable. Régénérer les vignettes ne devrait pas changer l’URL de l’original, mais cela peut ajouter/enlever des fichiers intermédiaires que le front-end référence via srcset.

Décision : Si vous régénérez, vérifiez la sortie srcset dans le HTML rendu et assurez-vous que le CDN/l’origine possèdent les tailles intermédiaires.

Blague n°2 : « Nous allons juste régénérer les vignettes » est le WordPress pour « J’aimerais planifier un test de charge surprise ».

Trois micro-récits d’entreprise tirés du terrain

Micro-récit 1 : L’incident causé par une mauvaise hypothèse (détaché = inutilisé)

L’entreprise avait une équipe contenu qui aimait les pages de destination et détestait attendre l’ingénierie. En quelques années, ils sont passés de l’éditeur classique à un page builder, puis aux blocs, puis de nouveau au builder « pour la vitesse ». La bibliothèque médias a explosé. Les sauvegardes sont devenues plus lentes. Quelqu’un a finalement dit l’évidence : « Supprimons les médias détachés. »

Un ingénieur a exécuté une requête pour les attachments avec post_parent=0 et les a supprimés. Ça semblait scientifique. C’était aussi faux, de la manière spécifique dont WordPress aime être faux : les éditeurs modernes uploadent souvent des images qui ne sont jamais « attachées » à un post parent, même si elles sont utilisées partout.

La panne ne s’est pas manifestée par une défaillance totale du site. C’était pire. La navigation principale chargeait toujours. Les images hero avaient disparu sur des pages à forte valeur. Les pages de campagne ressemblaient à des sites de 1998. Le support a été submergé de captures d’écran et de messages commençant par « C’est seulement chez moi ou… ».

Le postmortem était peu glorieux : pas d’index de références, pas de quarantaine, et pas de validation basée sur les logs. Ils ont restauré depuis une sauvegarde, mais la restauration a aussi annulé des changements de contenu non liés et créé un second désordre. La vraie correction fut ennuyeuse : scanner les références en staging, mettre en quarantaine d’abord, puis surveiller les 404 et rollback rapide si nécessaire.

Micro-récit 2 : L’optimisation qui a mal tourné (déduplication + renommage pour « propreté »)

Une autre organisation voulait réduire le stockage et « standardiser » les noms de fichiers. Leur plan : détecter les images identiques, garder une copie canonique et renommer tout en un schéma propre comme brand-product-usecase-001.jpg. Ils avaient même un tableur. C’est le moment où l’on entend un SRE inspirer profondément.

Ils ont réécrit les URL d’images dans le contenu par un search/replace, puis supprimé les fichiers « dupliqués ». Ça semblait correct après un contrôle rapide. Le problème : toutes les références ne se trouvaient pas dans le contenu des posts. Certaines étaient dans les options du thème, d’autres dans le CSS, d’autres intégrées dans d’anciens PDFs, et certaines en cache dans un consumer headless qui scrapait le contenu chaque nuit.

Pendant deux semaines, des images échouaient aléatoirement selon le chemin pris par la requête : le HTML en cache pointait encore vers les anciens noms de fichiers, et le CDN mettait en cache agressivement des 404. Le support ne parvenait pas à reproduire de façon cohérente. L’ingénierie accusait le CDN. Le CDN accusait l’origine. L’origine accusait « le site ». Classique.

Ils ont fini par implémenter des redirections des anciennes vers les nouvelles URL, mais comme les chemins avaient été changés en masse, la table de redirections était énorme et fragile. La leçon finale : dédupliquer et renommer est acceptable seulement si vous traitez les anciennes URL comme permanentes et fournissez des redirections durables (ou ne changez jamais l’URL).

Micro-récit 3 : La pratique ennuyeuse mais correcte qui a sauvé la situation (quarantaine + vérification par logs)

Une autre équipe avait une bibliothèque médias qui avait grossi silencieusement jusqu’à ce que leur stockage partagé commence à couiner. Ils n’ont pas paniqué en supprimant. Ils ont cloné la production en staging, écrit un script pour lister les candidats « orphelins », puis ont fait quelque chose de profondément démodé : ils ont revu un échantillon manuellement avec l’équipe contenu.

Puis ils ont mis en quarantaine par année : déplacé l’année la plus ancienne des uploads vers un répertoire séparé et ajouté une solution de secours temporaire dans le serveur web pour servir depuis la quarantaine si le fichier était demandé. Ils l’ont laissé quelques semaines. Pendant ce temps, ils ont surveillé les logs d’accès et les 404, et ont obtenu des preuves réelles de ce qui était encore demandé.

Ils ont trouvé des usages surprenants en longue traîne : une image d’un vieil article était toujours intégrée dans le wiki d’un partenaire, et un PDF d’une campagne retirée était encore lié dans une présentation commerciale. Parce que la quarantaine servait encore les fichiers, personne n’a remarqué de rupture. L’équipe a simplement déplacé ces assets spécifiques vers l’arborescence principale et les a marqués « ne pas supprimer ».

Après la fenêtre de surveillance, ils ont supprimé les fichiers restants en quarantaine, réduit le temps de sauvegarde et évité tout incident d’URL. Le résultat n’était pas spectaculaire. C’était mieux : personne en dehors de l’ingénierie n’a remarqué, ce qui est le plus grand compliment que puissent recevoir des systèmes de production.

Erreurs fréquentes : symptôme → cause → correction

1) Symptom: sudden spike of 404s under /wp-content/uploads/

Cause racine : Fichiers supprimés ou déplacés sans redirections ; ou cache CDN manquant après changement de paramètre d’offload.

Correction : Restaurer/rollback de la quarantaine en premier. Puis implémenter un service de secours temporaire et reconstruire une carte des références. Supprimez seulement après une fenêtre de surveillance.

2) Symptom: images load in admin preview but not on the public site

Cause racine : L’admin utilise des routes authentifiées ou un domaine différent ; le site public utilise un domaine CDN ou un mappage de chemin différent.

Correction : Comparez wp option get siteurl, home, et les paramètres offload/CDN. Validez l’URL exacte dans les outils dev du navigateur et traquez-la jusqu’à l’origine.

3) Symptom: random missing sizes like -1024x768 while originals exist

Cause racine : Tailles intermédiaires supprimées, mais le contenu les référence via srcset ou des URLs codées en dur.

Correction : Régénérez les vignettes de façon sélective, ou ajoutez des règles de réécriture qui retombent sur l’original si une taille intermédiaire manque (attention : peut augmenter la bande passante).

4) Symptom: Media Library grid is slow, search is painful

Cause racine : Problèmes de performance de la BD, tables d’attachments énormes, stockage lent, ou l’admin effectue des requêtes coûteuses ; parfois absence de cache d’objets.

Correction : Profilez les requêtes DB ; assurez des index pour les patterns courants ; ajoutez du cache d’objets (Redis/Memcached) si approprié ; évitez les régénérations massives pendant les heures ouvrables.

5) Symptom: “cleanup” frees almost no space

Cause racine : Vous avez supprimé des entrées BD mais pas les objets fichiers, ou vous utilisez de l’offload où le disque local n’est pas le principal consommateur, ou ce sont les vignettes qui dominent.

Correction : Mesurez le système de fichiers par année et par type de fichier. Confirmez où les médias sont stockés. Comptez les tailles intermédiaires par image. Nettoyez ce qui est réellement volumineux.

6) Symptom: after migration, old image URLs redirect to homepage or 301 loop

Cause racine : Règles de réécriture trop larges ou comportement de redirection canonique de WordPress/plugins SEO.

Correction : Rendre les redirections spécifiques (chemin uploads uniquement), testez avec curl, et évitez de réécrire tout vers /. Assurez-vous que les redirections préservent les segments de chemin.

7) Symptom: deleting “unused” images breaks PDFs and email templates

Cause racine : Des références existent en dehors des posts WordPress (PDF, HTML d’email stocké ailleurs, templates CRM, sites externes qui hotlinkent).

Correction : Utilisez les logs d’accès + logs CDN pour détecter les hits externes. Considérez les médias legacy à fort trafic comme une « API publique ». Conservez-les ou redirigez-les.

8) Symptom: cleanup job times out or pegs CPU/I/O

Cause racine : Scanner des millions de fichiers sur un stockage réseau, régénérer des vignettes en masse, ou exécuter des requêtes DB LIKE sans contraintes.

Correction : Fractionnez le travail par année/mois, exécutez hors heures, limitez la concurrence, et préférez un ciblage basé sur les logs plutôt qu’un scan complet.

Listes de vérification / plan étape par étape

Phase 0: Decide what “doesn’t break URLs” means for you

  • Strict : Chaque URL historique retourne les mêmes octets de l’asset pour toujours. (Commun pour les assets de marque/légaux.)
  • Pratique : Chaque URL historique retourne un asset valide (peut-être optimisé/remplacé), pas de 404. (La plupart des sites marketing.)
  • Lâche : Les URL importantes sont conservées ; la longue traîne peut 404. (Acceptez-le seulement si vous assumez des embeds cassés.)

Choisissez-en une. Écrivez-la. Faites-en une contrainte pour chaque décision.

Phase 1: Inventory and baseline

  1. Mesurez disque et inodes (df -h, df -i).
  2. Mesurez les uploads par année/mois (du --max-depth).
  3. Listez les plus gros coupables (find ... -printf '%s' sort).
  4. Comptez les attachments par mime type (requête DB).
  5. Vérifiez les plugins d’offload et confirmez leur mode.
  6. Établissez une baseline des 404 pour les uploads dans les logs (top des URL manquantes).

Phase 2: Build a reference model (good enough, not perfect)

Vous essayez de répondre : « Si je supprime ce fichier, qui crie ? » WordPress ne répondra pas à cette question pour vous.

  • Commencez par les références directes dans post_content : recherchez les motifs /wp-content/uploads/. C’est grossier mais capture beaucoup de cas.
  • Incluez l’usage des IDs d’attachement : les blocs réfèrent souvent des IDs, pas des URL. Scannez les motifs "id":123 dans le contenu des blocs si possible.
  • Incluez les options du thème : logos d’en-tête, favicons, defaults Open Graph, images de fond.
  • Incluez les tables de plugins connues : builders, sliders, galeries.
  • Superposez les logs d’accès : les requêtes sont le sérum de vérité. Si une URL est touchée, elle est utilisée par quelque chose, même si c’est un vieux PDF sur un site partenaire.

Phase 3: Quarantine, monitor, then delete

  1. Quarantaine par répertoire (année la plus ancienne d’abord). Déplacez les fichiers vers un chemin de quarantaine sur le même système de fichiers si possible (mouvements rapides).
  2. Fichier de sécurité optionnel : règle serveur temporaire pour servir depuis la quarantaine si introuvable.
  3. Surveillez : taux de 404 pour les uploads, top des URL manquantes, et signaux de budget d’erreur pendant au moins 1–4 semaines (selon les patterns de trafic).
  4. Restaurez les exceptions : remettez les fichiers demandés ou référencés.
  5. Supprimez la quarantaine quand la courbe de requêtes reste plate.

Phase 4: Prevent re-bloat

  • Réduisez les tailles intermédiaires inutiles enregistrées par le thème/plugins.
  • Appliquez des dimensions maximales d’upload pour les éditeurs (politique + outils).
  • Activez l’optimisation d’images côté serveur avec prudence (ne renommez pas ; ne changez pas les URLs).
  • Révisez qui peut uploader des médias et quels formats sont autorisés.
  • Planifiez des audits périodiques (trimestriels), pas des « fêtes de purge tous les cinq ans ».

Redirect strategy: when you must change paths, don’t improvise

Si vous migrez les uploads vers un nouveau chemin ou domaine (CDN ou bucket), la méthode sûre est : garder les anciennes URL fonctionnelles via des redirections ou une réécriture qui préserve le chemin relatif complet.

  • Meilleur : garder la même URL et ne changer que le stockage backend (l’origine pull depuis le store d’objets, mise à jour du CDN).
  • Ensuite : 301 depuis l’ancien chemin vers le nouveau avec une structure relative identique.
  • Évitez : 302 « temporaire pour toujours », rediriger tout vers la page d’accueil, ou réécrire les query strings sans test.

FAQ

1) Can I safely delete “unattached” media?

Non, pas en règle générale. post_parent=0 signifie souvent « uploadé via un éditeur/builder moderne », pas « inutilisé ». Utilisez des scans de références et les logs d’accès.

2) What’s the safest first cleanup that yields real space?

Commencez par les répertoires outliers (une année/mois énorme) et les fichiers manifestement surdimensionnés. Puis mettez cette tranche en quarantaine et surveillez. Vous apprendrez le système sans tout risquer.

3) If I regenerate thumbnails, will it break URLs?

Régénérer les vignettes ne casse généralement pas l’URL du fichier original. Cela peut casser des pages qui référencent des noms de fichiers intermédiaires spécifiques si ces tailles changent ou disparaissent. Testez la sortie srcset et vérifiez que les fichiers générés existent là où le serveur web et le CDN s’y attendent.

4) How do I keep URLs stable while optimizing images?

Optimisez sur place sans renommer et sans changer la structure des répertoires. Si votre optimiseur renomme les fichiers (ou convertit en WebP avec de nouveaux noms), vous aurez besoin de redirections ou vous accepterez des liens cassés.

5) I use S3/offload. Should I clean local uploads at all?

Peut-être. Confirmez d’abord si le mode d’offload conserve des copies locales. Si le local est juste un cache, supprimer local peut augmenter les fetchs d’origine ou causer des pics de latence. Si le stockage d’objets fait autorité, votre cible de nettoyage est le bucket, pas le disque du serveur.

6) Why do I see files on disk that aren’t in the Media Library?

Causes courantes : imports échoués, uploads manuels via FTP, comportement d’un vieux plugin, ou posts attachment supprimés sans retirer les fichiers. La réalité disque et la réalité BD divergent avec le temps — prévoyez-le.

7) Do I need a plugin to find unused media?

Pas strictement. Vous pouvez construire un process solide avec WP-CLI, des requêtes DB et des logs. Les plugins aident, mais ajoutent aussi des hypothèses — surtout autour des builders et des champs personnalisés. Validez avant de faire confiance.

8) What monitoring should I use during quarantine?

Surveillez le taux de 404 pour les chemins uploads, les top des URL manquantes, et tout pic de fetch origine du CDN. Surveillez aussi des checks synthétiques utilisateurs sur des pages clés intégrant beaucoup d’images.

9) How long should I keep quarantine before deleting?

Assez longtemps pour couvrir votre cycle de trafic. Pour des sites B2B, 2–4 semaines est généralement plus sûr que 2–4 jours. Pour des sites grand public à fort trafic, 48–72 heures peuvent suffire à donner du signal, mais la longue traîne d’embeds existe toujours.

10) What if legal/compliance requires deleting assets?

Alors votre contrainte « ne pas casser les URLs » change : vous devrez peut-être faire retourner un 410 Gone ou un asset de remplacement. Faites-le délibérément : journalisez, documentez, et évitez les 404 silencieuses.

Conclusion : que faire la semaine prochaine

Le nettoyage des médias qui ne casse pas les URL est un exercice de fiabilité portant un chapeau de gestion de contenu. Vous ne « nettoyez » pas une bibliothèque. Vous gérez une API d’assets publique avec des années de consommateurs, dont la plupart ne mettront jamais un ticket.

Étapes pratiques :

  1. Exécutez les tâches de baseline : disque, inodes, années majeures, top des URL manquantes, vérification d’offload.
  2. Choisissez une année/mois ancien comme pilote. Mettez-le en quarantaine, ne le supprimez pas.
  3. Ajoutez un filet de sécurité temporaire (fallback serveur) si possible, et surveillez les 404 des uploads quotidiennement.
  4. Remettez en place les exceptions, puis supprimez la quarantaine uniquement après une fenêtre de surveillance calme.
  5. Empêchez le re-gonflement : réduisez les tailles d’images, appliquez des politiques d’upload, et planifiez des audits trimestriels.

Si vous procédez ainsi, le nettoyage sera presque décevant de calme. C’est le but. Les systèmes de production récompensent les adultes.

← Précédent
Wi‑Fi se déconnecte toutes les 10 minutes : le paramètre avancé du pilote qui le corrige
Suivant →
Copies lentes vers un NAS : les réglages SMB qui comptent vraiment

Laisser un commentaire