WordPress: La limpieza de la biblioteca de medios que no rompe las URLs

¿Te fue útil?

Nada hace entrar en pánico a un equipo de marketing como una página principal llena de imágenes rotas. Nada hace entrar en pánico a un SRE como una “limpieza rápida” en producción que silenciosamente se convierte en un generador distribuido de 404.

Puedes reducir una Biblioteca de Medios de WordPress inflada, bajar facturas de almacenamiento y acelerar backups—sin romper URLs. Pero debes tratarlo como un cambio en producción: medir primero, borrar al final y mantener una vía de escape.

Lo que realmente estás limpiando (y por qué se rompen las URLs)

“Limpieza de medios” suena a borrar un montón de JPEGs. En WordPress, es más complicado:

  • Objetos del sistema de archivos: normalmente bajo wp-content/uploads/YYYY/MM/, además de tamaños generados (miniaturas) y a veces variantes “-scaled”.
  • Filas de la base de datos: los attachments son posts en wp_posts con post_type='attachment'.
  • Metadatos: metadata de attachment en wp_postmeta (notablemente _wp_attached_file y _wp_attachment_metadata) le dice a WordPress qué archivos existen y qué tamaños se generaron.
  • Referencias: posts/páginas guardan referencias a imágenes en HTML, JSON de bloques, shortcodes, opciones de tema, widgets y a veces en tablas de plugins.
  • Capas CDN/offload: tus archivos “reales” pueden estar en S3/Cloud Storage, con copias locales opcionales.

Las URLs se rompen por tres razones principales:

  1. Borraste un archivo que aún está referenciado (por contenido, un builder, una opción de tema, una tarjeta social o una tabla de plugin).
  2. Cambiaste el mapeo de URL (cambio de site URL, cambio en la configuración del plugin de offload, distinto bucket/ruta, o reescritura de la ruta de uploads).
  3. Cambiaste el nombre de archivo (deduplicación/renombrado/optimización que “útilmente” re-codificó, re-guardó o relocó imágenes).

La limpieza de medios que no rompe URLs no es fundamentalmente un problema de borrado. Es un problema de integridad de referencias.

Verdad operacional: WordPress no mantiene un índice completo “esta imagen es usada por estos posts”. Tú construyes ese mapa, o aceptas riesgo. En producción, acepta lo menos posible.

Broma #1: Borrar medios en un sitio WordPress en vivo sin un plan es como podar un bonsái con una motosierra—técnicamente una herramienta, emocionalmente un error.

Hechos interesantes & contexto histórico (breve, útil)

  1. Los attachments son posts. WordPress almacena medios como filas en wp_posts desde versiones tempranas, por eso puedes “editar” una imagen como si fuera un post.
  2. Las miniaturas cambiaron las reglas. Los tamaños intermedios automáticos existían desde temprano, pero el comportamiento moderno de imágenes responsivas (como srcset) convirtió “una subida → muchos archivos” en el comportamiento por defecto.
  3. El sufijo “-scaled” es un artefacto relativamente moderno. WordPress empezó a generar imágenes “scaled” para cargas muy grandes y evitar que los originales gigantes rompieran diseños o consumieran memoria.
  4. El manejo de EXIF ha sido una trampa recurrente. Los metadatos de orientación pueden hacer que una imagen parezca “girada mal” según cómo se procese y regenere.
  5. La ruta de uploads es configurable. WordPress puede almacenar archivos fuera del directorio de uploads por defecto, pero muchos plugins asumen la ruta por defecto de todos modos.
  6. Los CDNs hicieron que la permanencia de URL importara. Cuando una campaña de email se envía, esas URLs de medios se convierten en puntos finales públicos casi inmutables.
  7. Los builders no siempre almacenan HTML plano. Los page builders suelen serializar contenido en blobs tipo JSON; un simple grep no detecta todo el uso.
  8. Algunos plugins de offload tratan el bucket como la fuente de verdad. La limpieza local puede ser segura—o catastrófica—dependiendo de si el offload es “copy” o “move”.
  9. WP-CLI se volvió la supervisión adulta. Operacionalmente, WP-CLI es la diferencia entre mantenimiento repetible y “hice clics y esperé lo mejor”.

