Nada sube más la tensión que abrir la Biblioteca de Medios de WordPress y ver… una cuadrícula en blanco. No “unas miniaturas faltantes”. No “algunas imágenes rotas”. Simplemente vacío. Mientras tanto, usted sabe que los archivos existen en disco, o en S3, o en algún lugar que su factura indica.
Esta falla suele ser aburrida. También suele ser solucionable: si deja de adivinar y empieza a verificar las URLs exactas que WordPress está generando, las rutas exactas donde cree que están los “uploads”, y si su servidor web puede realmente servir esos bytes.
Guía rápida de diagnóstico
Si está de guardia, no tiene tiempo para una discusión filosófica sobre diseño de CMS. Haga esto en orden. Cada paso reduce el espacio de búsqueda y evita “arreglos” que mutan datos de producción a ciegas.
Primero: ¿es una ilusión de la interfaz o WordPress realmente no encuentra adjuntos?
- Compruebe el recuento de adjuntos en la base de datos. Si la tabla posts aún tiene adjuntos, la biblioteca no está “vacía”; está fallando al renderizar miniaturas o las consultas están filtradas.
- Compruebe un registro de adjunto. Confirme la ruta almacenada en
_wp_attached_filey la URL del sitio que se usa para construir la URL final.
Segundo: ¿se están generando mal las URLs?
- Confirme
homeysiteurl. Una discrepancia (http vs https, www vs sin www, dominio equivocado) rompe las URLs de medios y las llamadas Ajax del admin de formas creativas. - Compruebe
upload_pathyupload_url_path. Estas opciones pueden sobrescribir los valores por defecto de WordPress y sobrevivir migraciones cual herencia maldita.
Tercero: ¿puede el servidor web servir los archivos en esas URLs?
- Pruebe un archivo real con
curl -I. Si obtiene 403/404/500, está depurando Nginx/Apache/CDN/almacenamiento, no WordPress. - Compruebe permisos de sistema de archivos y restricciones SELinux/AppArmor. “Pero es 755” no es una frase completa.
Cuarto: ¿está descargando medios (S3/almacenamiento en la nube/CDN) y el plugin está mintiendo?
- Identifique plugins de offload y sus ajustes. Muchos reescriben URLs dinámicamente; algunos almacenan URLs canónicas en postmeta.
- Compruebe si el bucket de offload realmente tiene los objetos. WordPress puede estar correcto y el almacenamiento aún vacío.
Sólo después de esto considere un search/replace masivo en la base de datos. Eso es una motosierra. Úsela, pero no la haga malabarismo.
Cómo funciona realmente la Biblioteca de Medios (y cómo le engaña)
La Biblioteca de Medios de WordPress no es un explorador de archivos. Es una vista de la base de datos. Específicamente: es una lista de posts donde post_type = 'attachment', más metadatos que le dicen a WordPress dónde debería estar el archivo relativo a una base de uploads.
Cuando sube una imagen, WordPress normalmente almacena:
- Un post de adjunto en
wp_postscon un título, tipo MIME y un GUID (históricamente usado como “identificador único”, con frecuencia mal usado como campo URL). - La ruta relativa del archivo en
wp_postmetacon la clave meta_wp_attached_file(ejemplo:2025/12/photo.jpg). - Metadatos de adjunto opcionales en
_wp_attachment_metadata(tamaños, dimensiones, miniaturas).
Luego WordPress genera una URL pública combinando una URL base (usualmente derivada de home/siteurl más /wp-content/uploads) con la ruta relativa de _wp_attached_file. Si cualquiera de esas entradas es incorrecta, termina con registros válidos en la BD que apuntan a URLs muertas.
Dos consecuencias importantes en producción:
- Sus archivos pueden existir y aún así “no mostrarse”. Una URL base equivocada produce 404; la cuadrícula de la Biblioteca de Medios parece vacía porque las miniaturas nunca se cargan.
- Su base de datos puede estar correcta y aun así “no mostrarse”. Si el servidor web no puede leer
wp-content/uploads, obtendrá 403 y miniaturas rotas. WordPress mostrará diligentemente nada y esperará a que usted descubra que existe un problema a nivel OS.
Una cita para mantener la honestidad cuando le tiente “simplemente reiniciar la cosa”: La esperanza no es una estrategia.
—Chris Snook
Broma corta #1: Resolver WordPress es como hacer de detective, excepto que el sospechoso siempre es DNS y tiene coartada.
Datos interesantes y contexto (para que deje de sorprenderse)
- Los adjuntos de WordPress son posts. Los elementos multimedia viven en
wp_postscomopost_type=attachment; por esto la corrupción de la BD o el filtrado puede “borrar” una biblioteca sin tocar el disco. - El campo GUID está sobrecargado históricamente. WordPress originalmente usó GUID como un ID único (piénselo como RSS). Muchos temas/plugins lo tratan como la URL del archivo, lo que convierte las migraciones en un desastre a cámara lenta.
- La ruta de uploads se volvió configurable temprano—y se quedó pegajosa. Las opciones
upload_pathyupload_url_pathpueden persistir durante actualizaciones y migraciones, incluso si ya no coinciden con la realidad. - Las carpetas por año/mes fueron una elección de rendimiento y organización. La estructura por defecto
uploads/2025/12/reduce la sobrecarga de directorios; desactivarla puede crear millones de archivos en un solo directorio y poner de mal humor a los sistemas de archivos. - Regenerar miniaturas se volvió “algo necesario” porque los metadatos envejecen mal. Cambie de tema, tamaños o procesamiento de imágenes y su
_wp_attachment_metadatapuede no coincidir con lo que hay en disco. - Los plugins de offload cambiaron el dominio de fallo. Una vez que reescribe URLs a S3/CDN, su servidor WordPress puede estar saludable mientras su bucket de almacenamiento niega todo en silencio.
- Fallas en admin-ajax y REST pueden parecer “biblioteca vacía”. La cuadrícula carga datos vía llamadas JS; CSP, contenido mixto o endpoints bloqueados pueden causar una interfaz en blanco sin ningún problema en la BD.
- Los CDN pueden cachear sus errores. Un breve periodo de URLs equivocadas puede quedar cacheado como 404/403, y entonces “arreglar WordPress” no arregla la experiencia del usuario hasta que purgue.
Los modos reales de fallo: URLs en la BD, rutas, reescrituras, almacenamiento
1) La URL del sitio es incorrecta (o inconsistente)
Clásico: migró de staging a producción, activó HTTPS, añadió www, o se movió detrás de un proxy inverso. WordPress almacena URLs en la base de datos, pero también las deriva en tiempo de ejecución. Si home y siteurl no coinciden con la realidad que ven los usuarios, obtiene contenido mixto, URLs de medios equivocadas y pantallas de administración que se comportan como una máquina expendedora embrujada.
Cómo se ve el “vacío” aquí:
- La Biblioteca de Medios carga pero las miniaturas están en blanco (la red muestra 404/301 loops/contenido mixto bloqueado).
- Al hacer clic en un adjunto aparece un icono de imagen rota.
- La consola del navegador muestra peticiones bloqueadas, errores CORS o redirecciones repetidas.
2) Las sobreescrituras upload_path / upload_url_path le envenenan
La mayoría de sitios no configuran esto. Los que lo hacen a menudo lo olvidan. Tras una migración, WordPress puede seguir pensando que los uploads viven en /var/www/oldsite/uploads o que las URLs públicas comienzan con algún dominio retirado. Los archivos pueden estar correctamente situados en wp-content/uploads, pero WordPress busca en otro sitio.
3) Los registros en la base de datos están presentes, pero los archivos faltan (o viceversa)
Las copias de seguridad y restauraciones son excelentes restaurando un lado de este par. La gente restaura la base de datos pero no wp-content/uploads. O sincronizan uploads pero no la base de datos. De cualquier manera, la Biblioteca de Medios se convierte en un museo de referencias rotas o en un montón de archivos no referenciados que usted paga por almacenar.
4) El ruteo del servidor web bloquea /wp-content/uploads
Reglas Nginx demasiado estrictas, un .htaccess de Apache con deny, un plugin de seguridad que lanza 403, o una regla WAF que piensa que los JPG son sospechosos. Si no puede obtener un archivo conocido con curl, no tiene un problema de WordPress. Tiene un problema de entrega.
5) Permisos, propiedad, SELinux/AppArmor
Uploads legibles por root pero no por el usuario del servidor web. O legibles, pero el recorrido bloqueado por bits de ejecución en directorios. O el etiquetado SELinux que niega a httpd leer la ruta. Estos problemas se manifiestan como 403s (a veces 404s si el servidor oculta fallas de autorización).
6) Offload / CDN / almacenamiento de objetos mal configurado
Los plugins de offload o bien:
- reescriben URLs sobre la marcha (así la BD parece “bien” pero las peticiones van a S3/CDN), o
- almacenan URLs de la nube en postmeta / GUID / tablas personalizadas.
Fallo común: credenciales rotadas, política del bucket cambiada, endpoint de región modificado, u objetos privados servidos sin URLs firmadas. El admin de WordPress no muestra nada porque las miniaturas se buscan en el CDN y fallan.
7) La UI de la Biblioteca de Medios falla, no los medios
Si la consulta de lista de adjuntos está bien pero las peticiones JavaScript fallan, la cuadrícula puede renderizarse en blanco. Las causas incluyen cabeceras CSP, bloqueo de admin-ajax.php, problemas de autenticación en la API REST, o un plugin que rompe el admin con un error fatal sólo en la pantalla de medios.
Broma corta #2: La Biblioteca de Medios no desapareció; simplemente se mudó a un nuevo dominio sin avisar a Finanzas.
Tareas prácticas: comandos, salidas y decisiones
Estas son las tareas que realmente ejecuto cuando alguien escribe “biblioteca de medios vacía” en el chat. Cada una incluye (1) un comando, (2) una salida de ejemplo, y (3) qué decisión toma a partir de ello.
Task 1: Count attachments in the database
cr0x@server:~$ mysql -N -e "SELECT COUNT(*) FROM wp_posts WHERE post_type='attachment';"
12487
Qué significa: Hay adjuntos en la BD. Si la UI está vacía, probablemente esté tratando con generación de URLs, peticiones de miniaturas, fallos JS en el admin, o un filtro en la consulta.
Decisión: No restaure copias aún. Pase a la verificación de URL/ruta y comprobaciones de navegador/red.
Task 2: Fetch a sample attachment meta (file path)
cr0x@server:~$ mysql -N -e "SELECT p.ID, p.post_title, pm.meta_value FROM wp_posts p JOIN wp_postmeta pm ON pm.post_id=p.ID WHERE p.post_type='attachment' AND pm.meta_key='_wp_attached_file' ORDER BY p.ID DESC LIMIT 1;"
88211 hero-banner 2025/12/hero-banner.jpg
Qué significa: WordPress espera el archivo en uploads/2025/12/hero-banner.jpg bajo lo que considera la base de uploads.
Decisión: Valide que el archivo exista en disco o en el almacenamiento offload, y valide la URL pública construida para él.
Task 3: Check home and siteurl options
cr0x@server:~$ mysql -N -e "SELECT option_name, option_value FROM wp_options WHERE option_name IN ('home','siteurl');"
home https://www.example.com
siteurl https://example.com
Qué significa: Discrepancia. A veces es intencional (WordPress en un subdirectorio), con frecuencia accidental. Puede romper llamadas de admin y generación de URLs de medios.
Decisión: Decida cuál es el dominio canónico (con o sin www, https). Alinee estos valores, o implemente cabeceras proxy y reglas de reescritura si hay una separación deliberada.
Task 4: Check uploads overrides in wp_options
cr0x@server:~$ mysql -N -e "SELECT option_name, option_value FROM wp_options WHERE option_name IN ('upload_path','upload_url_path','uploads_use_yearmonth_folders');"
upload_path /var/www/legacy/uploads
upload_url_path https://legacy.example.net/uploads
uploads_use_yearmonth_folders 1
Qué significa: WordPress está configurado explícitamente para usar una ubicación y URL de filesystem legadas. Ahí tiene su “biblioteca vacía”.
Decisión: Elimine estas sobreescrituras (déjelas vacías) o actualícelas a la ruta/URL correcta. No lo haga a ciegas: verifique primero la ubicación real de los medios.
Task 5: Determine uploads directory from WordPress config (WP-CLI)
cr0x@server:~$ cd /var/www/html
cr0x@server:~$ wp option get upload_path
/var/www/legacy/uploads
Qué significa: Confirma la opción en la BD del Task 4 usando WordPress mismo (útil cuando los prefijos de tabla difieren).
Decisión: Si upload_path es incorrecto, arréglelo y vuelva a probar con la obtención de una imagen antes de operaciones masivas.
Task 6: Verify a known file exists on disk
cr0x@server:~$ ls -lah /var/www/html/wp-content/uploads/2025/12/hero-banner.jpg
-rw-r--r-- 1 www-data www-data 482K Dec 26 09:13 /var/www/html/wp-content/uploads/2025/12/hero-banner.jpg
Qué significa: El archivo existe, legible, propiedad del usuario del servidor web (o al menos legible por el grupo). Bien.
Decisión: Si el archivo existe pero la biblioteca está en blanco, pase a la comprobación HTTP y reglas del servidor web. Si no existe, necesita restaurar uploads o reconciliar almacenamiento offload.
Task 7: Check directory permissions up the tree (the “execute bit” trap)
cr0x@server:~$ namei -l /var/www/html/wp-content/uploads/2025/12/hero-banner.jpg
f: /var/www/html/wp-content/uploads/2025/12/hero-banner.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--- root root uploads
drwxr-xr-x www-data www-data 2025
drwxr-xr-x www-data www-data 12
-rw-r--r-- www-data www-data hero-banner.jpg
Qué significa: El directorio uploads es drwxr-x--- propiedad de root, así que www-data puede no poder atravesarlo según el usuario/grupo del servidor. Ahí tiene un 403 esperando.
Decisión: Arregle la propiedad/permisos en uploads para que el servidor web pueda atravesar y leer. No haga chmod 777; así es como se conoce a un encuentro profesional con ransomware.
Task 8: If SELinux exists, check enforcement and recent denials
cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ sudo ausearch -m avc -ts recent | tail -n 5
type=AVC msg=audit(1735142651.812:912): avc: denied { read } for pid=2143 comm="nginx" name="hero-banner.jpg" dev="sda1" ino=550912 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0
Qué significa: SELinux está bloqueando lecturas porque el contexto del archivo es erróneo (default_t en lugar de un tipo legible por web).
Decisión: Relabel los uploads con el contexto correcto y hágalo persistente. De lo contrario “lo arreglará” hasta la siguiente restauración.
Task 9: Probe the media URL via HTTP (bypass the browser)
cr0x@server:~$ curl -I https://www.example.com/wp-content/uploads/2025/12/hero-banner.jpg
HTTP/2 403
date: Sat, 27 Dec 2025 10:12:15 GMT
content-type: text/html
server: nginx
Qué significa: 403 en el borde. Eso no es “WordPress olvidó la imagen.” Es política, autenticación, WAF, permisos o una regla que niega /wp-content.
Decisión: Inspeccione la configuración de Nginx/Apache y las capas de seguridad. No toque la BD hasta que HTTP sirva archivos conocidos.
Task 10: Check Nginx config for accidental denies on uploads
cr0x@server:~$ sudo nginx -T 2>/dev/null | grep -nE "wp-content|uploads|deny|location"
128: location ~* /(wp-content|wp-includes)/.*\.php$ { deny all; }
141: location ~* ^/wp-content/uploads/ { deny all; }
Qué significa: Alguien bloqueó uploads. A menudo la intención era bloquear ejecución de PHP, pero implementaron “deny all” en lugar de “deny PHP”.
Decisión: Cambie las reglas para permitir archivos estáticos mientras se deniega contenido ejecutable. Vuelva a probar con curl -I.
Task 11: Check Apache .htaccess or vhost rules affecting uploads
cr0x@server:~$ sudo grep -RIn "uploads\|wp-content" /var/www/html/.htaccess /etc/apache2/sites-enabled 2>/dev/null | head
/var/www/html/.htaccess:12:RewriteRule ^wp-content/uploads/ - [F,L]
Qué significa: Una regla de reescritura prohíbe el acceso a uploads (devuelve 403). Esto es o bien un malentendido o un snippet de seguridad copiado y pegado aplicado demasiado ampliamente.
Decisión: Arregle la regla para bloquear sólo patrones peligrosos (como PHP en uploads), no todo el directorio.
Task 12: Check whether requests are being redirected to the wrong host
cr0x@server:~$ curl -I http://www.example.com/wp-content/uploads/2025/12/hero-banner.jpg
HTTP/1.1 301 Moved Permanently
Location: https://example.com/wp-content/uploads/2025/12/hero-banner.jpg
Qué significa: El host canónico es diferente. Si WordPress piensa “www” pero su redirección fuerza sin-www (o viceversa), puede obtener contenido mixto o llamadas de admin bloqueadas según cómo funcionen cookies y CORS.
Decisión: Elija un host canónico y alinee las opciones de WordPress, redirecciones y la configuración del CDN con él.
Task 13: Confirm attachment GUID patterns (spot stale domains)
cr0x@server:~$ mysql -N -e "SELECT COUNT(*) FROM wp_posts WHERE post_type='attachment' AND guid LIKE '%legacy.example.net%';"
12487
Qué significa: Cada adjunto aún tiene un dominio legado en el GUID. Esto puede o no importar, dependiendo del comportamiento de temas/plugins.
Decisión: Si su front-end o plugin usa GUID para URLs, necesita una actualización controlada. Si no, podría actualizarlo por consistencia, pero hágalo con cuidado y pruebe feeds/integraciones.
Task 14: Search for old domains in postmeta (actual file URL overrides)
cr0x@server:~$ mysql -N -e "SELECT meta_key, COUNT(*) FROM wp_postmeta WHERE meta_value LIKE '%legacy.example.net%' GROUP BY meta_key ORDER BY COUNT(*) DESC LIMIT 10;"
_wp_attached_file 0
amazonS3_info 12487
Qué significa: Metadatos de offload hacen referencia a la configuración de dominio/bucket legada. Eso suele ser el verdadero motor de miniaturas rotas al usar plugins de offload.
Decisión: Arregle primero la configuración del plugin de offload; luego considere herramientas de migración o rehidratación de metadatos específicas para ese plugin.
Task 15: Validate WP-CLI can list attachments (bypass the admin UI)
cr0x@server:~$ wp post list --post_type=attachment --fields=ID,post_title,guid --format=table | head
+------+------------------+--------------------------------------------------------------+
| ID | post_title | guid |
+------+------------------+--------------------------------------------------------------+
| 88199| hero-banner | https://legacy.example.net/wp-content/uploads/2025/12/hero... |
| 88185| team-headshot | https://legacy.example.net/wp-content/uploads/2025/12/team... |
+------+------------------+--------------------------------------------------------------+
Qué significa: WordPress ve los adjuntos bien a nivel de aplicación.
Decisión: Si WP-CLI lista adjuntos pero la UI está vacía, enfoque en JS del admin, llamadas API y fallos de fetch de miniaturas, no en “filas faltantes en BD”.
Task 16: Check admin Ajax/REST endpoints quickly (server-side)
cr0x@server:~$ curl -I https://www.example.com/wp-admin/admin-ajax.php
HTTP/2 302
location: https://www.example.com/wp-login.php?redirect_to=https%3A%2F%2Fwww.example.com%2Fwp-admin%2Fadmin-ajax.php
Qué significa: El endpoint es accesible. Un 302 al login es normal sin autenticación. Si obtiene 403/500 aquí, la UI de la Biblioteca puede romperse.
Decisión: Si los endpoints de admin fallan, compruebe reglas WAF, mod_security, capas de caché y errores PHP antes de tocar URLs de medios.
Task 17: Find PHP errors tied to media requests (quick grep)
cr0x@server:~$ sudo tail -n 80 /var/log/php8.2-fpm.log | grep -iE "fatal|wp-admin|media|imagick|gd" | tail -n 10
[27-Dec-2025 10:11:05] PHP Fatal error: Uncaught Error: Call to undefined function imagewebp() in /var/www/html/wp-includes/media.php:4212
Qué significa: Falta una función de la librería de imágenes (aquí soporte WebP) que puede romper la generación de miniaturas u operaciones de metadatos, a veces cascada hacia rarezas en la UI.
Decisión: Arregle la extensión/biblioteca PHP faltante o desactive la función/plugin que la usa; luego regenere miniaturas si es necesario.
Task 18: Controlled DB search/replace for domain changes (dry run)
cr0x@server:~$ wp search-replace 'https://legacy.example.net' 'https://www.example.com' --dry-run --all-tables
Success: Made 0 replacements.
Qué significa: WP-CLI no encontró coincidencias (o no está escaneando las tablas/prefijo correcto). Alternativamente, el dominio antiguo está almacenado sin esquema, o en arrays serializados en otra forma.
Decisión: Reenfoque la búsqueda (http vs https, con/sin www), o apunte tablas específicas. Cuando lo ejecute de verdad, haga primero un snapshot de la BD.
Task 19: Check for mixed-content patterns (http media on https site)
cr0x@server:~$ mysql -N -e "SELECT COUNT(*) FROM wp_posts WHERE post_type='attachment' AND guid LIKE 'http://%';"
12487
Qué significa: Los adjuntos aún referencian http. Los navegadores a menudo bloquean o advierten, y las miniaturas del admin pueden fallar según las políticas.
Decisión: Planifique una migración cuidadosa a URLs https (o asegúrese de que WordPress genere https independientemente del uso de GUID).
Tres micro-historias corporativas desde el frente
Micro-historia 1: El incidente causado por una suposición equivocada
Una organización mediana migró un estate de WordPress desde una VM legada a contenedores. El plan de migración parecía limpio: volcado/restore de la base de datos, rsync de wp-content, actualizar DNS, listo. La Biblioteca de Medios quedó en blanco justo después del corte.
El equipo supuso “biblioteca vacía = uploads faltantes”. Así que relanzaron rsync, dos veces, en horas laborales. Luego empezaron a restaurar backups más antiguos porque seguro que la copia más reciente estaba corrupta. Quemaron un día y generaron un bonito backlog de estados inconsistentes: algunas páginas referenciaban medios que existían sólo en la primera restauración, otras en la segunda.
La causa real fue mundana: upload_url_path en wp_options aún apuntaba al dominio antiguo. WordPress generaba URLs de miniaturas a un hostname que ahora devolvía un 301 a un endpoint sólo interno. Desde dentro de la VPN de la oficina “funcionaba”, desde Internet no. La UI del admin parecía vacía porque cada petición de miniatura era redirigida a un lugar que el navegador no seguiría debido a cookies mixtas y CORS bloqueado.
Arreglarlo fue anticlimático: borrar la opción, alinear home/siteurl, purgar el CDN, y de repente la biblioteca “reapareció”. Los archivos habían estado allí todo el tiempo. El incidente vino de una suposición equivocada: que la Biblioteca de Medios es una vista directa del filesystem. No lo es.
Micro-historia 2: La optimización que salió mal
Un equipo de marketing se quejó de que las subidas de imágenes eran “lentas”, así que un ingeniero introdujo un plugin de offload a almacenamiento de objetos y un CDN por delante. En papel: menos escrituras de disco, menor carga en el origen, páginas más rápidas. En la práctica: una nueva dependencia de producción con un diámetro explosivo muy emocionante.
Unas semanas después, la Biblioteca de Medios empezó a mostrar cuadrados vacíos. No iconos rotos; simplemente en blanco, como si nunca hubiera oído de imágenes. Las páginas front-end también perdieron imágenes, pero no de forma consistente. Algunos navegadores funcionaban. Otros no. El problema fue intermitente, que es la manera en que el universo se ríe.
Causa raíz: el CDN cacheó respuestas 403 del almacenamiento de objetos para un subconjunto de claves. Una actualización de política había denegado temporalmente lecturas para objetos sin un prefijo específico. El plugin seguía generando URLs del CDN, y WordPress no tenía idea de que algo iba mal. Las miniaturas del admin venían del CDN, que servía fielmente 403s cacheados.
La “optimización” había movido la disponibilidad de medios de “¿puede el origen servir archivos?” a “¿está correcta la política del CDN?, ¿está bien la política del bucket?, ¿son válidas las credenciales?, ¿está reescribiendo bien URLs el plugin?, ¿purgué caches después de cambios de política?” Lo arreglaron corrigiendo la política y purgando el CDN, pero la lección real fue gobernanza: trate el offload de medios como un cambio de plataforma, no como una instalación rápida de plugin.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Otra compañía tenía un ritual: cada migración requería dos verificaciones antes de declarar éxito. Primero, confirmar el recuento de adjuntos y una muestra de entradas _wp_attached_file. Segundo, obtener diez imágenes aleatorias con curl -I desde fuera de la red (un sondeo externo real, no “funciona en mi portátil”). A nadie le encantaba esa lista, por eso funcionaba.
Durante una mudanza de datacenter, la Biblioteca de Medios lucía vacía en el admin para un subconjunto de usuarios. Los ingenieros no entraron en pánico. Ejecutaron las comprobaciones rutinarias. Los recuentos en la BD estaban bien. La existencia de archivos en disco estaba bien. Las obtenciones HTTP desde fuera devolvían 403—pero sólo para peticiones que faltaban cierta cabecera. Eso lo acotó a la capa edge.
Resultó que el nuevo perfil WAF bloqueaba peticiones a /wp-content/uploads/ a menos que tuvieran un User-Agent parecido a navegador. El proveedor del WAF lo llamó “protección contra bots”. La compañía lo llamó “romper el sitio web”.
Porque tenían el hábito de hacer las mismas pruebas cada vez, tenían evidencia limpia: “el origen puede leer archivos, WordPress genera la ruta correcta, el edge devuelve 403”. Arreglaron la regla rápidamente y evitaron el error clásico de reescribir URLs en la BD para perseguir un problema que no era de base de datos.
Errores comunes: síntoma → causa raíz → solución
-
Síntoma: La cuadrícula de la Biblioteca de Medios está en blanco; el recuento de adjuntos aparece como cero en la UI.
Causa raíz: Peticiones JS del admin fallando (REST/Ajax bloqueado por WAF, CSP, plugin de caché, o un error fatal en la pantalla de medios).
Solución: Compruebe las herramientas de desarrollo del navegador (network/console), luego valide
admin-ajax.phpy endpoints REST. Arregle la regla del servidor/WAF o el conflicto de plugins antes de tocar rutas de uploads. -
Síntoma: Los adjuntos existen en la BD, pero las miniaturas están rotas y devuelven 404.
Causa raíz:
home/siteurlequivocados, o dominio antiguo incrustado en overrides de URL.Solución: Alinee
homeysiteurlcon el dominio canónico. Borre o corrijaupload_url_path. Purge cachés de CDN luego. -
Síntoma: Las miniaturas devuelven 403 desde
/wp-content/uploads.Causa raíz: Reglas de denegación del servidor web, permisos de recorrido de filesystem, o contexto SELinux bloqueando lecturas.
Solución: Ajuste reglas de Nginx/Apache para permitir archivos estáticos; corrija propiedad/permisos; relabel contexts SELinux.
-
Síntoma: Los archivos existen en disco pero WordPress sube nuevos medios a un directorio inesperado.
Causa raíz: Override de
upload_pathapunta a otro lado, o el mapeo de volúmenes en contenedores es incorrecto.Solución: Elimine el override para que WordPress use
wp-content/uploads, o arregle la ruta de montaje. Confirme con una subida de prueba. -
Síntoma: Los medios se muestran en el admin pero las imágenes están rotas sólo en el frontend.
Causa raíz: CDN cacheando 404s antiguos, problemas de contenido mixto http/https, o tema que construye URLs desde GUID diferente al admin.
Solución: Purge CDN, aplique https forzado, y audite tema/plugins por uso de GUID. Arregle la generación de URLs canónicas.
-
Síntoma: Tras una migración, algunas imágenes funcionan y las más nuevas no.
Causa raíz: Restauración parcial del directorio uploads; subárboles año/mes faltantes; o sincronización incompleta de almacenamiento de objetos.
Solución: Compare árboles del filesystem, restaure directorios faltantes, o vuelva a ejecutar la sincronización de almacenamiento de objetos con verificación.
-
Síntoma: Al hacer clic en un adjunto se muestran metadatos pero no vista previa; redimensionar falla.
Causa raíz: Faltan funcionalidades GD/Imagick o errores fatales PHP en el procesamiento de medios.
Solución: Instale/habilite extensiones PHP y librerías necesarias; luego regenere miniaturas si los metadatos están obsoletos.
-
Síntoma: La Biblioteca de Medios muestra items pero la búsqueda/filtrado no devuelve nada.
Causa raíz: Plugin que altera consultas (hooks) o problemas de colación/índices en la BD que causan consultas lentas/fallidas.
Solución: Desactive plugins sospechosos, revise los logs lentos de MySQL y errores, repare tablas/índices si hace falta.
Listas de verificación / plan paso a paso
Checklist A: “La Biblioteca de Medios parece vacía” en producción
- Confirme que la BD tiene adjuntos. Si es cero, tiene pérdida de datos o una conexión a base de datos/prefijo de tabla incorrecto.
- Compruebe el
_wp_attached_filede un adjunto. Valide que sea sensato (ruta relativa, no alguna ruta absoluta legada extraña salvo que sea intencional). - Confirme
homeysiteurl. Decida su dominio y esquema canónicos. - Compruebe
upload_pathyupload_url_path. Borre overrides legados salvo que tenga una razón para usarlos. - Obtenga un archivo conocido con
curl -I. Observe 200 vs 301 vs 403 vs 404. - Verifique existencia en filesystem y permisos de recorrido. Use
namei -lpara toda la ruta. - Compruebe SELinux/AppArmor si aplica. Modo Enforcing más denegaciones AVC es igual a miseria silenciosa.
- Revise reglas del servidor web. Asegúrese de bloquear PHP en uploads, no todo el directorio de uploads.
- Compruebe CDN/WAF. Busque 403/404 cacheados y bloqueos por ruta.
- Sólo entonces haga search/replace en la BD. Tome una snapshot primero, haga dry runs y valide arrays serializados vía WP-CLI.
Checklist B: Remediación segura de URL/ruta sin empeorar
- Backup de la base de datos. Volcado lógico más snapshot si está disponible. Si no puede restaurar, no puede “arreglar”.
- Decida la URL canónica. https vs http, www vs sin-www, y si WordPress vive en un subdirectorio.
- Arregle
home/siteurlprimero. Influye en todo lo demás. - Borre overrides de uploads salvo que sean necesarios. Deje que WordPress use
wp-content/uploadssalvo que tenga una razón. - Valide un adjunto de extremo a extremo. Registro BD → filesystem/objeto → HTTP 200.
- Ejecute
wp search-replacecon--dry-run. Confirme el alcance y la selección de tablas. - Ejecute el reemplazo real en horas de baja. Mida duración, impacto de bloqueos, y tenga rollback listo.
- Purgue cachés del CDN. Si no, su “arreglo” es correcto pero invisible.
- Regenerar miniaturas si cambió tamaños. Sólo si confirmó que los originales son accesibles.
Checklist C: Endurecimiento para que esto no vuelva a pasar
- Monitoree HTTP para objetos de medios de muestra. Un chequeo sintético que fetchée algunos uploads conocidos detecta 403/404 temprano.
- Rastree recuento de adjuntos y tamaño del árbol de uploads. Grandes divergencias indican restauraciones parciales o pipelines rotos.
- Documente si usa offload/CDN. Trátelo como infraestructura crítica con control de cambios.
- Estandarice manejo de dominio canónico. Cabeceras proxy, redirecciones y ajustes de WordPress deben coincidir.
- Prohíba search/replace SQL ad-hoc en producción. Use WP-CLI para seguridad con serialización y trazabilidad.
Preguntas frecuentes
¿Por qué la Biblioteca de Medios está vacía pero los posts aún muestran imágenes?
A menudo el front-end está renderizando HTML cacheado o usa otra fuente de URLs (tema que codifica rutas, URLs del CDN), mientras que la cuadrícula del admin depende de llamadas JS y endpoints de miniaturas que están bloqueados.
¿Es seguro actualizar home y siteurl directamente en la base de datos?
Sí, si conoce la URL canónica correcta y tiene rollback. Hágalo deliberadamente; las discrepancias pueden romper cookies de login y acceso al admin. Prefiera WP-CLI cuando sea posible para comportamiento consistente.
¿Cuál es la diferencia entre home y siteurl?
home es la dirección pública del sitio. siteurl es donde residen los archivos core de WordPress. A menudo son iguales, excepto en instalaciones en subdirectorios o configuraciones inusuales. Las diferencias accidentales causan rarezas.
¿Debería “arreglar” los GUID de los adjuntos durante una migración?
A veces. WordPress no depende del GUID para las URLs de medios en el camino limpio, pero plugins y temas podrían. Si ve que los GUID se usan en plantillas, o feeds/integraciones dependen de ellos, planifique la actualización con cuidado.
¿Por qué obtengo 403 al solicitar archivos en wp-content/uploads?
O bien la configuración del servidor web deniega esa ruta, los permisos del filesystem bloquean el recorrido/lectura, o SELinux/AppArmor está denegando acceso. No asuma que es un bug de WordPress; demuéstrelo con curl -I y logs.
Después de arreglar URLs, ¿por qué sigo viendo miniaturas faltantes?
Los CDN y los navegadores cachean fallos. Además, las miniaturas pueden no existir en disco si sólo restauró los originales o cambió tamaños de imagen. Purge caches y luego regenere miniaturas si los originales son accesibles.
¿Cómo puedo saber si hay un plugin de offload involucrado?
Busque ajustes del plugin relacionados con S3/almacenamiento de objetos, revise postmeta por claves como amazonS3_info, e inspeccione las URLs de miniaturas en el panel de red del navegador. Si apuntan a dominio CDN/bucket, está offloading.
¿Puede un proxy inverso causar una “biblioteca de medios vacía”?
Sí. Si WordPress no ve el esquema/host correctos (cabeceras proxy faltantes), puede generar URLs http en un sitio https, provocando bloqueos por contenido mixto o bucles de redirección que impiden la carga de miniaturas.
¿Cuál es el error más común en migraciones?
Restaurar la base de datos sin restaurar wp-content/uploads (o restaurar uploads sin la base de datos). Los medios son un sistema acoplado: metadatos + bytes.
Conclusión: siguientes pasos que realmente reducen el riesgo
Si su Biblioteca de Medios de WordPress parece vacía, resista la tentación de “reinstalar WordPress” o lanzar consultas de búsqueda/reemplazo aleatorias en producción. La solución suele ser una discrepancia entre lo que WordPress cree que es la URL/ruta de uploads y lo que su infraestructura realmente sirve.
Haga esto a continuación:
- Pruebe que los adjuntos existen en la BD y obtenga una muestra de
_wp_attached_file. - Alinee
home/siteurly elimine o corrijaupload_path/upload_url_path. - Obtenga un upload conocido con
curl -Iy arregle edge/servidor web/permisos hasta obtener un 200 limpio. - Si hay offloading, valide permisos de bucket/CDN y purgue 403/404 cacheados.
- Sólo entonces ejecute search/replace controlado con WP-CLI y (si hace falta) regenere miniaturas.
Hágalo aburrido: añada una pequeña comprobación sintética que obtenga un puñado de uploads conocidos. El mejor momento para descubrir que sus uploads están bloqueados es: nunca.