WordPress “Destination folder already exists”: soluciona instalaciones sin destrozar wp-content
Son las 9:12 AM, marketing quiere un nuevo plugin de formularios “ahora mismo” y WordPress responde con calma: “Destination folder already exists.” El sitio sigue cargando, pero el administrador está bloqueado, las instalaciones fallan y cada “solución” que encuentras en línea implica borrar algo bajo wp-content como si fuera una servilleta descartable.
No hagas eso. wp-content es donde tu negocio guarda sus zapatos. Patearlo y pasarás el resto del día pidiendo disculpas a gente con agendas.
Qué significa realmente el error (y qué no significa)
WordPress lanza “Destination folder already exists” cuando intenta instalar o actualizar un plugin/tema y detecta que el nombre del directorio destino ya está presente. Eso suena obvio, pero el detalle clave es este: WordPress no puede decidir con confianza si el directorio existente es una instalación válida, una extracción parcial, un residuo de una actualización fallida o algo que no debería tocar.
Así que WordPress actúa con cautela y se detiene. Por lo general esa es la elección correcta. Tu trabajo es determinar si estás viendo:
- Un plugin/tema legítimamente instalado (estás intentando “instalar” algo que ya está ahí).
- Una instalación parcial (el directorio existe pero faltan archivos; WordPress se niega a sobrescribir).
- Un problema de permisos/propiedad (WordPress no puede eliminar/reemplazar el directorio, así que lo reporta como “existe” incluso cuando necesita reemplazarlo).
- Un desajuste en la estructura del ZIP (el archivo se extrae en un nombre de carpeta que choca con una existente).
- Un desajuste de abstracción del sistema de archivos (escrituras directas frente a métodos FTP/SSH, directorios temporales y juegos de umask).
Lo que generalmente no es:
- Una situación de “WordPress está roto” que requiere reinstalar todo el sitio.
- Una razón para borrar carpetas al azar bajo
wp-contenthasta que la pantalla cambie. - Una razón para chmod a todo 777 (a menos que tu modelo de amenaza sea “me gusta vivir peligrosamente”).
Una cita que vale la pena tener en la pared cerca de tu terminal: “La esperanza no es una estrategia.” — Gen. Gordon R. Sullivan. Los errores de WordPress suelen tentar operaciones basadas en esperanza. No caigas en eso.
Guía rápida de diagnóstico (comprueba esto primero)
Si vas contra reloj, no divagues. Ejecuta esto como un triage de incidente.
Primero: confirma qué intenta instalar WordPress
- ¿Plugin o tema? ¿Nombre/slug? ¿Desde el repositorio o un ZIP subido?
- ¿Es una instalación, una actualización o un intento de “reinstalación”?
- ¿Falló una actualización previa (pantalla blanca, “modo mantenimiento”, aviso de actualización atascado)?
Segundo: inspecciona el directorio destino
- ¿Existe
wp-content/plugins/<slug>owp-content/themes/<slug>? - ¿Es un árbol completo o un desastre medio extraído?
- ¿Hay hermanos raros como
<slug>.tmp,<slug>_oldo un directorioupgradesobrante?
Tercero: verifica propiedad, permisos y ruta de escritura
- ¿Puede el usuario del servidor web escribir en
wp-contentywp-content/upgrade? - ¿Está WordPress usando acceso directo al sistema de archivos o pidiendo credenciales FTP?
- ¿Está lleno el disco o agotados los inodos?
Cuarto: revisa los registros (sí, en serio)
- Registro de errores del servidor web: permiso denegado, errores de unzip, timeouts.
- Registro de PHP-FPM: restricciones open_basedir, problemas con el directorio temporal.
- Registro de depuración de WordPress: mensajes sobre sistema de archivos, upgrade y extracción.
Quinto: decide la ruta más segura
- Si el directorio contiene una instalación válida: haz una actualización, no una instalación; o usa WP-CLI con
--forcecon cuidado. - Si es parcial: renómbralo y reinstala limpio.
- Si es permisos: arregla propiedad/ACLs y vuelve a ejecutar la actualización.
- Si es estructura del ZIP: reempaqueta o elige otro ZIP.
Datos interesantes y contexto histórico (para que la rareza tenga sentido)
Seis a diez datos rápidos que explican por qué existe este error y por qué sigue siendo molesto:
- WordPress originalmente asumía hosting compartido, donde “instalar un plugin” significaba “subir vía FTP”, no intercambiar directorios de forma atómica.
- El sistema de actualización depende de un espacio de trabajo temporal (a menudo
wp-content/upgrade) y de una secuencia de copiar/renombrar; cuando esa secuencia se interrumpe, son comunes los restos. - Los archivos ZIP no son generosos por convención: muchos proveedores envían un ZIP con una carpeta de nivel superior que no coincide con el slug del plugin, causando colisiones o carpetas anidadas.
- En algunos hosts, PHP se ejecuta como un usuario distinto al servidor web (o bajo un usuario de pool), produciendo propiedad mixta que solo falla durante escrituras.
- La lógica del “método de sistema de archivos” existe porque WordPress no puede asumir acceso de escritura; intenta escrituras directas y luego recurre a métodos FTP/SSH cuando está bloqueado.
- La sustitución atómica de directorios no siempre está disponible en todos los modelos de sistema de archivos/permisos; WordPress actúa con conservadurismo para evitar borrar código funcional.
- Los directorios de plugins y temas se convirtieron en gestores de paquetes de facto mucho antes de que WordPress tuviera semánticas fiables de rollback/transacción.
- El “modo mantenimiento” atascado viene de un único archivo (
.maintenance) creado durante las actualizaciones; si la limpieza falla, el sitio puede parecer “caído” aunque esté bien.
Modos de fallo: las razones reales por las que la carpeta “ya existe”
1) El plugin/tema ya está instalado, pero intentas “instalar” de nuevo
Más común con subidas de ZIP y paquetes de proveedores. La carpeta existe, WordPress la ve, se niega a sobrescribir y te lanza el mensaje directo. La acción correcta es actualizar desde la página de Plugins, o eliminar/renombrar el directorio existente tras verificar que es seguro.
2) Extracción parcial por una falla previa
Un timeout a mitad de unzip, disco lleno, un worker PHP terminado, un timeout de proxy inverso o un ingeniero de operaciones que reinició PHP-FPM “para limpiarlo”. Terminas con un directorio que existe pero está incompleto. WordPress no puede saber si debe borrarlo, así que se detiene.
3) Desajuste de permisos/propiedad
La carpeta existe y debería poder reemplazarse, pero el proceso web no puede eliminarla. WordPress informa “ya existe” porque su intento de limpiar falla y no puede continuar de forma segura. Busca Permission denied en los registros. Lo encontrarás.
4) SELinux/AppArmor o políticas endurecidas
En sistemas endurecidos, los permisos parecen correctos pero el control de acceso obligatorio bloquea las escrituras. Esto aparece como errores de permiso incluso con los bits de modo “correctos”.
5) open_basedir o problemas con directorio temporal
La extracción suele usar un directorio temporal; si PHP no puede escribir ahí, obtienes instalaciones a medio terminar y errores confusos. WordPress entonces se topa con la carpeta destino medio creada y se detiene.
6) Comportamiento extraño del sistema de archivos (NFS, CIFS, volúmenes respaldados por objetos)
Los sistemas de archivos en red pueden tener caché, visibilidad retardada o semánticas de rename que no se comportan como ext4/XFS locales. La sustitución de directorios puede fallar de formas que parecen “existe” pero realmente significan “el rename no se propagó”.
7) Herramientas de despliegue luchando con WordPress
Si despliegas código vía Git, rsync o contenedores, y WordPress intenta automodificarse, tienes fuentes de verdad en pugna. El error es WordPress siendo el perro más pequeño ladrando a una puerta bloqueada.
Chiste #1: Las actualizaciones de WordPress son como sillas de oficina—generalmente bien, hasta que alguien se recuesta con confianza.
Tareas prácticas: comandos, salidas y decisiones
Estas son tareas reales que puedes ejecutar en un host Linux. Cada una incluye: el comando, qué significa una salida típica y la decisión que tomas.
Task 1: Confirmar que existe el directorio destino
cr0x@server:~$ sudo ls -ld /var/www/html/wp-content/plugins/contact-form-7
drwxr-xr-x 5 root root 4096 Dec 27 08:41 /var/www/html/wp-content/plugins/contact-form-7
Significado: El directorio del plugin existe y es propiedad de root:root. Eso es sospechoso en una máquina típica de WordPress donde el proceso web necesita acceso de escritura.
Decisión: No lo borres todavía. Comprueba si es una instalación válida y si la propiedad es incorrecta.
Task 2: Comprobar si es una instalación parcial (conteo de archivos y archivos esperados)
cr0x@server:~$ sudo find /var/www/html/wp-content/plugins/contact-form-7 -maxdepth 2 -type f | wc -l
3
Significado: Solo 3 archivos probablemente es incompleto para un plugin real. Un plugin saludable suele tener docenas de archivos.
Decisión: Trátalo como extracción parcial. Planea renombrarlo y reinstalar.
Task 3: Identificar el usuario del servidor web (Apache)
cr0x@server:~$ ps -eo user,comm | egrep 'apache2|httpd' | head
www-data apache2
www-data apache2
www-data apache2
Significado: Los procesos workers de Apache se ejecutan como www-data.
Decisión: Los directorios de plugins/temas deberían ser generalmente escribibles por www-data (o gestionados por la herramienta de despliegue, pero entonces WordPress no debería auto-actualizarse).
Task 4: Identificar el usuario del pool PHP-FPM (Nginx + PHP-FPM)
cr0x@server:~$ sudo grep -R "^\s*user\s*=" /etc/php/*/fpm/pool.d/www.conf | head -n 1
user = www-data
Significado: PHP se ejecuta como www-data también (bien; menos sorpresas por propiedad mixta).
Decisión: Si aún tienes carpetas de plugin propiedad de root, probablemente fueron creadas por acciones manuales (unzip como root, rsync como root o un paso de CI que se volvió loco).
Task 5: Probar acceso de escritura a wp-content (como el usuario web)
cr0x@server:~$ sudo -u www-data bash -lc 'touch /var/www/html/wp-content/.write-test && echo ok && rm /var/www/html/wp-content/.write-test'
ok
Significado: El usuario web puede escribir al menos en el nivel superior de wp-content.
Decisión: La falla probablemente esté dentro de un subdirectorio específico (plugins/themes/upgrade) o debido a propiedad/ACL en ese directorio de plugin.
Task 6: Comprobar salud del directorio temporal de upgrade
cr0x@server:~$ sudo -u www-data bash -lc 'mkdir -p /var/www/html/wp-content/upgrade && touch /var/www/html/wp-content/upgrade/.upgrade-test && ls -la /var/www/html/wp-content/upgrade | head'
total 8
drwxr-xr-x 2 www-data www-data 4096 Dec 27 09:01 .
drwxr-xr-x 10 www-data www-data 4096 Dec 27 09:01 ..
-rw-r--r-- 1 www-data www-data 0 Dec 27 09:01 .upgrade-test
Significado: WordPress puede preparar archivos de actualización.
Decisión: Si las actualizaciones aún fallan, céntrate en la propiedad del directorio destino, espacio en disco o estructura ZIP.
Task 7: Comprobar espacio en disco y presión de inodos
cr0x@server:~$ df -h /var/www/html
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 40G 39G 600M 99% /
Significado: Básicamente te quedaste sin disco. La extracción puede fallar a mitad, dejando el directorio destino atrás.
Decisión: Libera espacio primero. Luego limpia directorios parciales y reintenta. Si ignoras esto, seguirás generando instalaciones a medias.
cr0x@server:~$ df -i /var/www/html
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 2621440 2619000 2440 100% /
Significado: Los inodos están agotados. Esto puede romper instalaciones incluso cuando el “espacio en disco” parece estar bien en otros lados.
Decisión: Encuentra y elimina basura que consuma muchos inodos (directorios de caché, backups viejos). Luego reintenta.
Task 8: Buscar modo mantenimiento atascado
cr0x@server:~$ sudo ls -la /var/www/html/.maintenance
-rw-r--r-- 1 www-data www-data 57 Dec 27 08:40 /var/www/html/.maintenance
Significado: Una actualización previa no limpió. Los usuarios pueden ver “Brevemente no disponible por mantenimiento programado.”
Decisión: Si no hay ninguna actualización activa, elimínalo tras verificar que no hay procesos de actualización en curso. Luego céntrate en por qué falló la actualización.
Task 9: Revisar el log de depuración de WordPress por errores de extracción/FS
cr0x@server:~$ sudo tail -n 30 /var/www/html/wp-content/debug.log
[27-Dec-2025 08:41:12 UTC] PHP Warning: copy(/var/www/html/wp-content/plugins/contact-form-7/readme.txt): failed to open stream: Permission denied
[27-Dec-2025 08:41:12 UTC] PHP Warning: unlink(/var/www/html/wp-content/plugins/contact-form-7/readme.txt): Permission denied
Significado: Problema clásico de propiedad/permiso dentro del directorio destino.
Decisión: Arregla propiedad/modo/ACL en esa ruta. No sigas reintentando la instalación; solo llenarás los logs y la paciencia.
Task 10: Inspeccionar logs del servidor web / PHP para el error real
cr0x@server:~$ sudo tail -n 50 /var/log/nginx/error.log
2025/12/27 08:41:12 [error] 12345#12345: *901 FastCGI sent in stderr: "PHP message: PHP Warning: mkdir(): Permission denied in /var/www/html/wp-admin/includes/class-wp-filesystem-direct.php on line 56" while reading response header from upstream
Significado: El método de sistema de archivos es directo, pero mkdir falló. Esto no es un problema de “lógica” de WordPress; es un problema de acceso a nivel del SO.
Decisión: Arregla permisos/propiedad y vuelve a ejecutar. Evita subidas de ZIP hacky hasta que la ruta de escritura subyacente funcione.
Task 11: Validar la estructura del ZIP antes de subirlo (problema común de proveedores)
cr0x@server:~$ unzip -l /tmp/vendor-plugin.zip | head
Archive: /tmp/vendor-plugin.zip
Length Date Time Name
--------- ---------- ----- ----
0 2025-12-01 10:10 vendor-plugin/
0 2025-12-01 10:10 vendor-plugin/vendor-plugin/
2345 2025-12-01 10:10 vendor-plugin/vendor-plugin/plugin.php
Significado: Carpeta anidada: vendor-plugin/vendor-plugin/. WordPress extraerá a vendor-plugin y el plugin real vive un nivel más abajo, lo que causa confusión y puede chocar con directorios existentes.
Decisión: Reempaqueta el ZIP (la carpeta de nivel superior debe ser el slug del plugin y contener los archivos del plugin directamente) o instala vía WP-CLI desde una ruta corregida.
Task 12: Usar WP-CLI para ver qué reconoce WordPress como instalado
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp plugin list --field=name | head
akismet
contact-form-7
hello
Significado: WordPress ya reconoce contact-form-7 como instalado. El intento de instalación desde el administrador es redundante o el directorio está roto pero todavía detectado.
Decisión: Considera una reinstalación controlada usando WP-CLI (con overwrite), o renombra el directorio y reinstala limpio.
Task 13: Reinstalación controlada con WP-CLI (plugin) sin borrado a ciegas
cr0x@server:~$ cd /var/www/html && sudo -u www-data wp plugin install contact-form-7 --force
Installing Contact Form 7 (6.0.3)
Downloading installation package from https://downloads.wordpress.org/plugin/contact-form-7.6.0.3.zip...
Unpacking the package...
Installing the plugin...
Plugin installed successfully.
Significado: --force indica a WP-CLI que sobrescriba. Esto es más seguro que un rm -rf manual si confías en la fuente y los permisos del sistema de archivos son correctos.
Decisión: Si esto funciona, tu problema probablemente fue que el administrador de WordPress se negó a sobrescribir mientras WP-CLI lo manejó. Si falla, lee el error exacto y arregla la causa a nivel OS.
Task 14: Renombrar la carpeta destino para preservar evidencia
cr0x@server:~$ sudo mv /var/www/html/wp-content/plugins/contact-form-7 /var/www/html/wp-content/plugins/contact-form-7.broken-20251227
Significado: Has eliminado la colisión mientras conservas el directorio antiguo para forenseo o rollback.
Decisión: Ahora reintenta la instalación. Si tiene éxito, compara el folder antiguo más tarde para entender qué falló.
Task 15: Arreglar propiedad limpiamente (y solo donde haga falta)
cr0x@server:~$ sudo chown -R www-data:www-data /var/www/html/wp-content/plugins/contact-form-7
Significado: El usuario web ahora puede gestionar archivos dentro de ese directorio de plugin.
Decisión: Reintenta actualización/instalación. Si tienes una canalización de despliegue que espera propiedad root, detente y decide quién posee el código: WordPress o tu pipeline. Elige uno.
Task 16: Detectar y quitar banderas inmutables tercas (raro, pero picante)
cr0x@server:~$ sudo lsattr -d /var/www/html/wp-content/plugins/contact-form-7
----i---------e------- /var/www/html/wp-content/plugins/contact-form-7
Significado: El directorio tiene el atributo inmutable (i). Las eliminaciones y escrituras fallarán incluso como root.
Decisión: Si esto lo puso un endurecimiento o una herramienta de backup, quita la inmutabilidad antes de intentar actualizar.
cr0x@server:~$ sudo chattr -i /var/www/html/wp-content/plugins/contact-form-7
Task 17: Comprobar contexto SELinux (si aplica)
cr0x@server:~$ sudo getenforce
Enforcing
cr0x@server:~$ sudo ls -Zd /var/www/html/wp-content
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/wp-content
Significado: Si wp-content está etiquetado httpd_sys_content_t, Apache puede leer pero puede que no escribir. Las rutas escribibles típicamente necesitan httpd_sys_rw_content_t según la política.
Decisión: Ajusta contextos solo para directorios escribibles, no para todo el docroot.
Task 18: Encontrar directorios de trabajo de upgrade sobrantes
cr0x@server:~$ sudo find /var/www/html/wp-content/upgrade -maxdepth 1 -type d -printf '%f\n' | head
upgrade
tmp-1234567890
tmp-1735290072
Significado: Directorios temporales antiguos pueden indicar upgrades fallidos.
Decisión: Si no hay una actualización en curso, elimina directorios temporales viejos para reducir colisiones y desorden, pero conserva el último si necesitas evidencia.
Chiste #2: Nada dice “grado empresarial” como una carpeta llamada tmp-1735290072 controlando silenciosamente tu fin de semana.
Soluciones seguras que no destrozan wp-content
Estratagema A: Renombrar, no borrar (mover por defecto)
Cuando no estás 100% seguro de qué hay en ese directorio, renómbralo. Renombrar es reversible, rápido y conserva evidencia. La única vez que deberías borrar inmediatamente es cuando limpias un directorio de cache conocido o un directorio temporal conocido.
Bueno: contact-form-7.broken-20251227
Malo: rm -rf contact-form-7 y luego “¿creo que estaba bien?”
Estratagema B: Usar WP-CLI para sobrescritura controlada
La interfaz de administración de WordPress es conservadora. WP-CLI es directa. Si eres persona de ops, quieres comportamiento determinista y logs que puedas capturar. Usa:
wp plugin install <slug> --forcewp theme install <slug> --force
Pero solo después de confirmar que los permisos de escritura subyacentes son correctos. Si los permisos están mal, forzar escrituras solo producirá fallos más ruidosos.
Estratagema C: Arreglar propiedad/permisos con moderación
La “solución” más peligrosa en el mundo WordPress son los cambios de permisos generales:
- Evita:
chmod -R 777en cualquier lugar. - Evita: cambiar recursivamente el chown de todo el docroot si despliegas código vía Git/CI; crearás deriva y sorprenderás al siguiente despliegue.
Haz en su lugar: mantiene el código core propiedad del usuario de despliegue (o root), y haz solo los directorios escribibles por el usuario de runtime:
wp-content/uploadswp-content/cache(si se usa)wp-content/upgrade- directorios de plugin/tema solo si permites actualizaciones in situ
Estratagema D: Evita que WordPress se auto-modifique en producción (si puedes)
Postura opinada: en sistemas de producción serios, no deberías permitir que WordPress se actualice a sí mismo con botones en wp-admin. No es purismo; es reducir cambios no controlados. Si tienes CI/CD, imágenes inmutables o incluso control básico de cambios, gestiona las actualizaciones como despliegues. Entonces toda esta clase de errores disminuye drásticamente.
Compromiso: también debes dejar de usar WordPress como gestor de paquetes. Elige un modelo:
- Modelo 1: WordPress gestiona plugins/temas. Tú aseguras permisos y aceptas el riesgo de cambios desde el administrador.
- Modelo 2: Tu pipeline gestiona plugins/temas. Deshabilitas modificaciones de archivos en WordPress y despliegas artefactos.
Estratagema E: Reparar una instalación rota sin perder personalizaciones
Algunos plugins guardan configuración en la base de datos, no en el sistema de archivos. Si el directorio del plugin está corrupto, generalmente puedes reinstalar los archivos del plugin sin perder ajustes. Pero “generalmente” no es “siempre”.
Enfoque seguro:
- Haz copia de seguridad de la base de datos (o snapshot).
- Haz backup del directorio del plugin renombrándolo.
- Reinstala el plugin/tema limpio.
- Verifica comportamiento y logs.
- Sólo entonces elimina el directorio antiguo.
Tres micro-historias corporativas (cómo falla esto en la vida real)
Micro-historia 1: El incidente causado por una suposición equivocada
Una compañía mediana ejecutaba WordPress detrás de Nginx y PHP-FPM en una VM. Marketing tenía acceso de administrador e instalaba plugins directamente. El equipo de ops asumió que porque el sitio había funcionado durante meses, los permisos estaban “bien”. No lo estaban—simplemente no habían sido probados.
Una mañana, una actualización de plugin falló durante la extracción. El directorio del plugin existía, parcialmente actualizado, y WordPress se negó a reinstalar: “Destination folder already exists.” El panel de administración siguió pidiendo actualizaciones que no podían completarse. Nadie comprobó espacio en disco ni propiedad; simplemente reintentaron la actualización cinco veces, creando más carpetas temporales parciales.
La suposición equivocada fue sutil: “Si WordPress puede leer plugins, puede actualizarlos.” En ese host, una ventana de mantenimiento previa había copiado directorios de plugins como root desde un tarball de backup. WordPress podía leerlos. No podía reemplazarlos.
La solución fue aburrida: chown solo el directorio de plugin afectado y el directorio de staging de upgrade al usuario de PHP-FPM, luego reinstalar con WP-CLI. La acción postmortem fue mejor: bloquear actualizaciones en producción y mover la gestión de plugins al pipeline de despliegue.
Micro-historia 2: La optimización que salió mal
Una organización movió wp-content a un sistema de archivos en red para que múltiples nodos web compartieran uploads y plugins. Parecía ordenado: una fuente de verdad, no más rsync de archivos multimedia, escalado más fácil. La optimización fue “almacenamiento compartido lo resuelve todo”, que es el tipo de frase que envejece mal.
Durante una actualización de plugin, WordPress extrajo el ZIP en el nodo A, pero el nodo B aún veía el estado antiguo del directorio debido a caché y propagación retardada de metadatos. WordPress en el nodo B entonces intentó actualizar también (sí, la gente hace clic dos veces), chocó con un directorio en un estado intermedio y lanzó “Destination folder already exists.” El plugin quedó medio nuevo, medio viejo entre nodos.
Intentaron “arreglarlo” aumentando timeouts de PHP y añadiendo reintentos. Eso amplió el radio del problema. Eventualmente tuvieron que poner la interfaz de administración en solo lectura durante despliegues y asegurar que solo un nodo realizara las actualizaciones—o mejor, dejar de hacer actualizaciones vía wp-admin por completo.
La lección no fue “nunca uses NFS”. Fue: si tu aplicación espera semánticas de sistema de archivos locales para la sustitución atómica de directorios, necesitas diseñar en torno a eso. Centraliza actualizaciones mediante un job de despliegue único o empaqueta plugins/temas como artefactos y distribúyelos de forma predecible.
Micro-historia 3: La práctica aburrida pero correcta que salvó el día
Una gran instancia interna de WordPress servía documentación. No era glamurosa, pero era crítica. El equipo aplicaba dos prácticas: (1) cada cambio pasaba por un pipeline de despliegue, y (2) el sistema de archivos era mayormente solo lectura para el usuario de runtime excepto uploads y un pequeño conjunto de directorios escribibles.
Una tarde, un proveedor entregó un ZIP de “hotfix plugin” y exigió que se instalara de inmediato. El administrador lo intentó. WordPress no pudo escribir en el directorio de plugins (por diseño), y el error en la UI fue—predeciblemente—alguna variante de conflicto de carpeta destino / imposibilidad de sobrescribir. El pánico duró unos cinco minutos.
Puesto que la práctica era aburrida y consistente, la respuesta fue directa: el equipo escenificó el plugin en un workspace de build, validó la estructura del ZIP, lo reempaquetó correctamente, lo escaneó y luego lo desplegó como cualquier otro cambio de código. Sin chmod manual. Sin arqueología nocturna en wp-content.
El sitio se mantuvo estable y la “emergencia” se convirtió en un cambio normal con auditabilidad. Es increíble con qué frecuencia “hacemos despliegues de la misma manera siempre” marca la diferencia entre una pequeña molestia y una gran caída.
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: “Destination folder already exists” al subir un ZIP
Causa raíz: El directorio del plugin/tema ya existe por una instalación previa, o el ZIP contiene una carpeta de nivel superior que coincide con un directorio existente.
Solución: Verifica la ruta destino. Renombra el directorio existente. Valida la estructura del ZIP con unzip -l. Reempaqueta si está anidado.
2) Síntoma: El error se repite después de “eliminar el plugin” en wp-admin
Causa raíz: La eliminación en la UI falló por permisos; el directorio quedó en disco. La UI de WordPress no es una promesa, es una petición.
Solución: Comprueba la presencia y propiedad del directorio en disco. Elimínalo/renómbralo en disco una vez confirmado que es seguro.
3) Síntoma: La actualización falla; el directorio del plugin existe pero está roto
Causa raíz: Extracción parcial por timeout, disco lleno, agotamiento de inodos o worker PHP terminado.
Solución: Libera espacio/inodos, limpia dirs temporales de upgrade obsoletos, renombra el directorio roto del plugin, reinstala con WP-CLI.
4) Síntoma: WordPress pide credenciales FTP inesperadamente
Causa raíz: WordPress no puede escribir directamente al sistema de archivos, así que cambia el método de filesystem.
Solución: Corrige permisos/propiedad para el usuario de runtime en los directorios necesarios, o adopta un flujo de trabajo FTP/SSH (no recomendado para producción).
5) Síntoma: Los permisos parecen bien pero las actualizaciones siguen fallando
Causa raíz: SELinux/AppArmor bloqueando escrituras; o restricciones de open_basedir/directorio temporal.
Solución: Revisa el modo de enforcement y los contextos; comprueba el dir temporal de PHP; ajusta la política para rutas escribibles específicas.
6) Síntoma: Funciona en un nodo, falla en otro (multi-nodo)
Causa raíz: Problemas de consistencia en almacenamiento compartido o mapeos de UID/GID distintos entre nodos.
Solución: Asegura UID/GID consistentes, centraliza actualizaciones, evita actualizaciones desde wp-admin en entornos clusterizados.
7) Síntoma: Solo un plugin/tema falla; otros se actualizan bien
Causa raíz: Ese directorio tiene diferente propiedad, ACL, bandera inmutable o contenido corrupto.
Solución: Inspecciona esa ruta específica con ls -l, getfacl, lsattr. Repara de forma precisa.
8) Síntoma: “No se pudo crear el directorio” durante la instalación, luego “destination folder already exists” al reintentar
Causa raíz: El primer intento creó el directorio pero falló al poblarlo; el segundo intento choca con la carpeta vacía sobrante.
Solución: Elimina/renombra la carpeta vacía, arregla la causa original (permisos/disco/temp), luego reinstala.
Listas de verificación / plan paso a paso (sensato y repetible)
Checklist 1: WordPress en servidor único donde se permiten instalaciones desde wp-admin
- Identifica el slug del plugin/tema que instalas (nombre de carpeta bajo
wp-content). - Comprueba existencia: ¿existe ya el directorio?
- Comprueba salud: ¿es una instalación completa o parcial?
- Comprueba ruta de escritura: ¿puede el usuario de runtime escribir en
wp-contentywp-content/upgrade? - Comprueba disco/inodos: no investigues instalaciones en un sistema de archivos lleno.
- Renombra el directorio si sospechas instalación parcial o corrupción.
- Reinstala con WP-CLI si es posible (es más claro y scriptable).
- Verifica: el plugin se activa, los logs están limpios, el sitio renderiza.
- Limpia: elimina el directorio renombrado tras un periodo de verificación.
Checklist 2: Sistema de producción con CI/CD (recomendado)
- Deshabilita modificaciones de archivos en producción (para que wp-admin no muta el código).
- Haz el docroot solo lectura para el usuario de runtime excepto uploads y un conjunto controlado de dirs.
- Construye artefactos (plugins/temas fijados a versiones) en CI, no en el servidor.
- Despliega de forma atómica (directorios de release + swap de symlink) donde sea posible.
- Plan de rollback debe existir y estar probado (sí, probado).
- Observabilidad: recopila centralmente logs de errores, PHP-FPM y depuración de WordPress.
Paso a paso: recuperación limpia de “destination folder already exists” (plugin)
- Toma un snapshot del sistema de archivos o al menos un tarball rápido de
wp-content/plugins/<slug>y un volcado de la base de datos. - Confirma que el directorio existe e inspecciónalo.
- Renombra
<slug>a<slug>.broken-<date>. - Asegura que el usuario de runtime pueda escribir en
wp-contentywp-content/upgrade. - Instala/reinstala usando WP-CLI con
--force(o instala vía admin una vez eliminada la colisión). - Activa el plugin y realiza una prueba funcional rápida (front-end, wp-admin, tareas cron relevantes).
- Observa los logs durante 10–15 minutos de uso normal.
- Elimina el directorio renombrado cuando estés confiado.
Preguntas frecuentes
1) ¿“Destination folder already exists” significa que el plugin ya está instalado?
A veces. Solo significa que el nombre del directorio existe. Puede ser una instalación sana o un directorio medio extraído de una actualización fallida. Confirma con wp plugin list e inspeccionando el contenido del directorio.
2) ¿Debería borrar la carpeta bajo wp-content/plugins para arreglarlo?
Mejor renombrar primero. Borrar es irreversible y a menudo destruye evidencia que necesitas para entender la falla. Renombra, reinstala, valida y luego borra si procede.
3) ¿Por qué WordPress se niega a sobrescribir la carpeta?
Porque sobrescribir puede ser destructivo si el directorio existente contiene una versión funcional o modificaciones locales. WordPress elige seguridad sobre conveniencia, incluso cuando es inconveniente.
4) ¿WP-CLI puede arreglar esto más rápido que wp-admin?
Sí. WP-CLI te da flags deterministas como --force y salidas de error más claras. También evita timeouts de navegador/proxy durante extracciones grandes.
5) ¿Reinstalar un plugin borrará sus ajustes?
Usualmente los ajustes del plugin viven en la base de datos y sobreviven la reinstalación de archivos. Pero algunos plugins guardan archivos de configuración en su directorio. Haz backup de la carpeta y de la base de datos antes de sobrescribir nada.
6) Arreglé permisos, pero el error persiste. ¿Y ahora?
Revisa denegaciones de SELinux/AppArmor, atributos inmutables y agotamiento de disco/inodos. También valida la estructura del ZIP—carpetas anidadas pueden crear colisiones repetidas.
7) ¿Por qué esto sucede más en configuraciones clusterizadas?
Porque el almacenamiento compartido y múltiples nodos añaden problemas de consistencia y concurrencia. Dos nodos intentando “actualizar” el mismo directorio es como obtienes código medio nuevo y comprobaciones de existencia confusas.
8) ¿Debería permitir que WordPress actualice plugins/temas en producción?
Si gestionas un sitio pequeño en un solo servidor y aceptas el riesgo, puede estar bien con buenas copias de seguridad. Si manejas producción con control de cambios, usa CI/CD y limita las escrituras en runtime.
9) ¿Qué hay de la configuración FS_METHOD de WordPress?
Forzar FS_METHOD a direct puede enmascarar problemas de permisos subyacentes en algunas configuraciones y empeorar la seguridad en otras. Arregla la propiedad/ACL subyacente y deja que WordPress elija direct solo cuando sea apropiado.
Siguientes pasos que puedes hacer hoy
El error “Destination folder already exists” es WordPress diciéndote: “Veo algo en disco y no tengo la confianza para pisarlo.” Trátalo como un interbloqueo de seguridad, no como un insulto.
- Ejecuta la guía rápida de diagnóstico: confirma el slug, inspecciona la carpeta destino, comprueba permisos de escritura, disco/inodos y luego los logs.
- Renombra antes de borrar. Conserva evidencia, reduce el arrepentimiento.
- Arregla la causa (propiedad, directorio de staging, SELinux, dirs temporales), no el síntoma.
- Elige un modelo operativo: o WordPress gestiona plugins/temas (y lo soportas), o tu pipeline lo hace (y WordPress deja de intentarlo).
- Hazlo repetible: convierte los pasos anteriores en un runbook y deja de reaprender la misma lección a las 9 AM.
Si no haces otra cosa: deja de “arreglar” WordPress borrando aleatoriamente contenidos de wp-content. Eso no es operaciones. Es jugar a los dados con mejor marca.