Principios no negociables para una limpieza segura de medios

1) Las URLs son contratos

Marketing ve las URLs de imágenes como “activos”. Ingeniería debe verlas como interfaces públicas. Una vez que una URL se publica—páginas web, emails, PDFs, vistas previas sociales—cambiarla es un cambio que rompe compatibilidad.

2) Haz un plan de borrado que asuma que estás equivocado

Vas a perder una referencia en el primer intento. El plan debe incluir:

  • Una etapa reversible (cuarentena/mover en lugar de borrar).
  • Una ventana de monitorización (vigilar 404s, fetches de origen y tasas de error).
  • Un rollback (mover archivos de vuelta, restaurar filas DB, revertir reglas de redirección).

3) Separa “huérfano en la BD” de “no usado en contenido”

Un attachment puede no aparecer en la interfaz de la Biblioteca de Medios (o no verse referenciado) y aún así estar en uso:

  • En opciones de tema (customizer, logo de cabecera, imagen Open Graph).
  • En widgets/menús.
  • En imágenes de fondo en CSS.
  • En tablas de plugins de builders.

4) No confundas “bytes duplicados” con “seguro para deduplicar”

Dos archivos pueden ser idénticos pero tener URLs diferentes que ambas estén en uso. Dedupear sin redirecciones es simplemente una caída lenta.

5) Tu estrategia de backups es parte de la estrategia de limpieza

Si tus backups son lentos, caros o poco fiables, te sentirás tentado a “simplemente borrar”. Arregla la canalización de backups y tomarás decisiones con más calma. Además: prueba restauraciones. Un backup que no has restaurado es solo una sensación cara.

6) Confirma qué sirve realmente los medios

¿Es disco local? ¿Un NFS/EFS montado? ¿Un object store vía un plugin? ¿Un CDN que hace pull del origen? Si no conoces la ruta de servicio, no puedes predecir el radio de impacto.

Una cita para pegar en un sticky:

“La esperanza no es una estrategia.” — General Gordon R. Sullivan

Guía rápida de diagnóstico

Cuando alguien dice “la Biblioteca de Medios es enorme y el sitio está lento”, no recurras inmediatamente a scripts de borrado. Primero, identifica qué tipo de dolor tienes: presión de almacenamiento, problemas de backups, lentitud en admin, lentitud en frontend o churn CDN/origen.

Primero: confirma que el síntoma es real y actual

  • Almacenamiento: uso del sistema de archivos y agotamiento de inodos.
  • Backups: tiempo de ejecución, comportamiento incremental, cuenta de objetos.
  • Frontend: 404s en uploads, picos de ancho de banda en origen, tasa de misses en caché.
  • Admin: grid de medios lento/búsqueda dolorosa por lentitud de BD o filas de metadata enormes.

Segundo: encuentra el dominio del cuello de botella

  • Limitado por disco: I/O lento, demasiados archivos pequeños, sistema de archivos en red lento.
  • Limitado por BD: consultas lentas en wp_posts/wp_postmeta, falta de índices para tus patrones de búsqueda, autoload bloat que interfiere con el admin.
  • Limitado por red: misses de caché en CDN, origen inalcanzable, cabeceras de caché erróneas, mala configuración del plugin de offload.

Tercero: elige la palanca de menor riesgo

  • Si el problema es duración de backups, implementa backups incrementales o excluye caches antes de borrar medios.
  • Si el problema es ancho de banda frontend, corrige el caching y el dimensionado de imágenes antes de “limpiar”.
  • Si el problema es presión de almacenamiento, pon en cuarentena medios antiguos primero; no borres a ciegas.

Tareas prácticas (comandos + salida + decisiones)

Estas son tareas de grado producción: cada una incluye un comando, salida de ejemplo, qué significa la salida y la decisión que tomas. Ejecútalas en un clon de staging primero. Luego ejecútalas en producción con una ventana de cambios y 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% /

