Haces clic en Add New en la Biblioteca de Medios. El icono de carga gira. Luego WordPress encoge de hombros: “HTTP error”, “Unable to create directory”, o falla en silencio donde no sucede nada pero tu tensión sube.
Este es uno de esos problemas que parece “WordPress siendo WordPress” hasta que recuerdas que en realidad es una pila: navegador → CDN/WAF → servidor web → PHP-FPM → sistema de archivos → librerías de imagen → base de datos. El truco es encontrar la primera capa que te está engañando.
Guía de diagnóstico rápido (hacer en este orden)
1) Confirma el error exacto en el borde y en el origen
- Pestaña Network de las herramientas de desarrollador del navegador: busca 413, 403, 500, 502, 504.
- Logs del origen: registros de Nginx/Apache + PHP-FPM en la misma marca temporal.
- El mensaje de WordPress suele ser vago. Tus logs no lo son.
2) Demuestra que el directorio de subidas es escribible por el usuario en tiempo de ejecución
- Comprueba la propiedad y el modo de
wp-content/uploads. - Intenta crear un archivo como el usuario del servidor web. No presumas.
3) Comprueba los límites a lo largo de la cadena (gana el más pequeño)
- PHP:
upload_max_filesize,post_max_size,memory_limit,max_execution_time. - Servidor web: Nginx
client_max_body_size; ApacheLimitRequestBody. - Proxies/CDN/WAF: 413, límites del cuerpo de la petición, reglas de seguridad.
4) Comprueba la salud del directorio temporal
- Permisos de
/tmp, espacio, disponibilidad de inodos, opciones de montaje. - El
upload_tmp_dirde PHP y las rutas de sesión.
5) Comprueba las librerías de procesamiento de imagen
- La falta de Imagick/GD puede convertir subidas válidas en “HTTP error” durante la generación de miniaturas.
- Busca errores fatales en los logs de PHP.
6) Aplicación de seguridad (solo después de lo básico)
- Los denegaciones de SELinux/AppArmor pueden parecer problemas de permisos, pero
chmod 777no arreglará una política. - ModSecurity puede bloquear subidas multipart con reglas genéricas.
Regla general: si las subidas fallan al instante, sospecha de límites/WAF. Si esperan un tiempo y luego fallan, sospecha de procesamiento PHP, espacio temporal, librerías, timeouts.
Qué sucede realmente cuando fallan las subidas
Las subidas en WordPress no son solo “copiar archivo al disco”. Un recorrido típico es el siguiente:
- El navegador envía un POST multipart a
/wp-admin/async-upload.php(o mediante endpoints REST según la versión/ruta del editor). - El borde/CDN/WAF inspecciona el tamaño de la petición y el tipo de contenido, y puede terminarla antes.
- El servidor web acepta el cuerpo de la petición; puede aplicar su propio tamaño máximo y timeouts.
- PHP recibe el archivo en una ubicación temporal (
$_FILES), luego lo mueve awp-content/uploads/YYYY/MM. - WordPress crea metadata del attachment, genera miniaturas y puede ejecutar operaciones de Imagick/GD.
- La base de datos recibe filas para el post de attachment + metadata; el sistema de archivos recibe el original y los derivados.
Son muchas piezas en movimiento. Cuando una falla, WordPress a menudo lo reporta como una galleta de la fortuna: vago pero técnicamente no equivocado.
Dos observaciones prácticas:
- Si el archivo nunca llega a uploads, busca en etapas anteriores: límites, permisos, temp, WAF.
- Si el original llega pero las miniaturas no, mira Imagick/GD, límites de memoria o timeouts.
Una idea parafraseada (mentalidad de fiabilidad): la “efectividad inusitada” de Eugene Wigner aplica también a operaciones: la instrumentación hace que sistemas desordenados sean de repente comprensibles (idea parafraseada).
Broma #1: El mensaje de error de la Biblioteca de Medios es como una alerta de pager que dice “algo pasó”. Gracias, WordPress. Muy accionable.
Datos y contexto interesantes (lo que explica las rarezas)
- La tubería de subida de PHP es clásica: las subidas se almacenan primero en un archivo temporal y luego se mueven. El espacio temporal es una dependencia real, no un detalle de implementación.
- “HTTP error” se volvió un cajón de sastre: históricamente, WordPress a veces mostraba fallos genéricos de subida cuando la excepción real estaba en la generación de miniaturas, no en la capa HTTP.
- Los valores por defecto de Imagick difieren por distribución: los archivos de política de ImageMagick pueden restringir memoria, disco o ciertos formatos; la misma instalación de WordPress puede comportarse distinto entre hosts.
- GD vs Imagick no es solo preferencia: GD puede ser más rápido para operaciones pequeñas; Imagick maneja más formatos y suele dar mejor calidad pero puede ser más pesado y sensible a límites de política.
- El límite de Nginx es explícito: a diferencia de algunos sistemas,
client_max_body_sizede Nginx es una puerta rígida que puede fallar rápido con 413. - Apache puede ocultar el límite en muchos sitios:
LimitRequestBodypuede estar en virtual host, contexto de directorio o config global; “pero yo lo cambié” no es prueba. - Los sistemas de archivos en la nube y contenedores cambian los modos de fallo: los sistemas overlay y discos efímeros hacen que “las subidas funcionaban hasta el redeploy” sea un patrón de incidente sorprendentemente común.
- La orientación EXIF cambió el juego: algunos flujos rotan imágenes durante el procesamiento; la falta de soporte EXIF puede llevar a inconsistencias “funciona en mi portátil”.
Tareas prácticas: comandos, salidas, decisiones
Estas son tareas “haz esto ahora”. Cada una incluye: un comando, una salida realista y la decisión que tomas. Ejecútalas en el host de WordPress (o contenedor) que realmente maneja PHP.
Task 1: Identify the web server and PHP runtime user
cr0x@server:~$ ps -eo user,comm | egrep 'nginx|apache2|httpd|php-fpm' | head
root nginx
www-data nginx
root php-fpm8.2
www-data php-fpm8.2
Qué significa: Nginx y PHP-FPM están corriendo, y el usuario worker es www-data.
Decisión: todas las pruebas de escritura en el sistema de archivos deben realizarse como www-data. Si estás en un host cPanel podrías ver nobody o usuarios por vhost; no presumas.
Task 2: Confirm WordPress path and locate uploads directory
cr0x@server:~$ sudo find /var/www -maxdepth 4 -type f -name wp-config.php 2>/dev/null
/var/www/site/wp-config.php
Qué significa: la raíz de WordPress probablemente es /var/www/site.
Decisión: centra las comprobaciones en /var/www/site/wp-content/uploads a menos que exista una constante UPLOADS personalizada.
Task 3: Check ownership and permissions on uploads
cr0x@server:~$ ls -ld /var/www/site/wp-content/uploads
drwxr-xr-x 12 root root 4096 Dec 27 09:40 /var/www/site/wp-content/uploads
Qué significa: uploads está propiedad de root:root. Es un clásico “funcionó durante el deploy como root, luego falla en producción”.
Decisión: cambia la propiedad al usuario/grupo en tiempo de ejecución (o a un grupo de deploy que lo incluya). Evita 777 como “arreglo”.
Task 4: Prove writability by creating a file as the runtime user
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/site/wp-content/uploads/.writetest && echo ok'
touch: cannot touch '/var/www/site/wp-content/uploads/.writetest': Permission denied
Qué significa: PHP tampoco puede escribir allí.
Decisión: arregla la propiedad/ACLs; no toques aún los límites de PHP.
Task 5: Fix ownership safely (one-liner, then re-test)
cr0x@server:~$ sudo chown -R www-data:www-data /var/www/site/wp-content/uploads
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/site/wp-content/uploads/.writetest && echo ok'
ok
Qué significa: el runtime puede escribir ahora.
Decisión: reintenta la subida en WordPress. Si sigue fallando, pasa a límites/logs.
Task 6: Check disk space (yes, really)
cr0x@server:~$ df -h /var/www/site/wp-content/uploads /tmp
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 40G 39G 220M 99% /
tmpfs 2.0G 1.9G 120M 95% /tmp
Qué significa: tanto el filesystem raíz como /tmp están casi llenos. Las subidas pueden fallar al prepararse o finalizar.
Decisión: libera espacio inmediatamente o amplía volúmenes; también limpia acumulaciones temporales. Si solo arreglas permisos de uploads, seguirá fallando de forma intermitente.
Task 7: Check inode exhaustion (the “disk is full but df lies” cousin)
cr0x@server:~$ df -i /var/www/site/wp-content/uploads /tmp
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/nvme0n1p2 2621440 2621201 239 100% /
tmpfs 524288 523901 387 100% /tmp
Qué significa: te quedaste sin inodos. Tormentas de archivos pequeños (cache, sesiones, temporales) causan esto.
Decisión: limpia directorios de alta rotación (cache/sesión/tmp). A largo plazo: mueve caches fuera del FS raíz, ajusta retención o redimensiona el sistema de archivos con más inodos.
Task 8: Inspect PHP limits actually in effect
cr0x@server:~$ php -i | egrep 'upload_max_filesize|post_max_size|memory_limit|max_execution_time|upload_tmp_dir'
upload_max_filesize => 2M => 2M
post_max_size => 8M => 8M
memory_limit => 128M => 128M
max_execution_time => 30 => 30
upload_tmp_dir => no value => no value
Qué significa: subidas mayores a 2 MB fallarán; además WordPress suele necesitar más memoria para procesar JPEGs grandes, y se usa el directorio temporal por defecto.
Decisión: ajusta upload_max_filesize y post_max_size según tus necesidades, y considera aumentar memory_limit si el procesamiento de imágenes falla.
Task 9: Verify Nginx request size limit
cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep -n 'client_max_body_size'
45: client_max_body_size 2m;
Qué significa: Nginx bloquea cuerpos mayores a 2 MB antes de que PHP los vea.
Decisión: aumenta client_max_body_size en el contexto correcto (http/server/location) y recarga Nginx.
Task 10: Reload Nginx and confirm configuration is valid
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
Qué significa: los cambios están activos sin caer conexiones.
Decisión: reintenta la subida con un archivo ligeramente por encima del límite anterior para confirmar que la puerta se movió.
Task 11: Check PHP-FPM pool config for per-pool overrides
cr0x@server:~$ sudo egrep -R 'php_admin_value\[upload_max_filesize\]|php_admin_value\[post_max_size\]|php_admin_value\[memory_limit\]' /etc/php/8.2/fpm/pool.d
/etc/php/8.2/fpm/pool.d/www.conf:php_admin_value[upload_max_filesize] = 2M
/etc/php/8.2/fpm/pool.d/www.conf:php_admin_value[post_max_size] = 8M
Qué significa: puedes editar php.ini eternamente y nada cambia porque la pool lo sobreescribe.
Decisión: actualiza los ajustes de la pool y reinicia PHP-FPM.
Task 12: Restart PHP-FPM and watch for errors
cr0x@server:~$ sudo systemctl restart php8.2-fpm
cr0x@server:~$ sudo systemctl status php8.2-fpm --no-pager -l | sed -n '1,12p'
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled)
Active: active (running) since Fri 2025-12-27 09:52:21 UTC; 2s ago
Docs: man:php-fpm8.2(8)
Qué significa: el servicio está saludable tras el cambio.
Decisión: si no arranca, tu cambio de configuración está mal; revierte y vuelve a aplicar con cuidado.
Task 13: Tail web server error logs while reproducing the issue
cr0x@server:~$ sudo tail -f /var/log/nginx/error.log
2025/12/27 09:54:10 [error] 1241#1241: *392 client intended to send too large body: 7340032 bytes, client: 203.0.113.10, server: site.example, request: "POST /wp-admin/async-upload.php HTTP/1.1", host: "site.example"
Qué significa: definitivamente es tamaño de petición de Nginx, no WordPress.
Decisión: aumenta client_max_body_size y confirma que editaste el bloque de servidor correcto (no un default que no usas).
Task 14: Tail PHP-FPM logs for fatal processing failures
cr0x@server:~$ sudo tail -n 30 /var/log/php8.2-fpm.log
[27-Dec-2025 09:55:37] WARNING: [pool www] child 2219 said into stderr: "PHP Fatal error: Uncaught Error: Call to undefined function imagecreatefromjpeg() in /var/www/site/wp-includes/class-wp-image-editor-gd.php:92"
Qué significa: faltan funciones de GD; la generación de miniaturas falla después de la subida, a menudo mostrado como “HTTP error”.
Decisión: instala/activa la extensión PHP correspondiente (como php-gd) y reinicia PHP-FPM.
Task 15: Verify installed PHP extensions (GD, Imagick)
cr0x@server:~$ php -m | egrep -i 'gd|imagick|exif'
exif
Qué significa: EXIF está presente; GD e Imagick no lo están.
Decisión: instala una librería de imagen (GD es la más sencilla; Imagick es potente pero necesita ImageMagick también). No te quedes sin ninguna.
Task 16: Check SELinux enforcement and recent denies
cr0x@server:~$ getenforce
Enforcing
cr0x@server:~$ sudo ausearch -m avc -ts recent | tail -n 3
type=AVC msg=audit(1735293382.612:1187): avc: denied { write } for pid=2311 comm="php-fpm" name="uploads" dev="dm-0" ino=420112 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=dir permissive=0
Qué significa: SELinux bloqueó acceso de escritura; los permisos podrían estar bien, pero las etiquetas no.
Decisión: arregla los contextos (por ejemplo, asigna httpd_sys_rw_content_t a uploads) en lugar de debilitar SELinux.
Permisos y propiedad: la causa número 1
Si solo evalúas una cosa, que sea esta: ¿puede el usuario runtime de PHP crear archivos en wp-content/uploads?
Cómo debe verse “correcto”
- El directorio uploads propiedad del usuario/grupo runtime del web (
www-dataen Debian/Ubuntu;apacheen muchas distribuciones basadas en RHEL), o escribible vía grupo/ACL. - Permisos de directorio típicamente
0755o0775. Archivos típicamente0644. - Si usas un usuario de deploy y mantienes el runtime en modo solo lectura para el código, entonces uploads debe ser una ruta separada escribible con permisos explícitos.
Qué no hacer
No “arregles” con chmod -R 777 wp-content/uploads. Estás cambiando una caída por un incidente de seguridad. En hosting multi-inquilino, es una puerta para movimiento lateral envuelta en un regalo.
Cuando la propiedad vuelve a root
Esto sucede cuando:
- Los scripts de despliegue se ejecutan como root y copian un árbol
uploadscon propiedad root. - Backups se restauran con mapeo incorrecto de uid/gid (especialmente entre hosts).
- Contenedores reconstruyen volúmenes y cambian permisos en el montaje.
Recomendación de política: trata uploads como datos persistentes, no como código. Adminístralo como datos. Hazle backup, móntalo, etiquétalo y monitorízalo.
Límites de tamaño: PHP, servidor web, proxies y CDNs
Las subidas fallan en el límite más pequeño de la cadena. Solo necesitas un componente mezquino para arruinar el día de todos.
Límites de PHP que importan
upload_max_filesize: tamaño máximo de un archivo subido.post_max_size: tamaño máximo del cuerpo POST completo (debe ser >= upload_max_filesize, y realísticamente mayor por la sobrecarga multipart).memory_limit: el procesamiento de imágenes puede requerir múltiplos del tamaño comprimido. Un JPEG de 12MB puede expandirse masivamente en memoria.max_execution_time: almacenamiento lento u operaciones intensivas en CPU pueden alcanzar timeouts.max_input_time: puede importar para subidas lentas.
Puertas de Nginx y Apache
- Nginx
client_max_body_sizees la causa más común de 413. - Apache
LimitRequestBodyy ajustes de proxy pueden imponer límites; también comprueba timeouts cuando PHP está detrás de un módulo proxy.
Proxies y CDNs
Incluso si tu origen es perfecto, un CDN/WAF puede rechazar POSTs grandes o cuerpos multipart, o imponer límites según el plan. Si las subidas fallan solo a través del dominio público pero funcionan al golpear el origen directamente (prueba desde una red privada), tu borde es el culpable.
Broma #2: En algún lugar de la pila siempre hay un límite de 2MB de 2009, viviendo su mejor vida en silencio.
Directorios temporales y agotamiento de inodos/disco
Las subidas van primero a un directorio temporal. Si el temporal está lleno, mal configurado, montado con noexec de formas raras, o es un tmpfs subdimensionado, verás fallos que parecen errores de permisos, HTTP errors o timeouts.
Firmas típicas de fallo
- Archivos pequeños tienen éxito, archivos grandes fallan: el espacio temporal (o su límite) está justo.
- Fallas intermitentes en picos de tráfico: agotamiento de inodos temporales, ruta de sesión o picos de concurrencia.
- Las subidas tienen éxito pero el procesamiento falla: el temporal está bien; las librerías de imagen o la memoria no.
Guía práctica
- No montes
/tmpcomo un tmpfs diminuto a menos que lo hayas dimensionado para subidas y picos. - Si estás en contenedores, sé explícito: monta un volumen persistente para uploads y un espacio scratch suficientemente grande para temporales.
- Vigila el uso de inodos. Es el asesino silencioso en sitios con mucho cache.
Librerías de imagen: Imagick vs GD, y por qué “HTTP error” a veces es mentira
WordPress normalmente intenta crear múltiples tamaños derivados al subir. Eso significa que necesita un backend editor de imágenes funcional. Si ni Imagick ni GD son utilizables, las subidas pueden “fallar” después de la transferencia, durante la generación de metadata.
Cómo elegir en producción
- GD: cadena de dependencias más simple, buena base. Instala la extensión PHP y normalmente ya está listo.
- Imagick: más capaz, maneja mejor algunos formatos y operaciones, pero requiere ImageMagick y políticas/recursos que pueden sorprenderte.
Modos comunes de fallo de librerías
- Extensión faltante: GD/Imagick no instalados para la versión de PHP que atiende las peticiones.
- Wrong PHP SAPI: instalaste
php-gdpara CLI pero FPM usa otra versión; las pruebas en CLI pasan, la web falla. - Restricciones de política de ImageMagick: operaciones denegadas, memoria/disco restringidos, formatos bloqueados.
- Agotamiento de memoria: errores fatales al crear miniaturas grandes.
Cuando dudes, sigue los logs de PHP-FPM durante un intento reproducible de subida. Si ves errores fatales en clases de editor de imágenes, deja de culpar a WordPress y arregla el runtime.
Capas de seguridad: SELinux/AppArmor, reglas WAF, ModSecurity
Los sistemas de seguridad fallan “correctamente”, lo que significa que fallan de formas que parecen que tu app está rota. Ese es su trabajo. Tu trabajo es confirmarlo rápido.
SELinux: permisos que no son permisos
En sistemas con SELinux, los contextos de archivo importan tanto como los modos UNIX. Puedes poner 0777 y aun así ser denegado. Si ves AVC denies relativos a uploads, arregla etiquetas en el directorio de contenido y mantén SELinux en modo enforcing.
AppArmor
AppArmor puede restringir a PHP-FPM o al servidor web de escribir en rutas específicas. El síntoma puede ser idéntico a propiedad equivocada. Revisa perfiles y logs si estás en Ubuntu con políticas AppArmor activas.
ModSecurity y reglas WAF
Las peticiones multipart son un objetivo favorito de reglas genéricas. Los falsos positivos ocurren. Si recibes 403 en subidas pero las páginas admin normales funcionan, revisa los logs del WAF por reglas bloqueadas en async-upload.php o endpoints REST.
Errores comunes: síntoma → causa raíz → solución
“Unable to create directory wp-content/uploads/2025/12. Is its parent directory writable by the server?”
- Causa raíz: propiedad incorrecta en
wp-content/uploads, bit de ejecución faltante en un directorio padre, o denegación de SELinux. - Solución: ajusta propiedad/modo; verifica escribiendo como el usuario runtime; en SELinux, establece el contexto correcto en uploads.
“HTTP error” inmediatamente al subir
- Causa raíz: 413 petición demasiado grande de Nginx/Apache/CDN, o rechazo del WAF.
- Solución: aumenta límites del cuerpo de la petición a lo largo de la cadena; confirma con logs; bypass temporal del borde para aislar.
La subida se completa pero la imagen nunca aparece en la Biblioteca de Medios
- Causa raíz: fatal PHP durante la generación de metadata; fallo al escribir en la base de datos; permisos impiden el movimiento final desde temp.
- Solución: sigue los logs de PHP; comprueba conectividad de la base de datos; verifica temp y uploads; revisa extensiones PHP.
Solo fallan imágenes grandes, las pequeñas sí funcionan
- Causa raíz:
upload_max_filesize,post_max_size,client_max_body_size, o espacio temporal/límites de memoria. - Solución: aumenta límites; verifica disco temporal; sube el memory limit para el procesamiento.
Las subidas funcionan en un servidor pero no en otro (mismo código)
- Causa raíz: falta GD/Imagick, diferente política de ImageMagick, diferente postura de SELinux/AppArmor, o distinta configuración de proxy.
- Solución: compara
php -m, valores ini de PHP, dumps de configuración de Nginx/Apache y ajustes de seguridad.
Las subidas a veces fallan después del deploy
- Causa raíz: el paso de despliegue resetea permisos de uploads o intercambia mounts; los contenedores pierden uploads efímeros.
- Solución: trata uploads como volumen persistente; aplica ownership en el despliegue; añade un chequeo de salud que escriba en uploads.
Las subidas fallan con 500 o 502 tras una larga espera
- Causa raíz: timeout de PHP-FPM, timeout upstream, agotamiento de memoria, almacenamiento lento, o operaciones pesadas de Imagick.
- Solución: revisa timeouts upstream; aumenta tiempo de ejecución de PHP; perfila IO; ajusta tamaños de imagen; considera externalizar el procesamiento de imágenes.
Listas de verificación / plan paso a paso
Checklist A: “Necesito esto arreglado en 10 minutos”
- Reproduce una vez con devtools abierto. Anota el código de estado y la respuesta.
- Sigue los logs de error de Nginx/Apache; sigue los logs de PHP-FPM al mismo tiempo.
- Comprueba la propiedad de
wp-content/uploadsy prueba de escritura como usuario runtime. - Comprueba uso de disco e inodos para uploads y temporales.
- Comprueba límites activos de PHP + servidor web para el cuerpo de la petición.
- Confirma que GD o Imagick están instalados para la versión de PHP que sirve.
- Si SELinux está en enforcing, revisa denegaciones AVC antes de hacer algo imprudente.
Checklist B: “Evitar que vuelva a pasar”
- Monitoriza: uso de disco, uso de inodos y utilización de
/tmpcon alertas. - Higiene de despliegue: asegura que tu pipeline nunca cambie la propiedad de uploads persistentes inesperadamente.
- Config como código: guarda overrides de PHP-FPM y límites de Nginx en control de versiones.
- Paridad de librerías: estandariza conjuntos de extensiones entre entornos; inclúyelas en las imágenes.
- Política de borde: documenta y prueba límites de cuerpo de petición de WAF/CDN; incluye endpoints de subida en allowlists según corresponda.
- Prueba operacional: añade un chequeo sintético que suba una imagen pequeña y verifique que se crean derivados (en una ruta de prueba segura).
Checklist C: comprobaciones de sentido común para ingenieros de almacenamiento
- Confirma que uploads residen en almacenamiento duradero, no en root efímero en contenedores.
- Confirma que el sistema de archivos soporta tu carga: muchos archivos pequeños, creación frecuente de directorios, escritos grandes ocasionales.
- Confirma que backup/restore conserva mapeo uid/gid (o tienes un fixup post-restore).
- Confirma latencia IO bajo carga; discos lentos causan timeouts que aparentan ser “errores de WordPress”.
Tres micro-historias corporativas desde producción
1) El incidente causado por una suposición errónea
La migración parecía sencilla: mover un sitio WordPress de una VM antigua a un host nuevo, mantener la misma disposición de directorios, restaurar un backup, cambiar DNS. El equipo supuso que si las páginas cargaban, lo difícil ya estaba hecho.
En minutos, marketing intentó subir imágenes de producto para un lanzamiento. Las subidas fallaron con “Unable to create directory”. Alguien hizo el ritual habitual: reiniciar PHP-FPM, limpiar caches, volver a guardar enlaces permanentes. Nada. El sitio “funcionaba”, así que todos asumieron que el problema era WordPress.
El problema real fue anodino: la restauración del backup se hizo como root, creando wp-content/uploads como root:root. En el host antiguo, una configuración distinta lo enmascaraba con ACL permisivas. En el host nuevo, el runtime era www-data y podía leer pero no escribir.
Tardó más de lo debido porque la primera respuesta fue ajustar límites de PHP. Esa es la suposición equivocada en acción: “si fallan las subidas, es por tamaño”. La solución fue un chown de una línea, seguido de añadir un paso post-restore que fije explícitamente la propiedad en directorios escribibles y lo verifique escribiendo como el usuario runtime.
La lección que quedó: no trates “las páginas cargan” como condición de éxito. Para WordPress, “poder subir medios” es una capacidad productiva clave, no un extra.
2) La optimización que salió mal
Un equipo orientado al rendimiento quiso todo más rápido. Movieron /tmp a tmpfs para reducir IO de disco y acelerar sesiones PHP y subidas. En papel fue elegante: la memoria es más rápida que el disco, y esos son archivos temporales de todas formas.
Por un tiempo pareció genial. Los tiempos de carga se estabilizaron y los gráficos de latencia de disco se vieron más limpios. Luego un equipo de contenido subió un lote de imágenes de alta resolución—exactamente lo que ocurre cuando alguien prepara una campaña con fecha límite y sin paciencia.
Las subidas comenzaron a fallar aleatoriamente. No todas. Lo suficiente para ser exasperante. Los logs de PHP mostraban esporádicos “failed to write file to disk”, mientras que el OS se veía “bien” a primera vista porque el disco tenía espacio global.
El problema fue simple: el tmpfs estaba dimensionado de forma conservadora, y subidas concurrentes más generación de miniaturas crearon picos de uso temporal. El tmpfs se llenó, los inodos se agotaron y el staging de PHP falló. La solución no fue abandonar tmpfs; fue dimensionarlo con margen, monitorizarlo y mover workflows voluminosos a un volumen scratch dedicado cuando fuera necesario.
Las optimizaciones que eliminan cuellos de botella son estupendas. Las que crean cuellos de botella ocultos son la razón por la que luego explicas a dirección por qué “más rápido” hizo inutilizable el sitio.
3) La práctica aburrida pero correcta que salvó el día
Otra organización ejecutaba WordPress a escala con actualizaciones frecuentes de contenido. No eran gente emocionante. Eran el tipo de personas que etiquetan cables y disfrutan las ventanas de cambio. Genial.
Tenían una comprobación estándar de salud de uploads en su pipeline de despliegue. Hacía dos cosas: verificaba que el usuario runtime pudiera escribir en la ruta de uploads, y ejecutaba una pequeña subida a través de PHP que confirmaba que la generación de metadata no daba errores fatales. Tomaba segundos.
Un día, un parche del SO introdujo un cambio sutil: PHP-FPM se reinició y cargó un orden distinto de directorios ini, efectivamente dejando fuera la extensión GD en ese entorno. El sitio seguía sirviendo páginas. Los logins de admin funcionaban. Pero las subidas de imágenes habrían fallado cuando llegara el equipo de contenido.
El pipeline lo detectó inmediatamente y bloqueó el despliegue. Los ingenieros instalaron la extensión faltante para la versión correcta de PHP, reiniciaron el servicio y volvieron a ejecutar la comprobación de salud. Sin caída. Sin crisis. Sin “chmod 777” de emergencia.
Esa es la magia de la corrección aburrida: no evita cada fallo, pero evita los humillantes.
Preguntas frecuentes
1) ¿Por qué WordPress dice “HTTP error” cuando los permisos de archivos están mal?
Porque el endpoint de subida puede fallar en distintas etapas y WordPress a veces las colapsa en un mensaje genérico. Revisa los logs del servidor web y PHP para hallar la falla real.
2) Aumenté upload_max_filesize pero las subidas siguen fallando. ¿Qué me falta?
Normalmente una de estas: post_max_size es más pequeño, client_max_body_size de Nginx es más pequeño, un override de la pool de PHP-FPM fuerza valores menores, o el CDN/WAF tiene un tope inferior.
3) ¿Necesito GD e Imagick a la vez?
No. Necesitas al menos un backend editor de imágenes funcional. GD es más sencillo de mantener consistente. Imagick está bien si gestionas la política y límites de ImageMagick.
4) La subida funciona pero no se generan miniaturas. ¿Qué debo comprobar?
Librerías de imagen y límites de memoria/tiempo. Sigue los logs de PHP-FPM durante una subida. La falta de gd/imagick o el agotamiento de memoria son causas comunes.
5) ¿Un problema de “disco lleno” puede presentarse como un error de permisos?
Sí. PHP puede fallar al escribir archivos temporales y WordPress puede reportarlo de forma pobre. Comprueba df -h y df -i para uploads y rutas temporales.
6) ¿Cuál es el modelo de permisos más seguro para uploads?
Mantén el código en solo lectura para el runtime si puedes, y haz que wp-content/uploads sea escribible por el usuario/grupo runtime (a menudo vía propiedad de grupo y 0775, o ACLs). Evita permisos world-writable.
7) ¿Por qué solo falla a través del dominio público, pero funciona cuando pruebo localmente?
Tu capa de borde (CDN/WAF/load balancer) impone límites o bloquea peticiones multipart. Compara códigos de estado y logs. Prueba evitando el borde para aislar.
8) ¿Cómo sé si SELinux es el problema?
Si SELinux está en enforcing y ves AVC denies que involucren a php-fpm o al servidor web al escribir en uploads, es SELinux. Arregla contextos; no “lo soluciones” deshabilitando SELinux.
9) ¿Por qué las subidas de WordPress en contenedores desaparecen tras redeploy?
Porque las subidas se escriben en el sistema de archivos del contenedor, que es efímero. Monta un volumen persistente para wp-content/uploads y trátalo como datos.
Conclusión: siguientes pasos para evitar repeticiones
Si tu WordPress no puede subir imágenes, la ruta más rápida es el escepticismo disciplinado. Ignora el mensaje de la UI. Confía en los logs. Demuestra escribibilidad. Luego persigue límites. Luego las librerías. Solo después de eso culpas a la política de seguridad.
Haz esto después
- Añade una comprobación de salud de uploads (prueba de escritura + verificación de generación de derivados) a los despliegues.
- Estandariza la configuración runtime (límites de PHP, límites de Nginx/Apache, overrides de pool PHP-FPM) y guárdala en control de versiones.
- Monitoriza el almacenamiento en serio: espacio en disco, uso de inodos y utilización de
/tmpcon alertas. - Elige una estrategia de librerías (GD como base o Imagick con política explícita) y hazla consistente entre entornos.
- Documenta límites de borde para que “funciona en staging” deje de sorprender cuando intervenga el CDN.
Las subidas son una canalización crítica de producción. Trátalas como tal y la Biblioteca de Medios dejará de manipular psicológicamente a tu rotación on-call.