Meaning: 89% used is uncomfortable but not an emergency. If this is 95%+ you’re in “incidents happen here” territory.

Decision: If >90%, prioritize safe quarantine + expansion plan; avoid long-running scripts that generate temp files.

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

Meaning: Inode usage is fine. If inode usage hits ~90% with plenty of GB free, you’re suffering from “too many files,” not “too much data.”

Decision: If inode-bound, focus on thumbnail sprawl and cache directories, not just big originals.

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

Meaning: One year dominates. That often correlates with a campaign, a migration, or a plugin that generated tons of sizes.

Decision: Target analysis on the outlier year first. It’s where you’ll get the safest ROI.

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

Meaning: You likely have “web-hostile” formats (TIFF, raw-ish JPGs, huge PNGs) that never should have been uploaded.

Decision: Prefer replacement + redirects (keep original URL alive) or leave originals and fix rendering sizes. Don’t bulk-delete.

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

cr0x@server:~$ wp option get upload_path

Meaning: Empty output usually means “default uploads path.” If it prints a custom path, your cleanup scripts must follow it.

Decision: If custom path exists, audit any plugins and Nginx/Apache rules that assume default uploads.

cr0x@server:~$ wp option get upload_url_path

Meaning: Empty output means WordPress builds URLs from site URL + uploads path.

Decision: If this is set (or offload plugin overwrites), URL mapping may differ from filesystem layout.

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    |
+----------------+--------+

Meaning: You have a large image library and a decent number of PDFs. PDFs often get embedded in downloads and emails; treat them like permanent.

Decision: Set different policies: images can be optimized; PDFs rarely should be deleted unless you can prove no 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

Meaning: Some attachments point to files that aren’t on disk (maybe offloaded, maybe deleted, maybe path changed).

Decision: Before deleting anything else, fix missing-file issues: they pollute your analysis and can indicate offload mismatch.

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   |
+---------------------------+----------+--------+---------+

Meaning: An offload plugin is active. The filesystem may not be the authoritative store.

Decision: Verify offload mode (copy vs move). If it “moves” to object storage, local files may already be absent, and deleting “orphans” locally does nothing useful.

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  |
+-----+--------------------------+

Meaning: At least one post directly references the URL. Deleting it will create a visible break.

Decision: If referenced, keep the file or replace it while preserving the URL (see redirect strategy later).

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      |
+------------+

Meaning: Many media items are “unattached.” This is normal for modern editors and builders; it does not prove unused.

Decision: Don’t use post_parent=0 as a deletion filter. Use reference scanning + access logs.

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

Meaning: One upload generated 18 derivatives. Multiply by 50k images and your inode count tells a story.

Decision: If sizes are excessive, reduce registered image sizes (theme/plugins) before regenerating anything.

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

Meaning: These are the URLs that are currently failing in production. Some are originals; some are generated sizes. That’s important.

Decision: Fix the top offenders first. If the 404s are mostly intermediate sizes, regeneration or redirects may be required.

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

Meaning: File exists. If users still get 404, your issue is likely routing, permissions, CDN/origin mismatch, or offload rules.

Decision: Don’t delete anything. Diagnose web serving path (Nginx alias, CDN behavior, file perms).

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

Meaning: Directories are 750 and file is 640. If Nginx runs as www-data, fine. If it runs as a different user, it’ll 403/404 depending on config.

Decision: Fix permissions before cleanup. Otherwise you’ll misclassify “unreachable” as “unused.”

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

Meaning: The last command prints nothing because files were moved out. This is reversible: you can move them back quickly.

Decision: Quarantine old media in chunks (by year/month). Then monitor 404s. If none spike, delete quarantine later.

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...

Meaning: Config is valid. Now you can safely add a location block (in a change window) that tries the normal file, then the quarantine path.

Decision: For a migration/quarantine period, you can serve from quarantine to keep URLs working while you confirm unused status.

Example location logic (conceptual; implement in the right server block and test): 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|
+------+

Meaning: Nearly a gigabyte of attachment metadata. That can affect backups and DB performance.

Decision: Don’t “optimize” by deleting metadata. Instead, reduce derivative sizes going forward and regenerate selectively.

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

Meaning: Original file path is stable. Regenerating thumbnails should not change the original’s URL, but it can add/remove intermediate files that the front-end references via srcset.

Decision: If you regenerate, verify srcset output in rendered HTML and ensure the CDN/origin has the intermediate sizes.

Broma #2: “We’ll just regenerate thumbnails” es WordPress para “Me gustaría programar una prueba de carga sorpresa.”

Tres microhistorias corporativas desde las trincheras

Microhistoria 1: El incidente causado por una suposición errónea (unattached = unused)

La empresa tenía un equipo de contenido que adoraba las landing pages y odiaba esperar a ingeniería. En unos años pasaron del editor clásico a un page builder, luego a bloques y luego volvieron al builder “por velocidad”. La biblioteca de medios se infló. Los backups se volvieron más lentos. Alguien finalmente dijo lo obvio: “Borramos los medios sin adjuntar”.

Un ingeniero ejecutó una consulta por attachments con post_parent=0 y los borró. Parecía científico. También estuvo equivocado en la forma que a WordPress le gusta equivocarse: los editores modernos suben imágenes que nunca quedan “adjuntas” a un post padre, aun cuando se usan por todas partes.

La caída no apareció como una falla total del sitio. Fue peor. La navegación superior seguía cargando. Las hero images desaparecieron en páginas de alto valor. Las páginas de campaña parecían hechas en 1998. El canal de soporte se llenó de capturas y mensajes que empezaban con “¿Soy el único o…”.

El postmortem fue poco glamoroso: no había índice de referencias, ni cuarentena, ni validación basada en logs. Restauraron desde backup, pero la restauración también revirtió cambios de contenido no relacionados y creó un segundo lío. La solución real fue aburrida: montar un escaneo de referencias, cuarentena primero, luego vigilar 404s y hacer rollback rápido.

Microhistoria 2: La optimización que salió mal (dedupe + rename por “limpieza”)

Otra organización quería reducir almacenamiento y “estandarizar” nombres de archivo. Su plan: detectar imágenes idénticas, mantener una copia canónica y renombrar todo a un esquema limpio como brand-product-usecase-001.jpg. Incluso tenían una hoja de cálculo. Aquí es donde se oye inhalar a un SRE con fuerza.

Reescribieron URLs de imágenes en el contenido con search/replace y luego eliminaron los archivos “duplicados”. Parecía bien en una comprobación rápida. El problema: no todas las referencias vivían en el contenido del post. Algunas estaban en opciones de tema, otras en CSS, algunas embebidas en PDFs antiguos y otras cacheadas en un consumidor headless que raspaba contenido cada noche.

Durante dos semanas, imágenes fallaban al azar según la ruta que tomara la petición: el HTML cacheado seguía apuntando a nombres antiguos, y el CDN cacheaba 404s agresivamente. Helpdesk no podía reproducir consistentemente. Ingeniería culpaba al CDN. El CDN culpaba al origen. El origen culpaba “al sitio web”. Clásico.

Terminaron implementando redirecciones de viejas a nuevas URLs, pero como cambiaron rutas en bloque, el mapa de redirecciones fue enorme y frágil. La lección final: dedupe y renombrados están bien solo si tratas las URLs antiguas como permanentes y provees redirecciones duraderas (o nunca cambias la URL).

Microhistoria 3: La práctica aburrida pero correcta que salvó el día (cuarentena + verificación por logs)

Otro equipo tenía una biblioteca de medios que crecía silenciosamente hasta que su almacenamiento compartido empezó a quejarse. No borraron por pánico. Clonaron producción a staging, escribieron un script para listar candidatos “huérfanos” y luego hicieron algo profundamente poco a la moda: revisaron una muestra manualmente con el equipo de contenido.

Luego pusieron en cuarentena por año: movieron el año de uploads más antiguo a un directorio separado y añadieron un fallback temporal en el servidor web para servir desde la cuarentena si se solicitaba el archivo. Lo dejaron unas semanas. Durante ese tiempo vigilaron logs de acceso y 404s, y obtuvieron evidencia real de lo que seguía siendo solicitado.

Encontraron usos inesperados de larga cola: una imagen de un post antiguo seguía embebida en el wiki de un partner, y un PDF de una campaña retirada seguía enlazado en una presentación de ventas. Como la cuarentena aún servía los archivos, nadie notó una rotura. El equipo simplemente movió esos activos específicos de vuelta al árbol principal y los marcó “no borrar”.

Tras la ventana de monitorización, eliminaron los archivos en cuarentena restantes, redujeron el tiempo de backups y evitaron un incidente de URLs por completo. El resultado no fue dramático. Fue mejor: nadie fuera de ingeniería lo notó, que es el mayor cumplido que pueden recibir los sistemas en producción.

Errores comunes: síntoma → causa raíz → solución

1) Síntoma: pico repentino de 404s bajo /wp-content/uploads/

Causa raíz: Archivos borrados o movidos sin redirecciones; o el cache del CDN ahora falta objetos tras un cambio de configuración de offload.

Solución: Restaurar/rollback de cuarentena primero. Luego implementar un fallback temporal y reconstruir un mapa de referencias. Solo borrar después de una ventana de monitorización.

2) Síntoma: las imágenes cargan en la vista previa del admin pero no en el sitio público

Causa raíz: El admin usa rutas autenticadas o un dominio distinto; el sitio público usa dominio CDN o un mapeo de ruta distinto.

Solución: Compara wp option get siteurl, home y la configuración de offload/CDN. Valida la URL exacta en dev tools del navegador y traza hasta el origen.

3) Síntoma: faltan tamaños aleatorios como -1024x768 mientras los originales existen

Causa raíz: Se borraron tamaños intermedios, pero el contenido los referencia vía srcset o URLs con tamaño hardcodeadas.

Solución: Regenera miniaturas selectivamente, o añade reglas de rewrite que caigan al original desde tamaños intermedios faltantes (con cuidado: puede aumentar ancho de banda).

4) Síntoma: grid de la Biblioteca lento, la búsqueda es dolorosa

Causa raíz: Problemas de rendimiento de BD, tablas de attachments enormes, almacenamiento lento, o admin ejecutando consultas costosas; a veces falta cache de objetos.

Solución: Perfila consultas de BD; asegura índices para patrones comunes; añade cache de objetos (Redis/Memcached) donde corresponda; evita regeneraciones masivas en horas de negocio.

5) Síntoma: “la limpieza” libera casi nada de espacio

Causa raíz: Borraste entradas de BD pero no objetos del filesystem, o usas offload donde el disco local no es la huella principal, o las miniaturas dominan.

Solución: Mide el filesystem por año y por tipo de archivo. Confirma dónde se almacena media. Cuenta tamaños intermedios por imagen. Limpia lo que realmente pesa.

6) Síntoma: después de la migración, las URLs antiguas redirigen a la página principal o loop 301

Causa raíz: Reglas de rewrite demasiado amplias o comportamiento de canonical redirect de WordPress/plugins SEO.

Solución: Haz redirecciones específicas (solo la ruta de uploads), prueba con curl y evita reescribir todo a /. Asegura que las redirecciones preserven segmentos de ruta.

7) Síntoma: borrar “imágenes no usadas” rompe PDFs y plantillas de email

Causa raíz: Referencias existen fuera de posts de WordPress (PDFs, HTML de emails almacenado en otro sitio, plantillas CRM, webs externas que hacen hotlink).

Solución: Usa logs de acceso + logs de CDN para detectar hits externos. Trata media legacy con muchas visitas como “API pública”. Conserva o redirige.

8) Síntoma: trabajo de limpieza se agota o consume CPU/I/O

Causa raíz: Escanear millones de archivos en almacenamiento en red, regenerar miniaturas en bloque, o ejecutar consultas DB con LIKE sin restricciones.

Solución: Trocea el trabajo por año/mes, ejecútalo fuera de horas, limita concurrencia y prefiere targeting guiado por logs sobre escaneos totales.

Listas de verificación / plan paso a paso

Fase 0: Decide qué significa “no romper URLs” para ti

  • Estricta: Cada URL histórica devuelve los mismos bytes del activo para siempre. (Común para activos de marca/legales.)
  • Práctica: Cada URL histórica devuelve un activo válido (quizá optimizado/reemplazado), sin 404s. (La mayoría de sitios de marketing.)
  • Relajada: Se conservan URLs importantes; la larga cola puede 404. (Aceptable solo si asumes embeds rotos.)

Elige una. Escríbela. Hazla una restricción para cada decisión.

Fase 1: Inventario y línea base

  1. Medir disco e inodos (df -h, df -i).
  2. Medir uploads por año/mes (du --max-depth).
  3. Listar los mayores culpables (find ... -printf '%s' sort).
  4. Contar attachments por tipo mime (consulta BD).
  5. Comprobar plugins de offload y confirmar su modo.
  6. Baselinar 404s para uploads en logs (top URLs faltantes).

Fase 2: Construir un modelo de referencias (suficientemente bueno, no perfecto)

Intentas responder: “Si elimino este archivo, ¿quién lo va a reclamar?” WordPress no te lo dirá.

  • Empieza con referencias directas en post_content: busca patrones /wp-content/uploads/. Es tosco pero captura mucho.
  • Incluye uso de attachment IDs: los bloques a menudo referencian IDs, no URLs. Escanea por patrones "id":123 en el contenido de bloques cuando sea posible.
  • Incluye opciones de tema: logos de cabecera, favicons, defaults Open Graph, imágenes de fondo.
  • Incluye tablas de plugins conocidas: builders, sliders, galerías.
  • Superpone logs de acceso: las peticiones son el suero de la verdad. Si una URL recibe hits, se usa algo, aunque sea un PDF antiguo en un sitio partner.

Fase 3: Cuarentena, monitorizar, luego borrar

  1. Cuarentena por directorio (el año más antiguo primero). Mueve archivos a una ruta de cuarentena en el mismo filesystem si es posible (movimientos rápidos).
  2. Red de seguridad opcional: regla temporal del servidor para servir desde cuarentena si no se encuentra en la ruta principal.
  3. Monitorizar: 404s de uploads, top URLs faltantes y señales del error budget por al menos 1–4 semanas (depende del patrón de tráfico).
  4. Restaurar excepciones: mover de vuelta los archivos que aún se solicitan o referencian.
  5. Borrar la cuarentena cuando la curva de peticiones se mantenga plana.

Fase 4: Prevenir re-inflado

  • Reducir tamaños intermedios innecesarios registrados por tema/plugins.
  • Hacer cumplir dimensiones máximas de subida para editores (política + tooling).
  • Habilitar optimización de imágenes del lado servidor con cuidado (no renombrar archivos; no cambiar URLs).
  • Revisar quién puede subir medios y qué formatos están permitidos.
  • Programar auditorías periódicas (trimestrales), no “fiestas de purga cada cinco años”.

Estrategia de redirección: cuando debas cambiar rutas, no improvises

Si migras uploads a una nueva ruta o dominio (CDN o bucket), el método seguro es: mantener las URLs antiguas funcionando vía redirecciones o una reescritura que preserve la ruta relativa completa.

  • Mejor: mantener la misma URL y cambiar solo el almacenamiento backend (origen hace pull del object store, se actualiza el CDN).
  • Siguiente mejor: 301 de la ruta antigua a la nueva con estructura relativa idéntica.
  • Evitar: 302 “temporal para siempre”, redirigir todo a la página principal, o reescribir query strings sin pruebas.

Preguntas frecuentes

1) ¿Puedo borrar con seguridad medios “sin adjuntar”?

No, no como regla. post_parent=0 a menudo significa “subido vía editor/builder moderno”, no “no usado”. Usa escaneos de referencias y logs de acceso.

2) ¿Cuál es la primera limpieza más segura que da espacio real?

Empieza con directorios outlier (un año/mes enorme) y archivos obviamente sobredimensionados. Luego pon en cuarentena ese segmento y monitoriza. Aprenderás del sistema sin arriesgarlo todo.

3) Si regenero miniaturas, ¿romperá URLs?

Regenerar miniaturas normalmente no cambia la URL del archivo original. Puede romper páginas que referencian nombres de archivos específicos de tamaños intermedios si esos tamaños cambian o desaparecen. Prueba la salida de srcset y verifica que los archivos generados existan donde el servidor web y el CDN los esperan.

4) ¿Cómo mantengo URLs estables mientras optimizo imágenes?

Optimiza en sitio sin renombrar y sin cambiar la estructura de directorios. Si tu optimizador renombra archivos (o convierte a WebP con nuevos nombres), necesitas redirecciones o aceptas enlaces rotos.

5) Uso S3/offload. ¿Debo limpiar uploads locales?

Quizá. Primero confirma si el modo de offload guarda copias locales. Si lo local es solo caché, borrar local puede aumentar fetches al origen o causar picos de latencia. Si el object storage es la fuente autoritativa, tu objetivo de limpieza es el bucket, no el disco del servidor.

6) ¿Por qué veo archivos en disco que no están en la Biblioteca de Medios?

Causas comunes: importaciones fallidas, subidas manuales vía FTP, comportamiento de plugins antiguos o posts de attachment borrados sin eliminar archivos. La realidad del disco y la realidad de la BD divergen con el tiempo—planifícalo.

7) ¿Necesito un plugin para encontrar medios no usados?

No estrictamente. Puedes construir un proceso sólido con WP-CLI, consultas BD y logs. Los plugins pueden ayudar, pero añaden supuestos—especialmente con builders y campos personalizados. Valida antes de confiar.

8) ¿Qué monitorización debo usar durante la cuarentena?

Controla la tasa de 404 para las rutas de uploads, las top URLs faltantes y cualquier pico de fetch al origen en el CDN. También vigila chequeos sintéticos de usuario en páginas clave que incluyen muchas imágenes.

9) ¿Cuánto debo mantener la cuarentena antes de borrar?

El tiempo suficiente para cubrir tu ciclo de tráfico. Para sitios B2B, 2–4 semanas suele ser más seguro que 2–4 días. Para sitios consumidores de alto tráfico, puedes ver suficiente señal en 48–72 horas, pero los embeds de larga cola aún existen.

10) ¿Y si legal/compliance requiere borrar activos?

Entonces tu restricción de “no romper URLs” cambia: quizá necesites que las URLs devuelvan 410 Gone o un activo de reemplazo. Hazlo deliberadamente: registra, documenta y evita 404s silenciosos.

Conclusión: qué hacer la próxima semana

La limpieza de medios que no rompe URLs es un ejercicio de confiabilidad con sombrero de gestión de contenido. No “limpias una biblioteca”. Gestionas una API pública de activos con años de consumidores, la mayoría de los cuales nunca presentarán un ticket.

Pasos prácticos:

  1. Ejecuta las tareas base: disco, inodos, años top, URLs faltantes principales, verificación de offload.
  2. Elige un año/mes antiguo como piloto. Ponlo en cuarentena, no lo borres.
  3. Añade una red de seguridad temporal (fallback del servidor) si puedes, y vigila 404s de uploads diariamente.
  4. Mueve de vuelta las excepciones, luego borra la cuarentena solo cuando la ventana de monitorización esté tranquila.
  5. Evita que vuelva a inflarse: reduce tamaños de imagen, aplica políticas de subida y agenda auditorías trimestrales.

Si lo haces así, la limpieza será casi decepcionantemente calmada. Ese es el punto. Los sistemas en producción recompensan a las personas maduras.

← Anterior
Cortes de Wi‑Fi cada 10 minutos: la opción avanzada del controlador que lo soluciona
Siguiente →
Copias lentas al NAS: los ajustes SMB que realmente importan

Deja un comentario