WordPress «Memory exhausted»: aumentar límites de memoria correctamente (donde importa)

¿Te fue útil?

Ese fatal “Allowed memory size exhausted” no es solo molesto. Es el tipo de fallo que aparece a las 2 a. m., justo después de actualizar un plugin y antes del lanzamiento de una campaña. El sitio funciona… hasta que deja de hacerlo. Entonces tira abajo tu administración, el proceso de compra o las tareas programadas.

La solución rara vez es “simplemente subir wp-config”. La memoria en WordPress es una pila de límites a través de PHP, PHP-FPM, contenedores, cgroups y, a veces, el techo invisible del proveedor de hosting. Subir el límite equivocado y nada cambia. Subir el correcto sin entender por qué lo necesitas y habrás ocultado una fuga hasta que se convierta en una interrupción.

Qué significa realmente el error (y qué no)

El choque canónico de WordPress se ve así:

  • “Fatal error: Allowed memory size of X bytes exhausted (tried to allocate Y bytes)”

Ese mensaje viene de PHP, no de WordPress. WordPress solo es el mensajero que recibió el ladrillo. PHP alcanzó un techo estricto y abortó la petición. El techo suele ser memory_limit, pero la causa subyacente puede ser muy diferente:

  • Un plugin/tema que carga demasiados datos en memoria (común: informes de WooCommerce, constructores de páginas, importadores).
  • Un bucle o recursión con arrays/objetos que crecen sin control.
  • Una ruta de procesamiento de imágenes que usa Imagick/GD y mantiene varios bitmaps a tamaño completo.
  • Fragmentación de OPcache o un OPcache pequeño en un ecosistema con muchos plugins (puede parecer lentitud aleatoria o 502s en lugar de “memory exhausted”, pero es la misma clase de fallo).
  • Un contenedor o VM eliminado por el OOM killer del kernel (sin fatal de PHP; solo un worker muerto).
  • Un límite del plan de hosting: subes la memoria de PHP, pero el proveedor ralentiza o mata el proceso igualmente.

Aquí está la clave: subir memoria no es una condición de victoria. Es una herramienta. Subes memoria para (a) volver a estar en línea con seguridad y luego (b) normalizar el perfil de memoria de la petición para poder bajarlo de nuevo. Si solo haces el paso (a), has pospuesto la interrupción y la has hecho más extraña.

Una cita para mantener la honestidad: “La esperanza no es una estrategia.” (Máxima de operaciones, a menudo atribuida a varios líderes de ingeniería; idea parafraseada.)

Datos y contexto interesantes (porque este problema tiene historia)

  1. El memory_limit por defecto de PHP solía ser pequeño. Las versiones antiguas de PHP comúnmente venían con 128M, lo cual bastaba para blogs y era hilarantemente insuficiente para pilas modernas de WordPress.
  2. WordPress tiene dos conceptos de “memoria”. Las constantes WP_MEMORY_LIMIT y WP_MAX_MEMORY_LIMIT pueden solicitar límites mayores, pero no pueden exceder lo que PHP permite.
  3. WooCommerce cambió las expectativas. El comercio electrónico convirtió “un sitio” en “una aplicación”, y las cargas de administración/informes se volvieron lo suficientemente intensas en datos como para necesitar memoria seria.
  4. PHP-FPM normalizó límites por pool. Antes de PHP-FPM, afinar límites por sitio era más engorroso; ahora puedes aislar sitios con configuraciones de pool (cuando controlas el host).
  5. OPcache se convirtió en una característica predeterminada de rendimiento. También introdujo un nuevo modo de fallo: agotamiento y fragmentación del caché de código que puede parecer fallos aleatorios.
  6. Los contenedores cambiaron las reglas. Un contenedor puede tener mucho memory_limit en PHP pero aun así ser eliminado por OOM porque el límite de cgroup es menor.
  7. El hosting compartido añade techos invisibles. Los proveedores suelen imponer límites por cuenta (RSS, segundos CPU, procesos de entrada); subir memory_limit puede ser meramente ceremonial.
  8. WP-Cron no es “cron real”. WP-Cron se activa por tráfico web; cuando ejecuta tareas pesadas dentro de peticiones, los picos de memoria ocurren en momentos inoportunos.

Un chiste corto, porque nos lo hemos ganado: los errores de memoria en WordPress son como el café de oficina—cuando finalmente notas el problema, ya es demasiado tarde y todos están enfadados.

Guía rápida de diagnóstico (comprobar primero/segundo/tercero)

Esta es la lista “deja de adivinar”. Puedes hacerlo en menos de 15 minutos en la mayoría de sistemas.

Primero: ¿es memory_limit de PHP o el SO/contenedor que te mata?

  • Si ves el fatal explícito de PHP “Allowed memory size exhausted”, casi siempre es memory_limit (o una constante de WordPress que no logró aumentarlo).
  • Si ves 502/504 sin fatal de PHP, revisa logs de PHP-FPM y eventos OOM del kernel/cgroup. Eso es otra vía.

Segundo: ¿qué SAPI y configuración se están usando realmente (FPM vs CLI)?

  • php -i es CLI. Tus peticiones web pueden usar un php.ini diferente bajo PHP-FPM.
  • Usa un endpoint o logs para confirmar ajustes en tiempo de ejecución para la SAPI web.

Tercero: identifica la ruta de la petición y el culpable

  • ¿Es solo wp-admin? ¿Solo el checkout? ¿Solo WP-Cron? ¿Solo un endpoint?
  • Activa logging dirigido, reproduce una vez y captura el trace o la lista de plugins que correlacionen con el pico.

Cuarto: elige el nivel de remediación correcto

  • Emergencia: sube el límite efectivo en la capa que lo controla para restaurar el servicio.
  • Estabilizar: aisla con afinación de pool de PHP-FPM, reduce la concurrencia si es necesario.
  • Arreglar la causa raíz: bug en plugin/tema, tamaño de consulta, tamaño de lote en importaciones, estrategia de caché, programación de WP-Cron.

Dónde viven los límites de memoria: la pila que decide tu destino

Piénsalo como puertas para la memoria de WordPress. La puerta más pequeña gana. Puedes pintar “512M” en la puerta de WordPress todo el día; si la puerta de PHP es 128M, igual no pasarás.

Puerta 1: constantes de WordPress (solicitudes, no garantías)

  • WP_MEMORY_LIMIT: pensado para el front-end.
  • WP_MAX_MEMORY_LIMIT: pensado para tareas de administración (actualizaciones, importaciones, etc.).

Estas constantes pueden aumentar el límite hasta el techo de PHP, dependiendo de si PHP permite cambiar memory_limit en tiempo de ejecución.

Puerta 2: ajuste de tiempo de ejecución de PHP (el techo real)

memory_limit es un ajuste de PHP, configurado vía php.ini, configuración del pool FPM, configuración de Apache, o overrides por directorio (dependiendo de SAPI y permisos). Este suele ser el ajuste que produce el mensaje “Allowed memory size exhausted”.

Puerta 3: configuración del pool PHP-FPM (aislamiento por sitio)

Si gestionas tu propio host (VM/bare metal), los pools de PHP-FPM te permiten fijar límites y comportamientos por sitio. Puedes imponer php_admin_value[memory_limit] (no sobreescribible por scripts), o permitir php_value (sobreescribible).

Puerta 4: restricciones del gestor de procesos (concurrencia)

La agotación de memoria no es solo “una petición grande”. A menudo es “muchas peticiones moderadamente grandes”. Ajustes de FPM como pm.max_children deciden cuántos workers pueden ejecutarse concurrentemente. Si cada worker puede usar hasta 512M, y permites 20 workers, acabas prometiendo al SO 10GB de memoria. Al SO no le hará gracia.

Puerta 5: SO, cgroups y el OOM killer

En contenedores y muchas plataformas gestionadas, el límite real es el límite de cgroup. El kernel puede matar workers de PHP-FPM (o todo el contenedor) sin un fatal de PHP. En Kubernetes verás OOMKilled. En servicios gestionados por systemd verás contabilidad y aplicación de memoria.

Puerta 6: políticas del proveedor de hosting

Hosts compartidos y algunas plataformas gestionadas aplican límites por cuenta (RSS, CPU seconds, entry processes). Podrías establecer memory_limit a 512M y aún así ser matado o ralentizado porque el límite del proveedor es menor o porque tu plan lo prohíbe.

Tareas prácticas (comandos, salidas y decisiones)

Estos son chequeos de grado producción. Cada uno incluye: comando, salida de ejemplo, qué significa y la decisión a tomar.

Tarea 1: Confirma la versión de PHP y la SAPI usada por CLI (línea base)

cr0x@server:~$ php -v
PHP 8.2.12 (cli) (built: Oct 12 2024 10:11:21) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.12, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.12, Copyright (c), by Zend Technologies

Significado: Este es PHP CLI. Útil para trabajos WP-CLI, no definitivo para peticiones web.

Decisión: Si el error ocurre vía web, no asumas que esta configuración aplica. Pasa a las comprobaciones de FPM/web.

Tarea 2: Encuentra qué php.ini usa CLI (evita espejismos de configuración)

cr0x@server:~$ php --ini
Configuration File (php.ini) Path: /etc/php/8.2/cli
Loaded Configuration File:         /etc/php/8.2/cli/php.ini
Scan for additional .ini files in: /etc/php/8.2/cli/conf.d
Additional .ini files parsed:      /etc/php/8.2/cli/conf.d/10-opcache.ini

Significado: Ahora sabes de dónde lee la configuración CLI.

Decisión: Si WP-CLI falla con memory exhausted, puedes ajustar esto. Si la web falla, necesitas la configuración de FPM a continuación.

Tarea 3: Lee el memory_limit efectivo para CLI

cr0x@server:~$ php -r 'echo ini_get("memory_limit").PHP_EOL;'
128M

Significado: CLI está limitado a 128M.

Decisión: Si WP-CLI importa o cron jobs fallan, aumenta el memory_limit de CLI (o ejecuta con -d memory_limit=... para tareas puntuales).

Tarea 4: Confirma que el servicio PHP-FPM esté presente y en ejecución (vía web)

cr0x@server:~$ systemctl status php8.2-fpm --no-pager
● php8.2-fpm.service - The PHP 8.2 FastCGI Process Manager
     Loaded: loaded (/lib/systemd/system/php8.2-fpm.service; enabled; preset: enabled)
     Active: active (running) since Fri 2025-12-27 08:41:10 UTC; 2h 11min ago
       Docs: man:php-fpm8.2(8)
   Main PID: 1042 (php-fpm8.2)
      Tasks: 23 (limit: 18963)
     Memory: 612.4M

Significado: FPM está en ejecución; puedes inspeccionar configs de pool y logs.

Decisión: Si ves un “Memory:” grande aquí y estás cerca de los límites de la máquina, revisa pm.max_children y la memoria por worker.

Tarea 5: Localiza las configuraciones activas de pools de FPM

cr0x@server:~$ grep -R "^\[" -n /etc/php/8.2/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/www.conf:1:[www]
/etc/php/8.2/fpm/pool.d/site.conf:1:[site]

Significado: Tienes múltiples pools. Excelente para aislamiento; también excelente para confusiones.

Decisión: Identifica qué pool sirve el vhost de WordPress y afina ese pool, no el equivocado.

Tarea 6: Comprueba el memory_limit de la SAPI web vía settings del pool FPM

cr0x@server:~$ grep -R "memory_limit" -n /etc/php/8.2/fpm/pool.d/*.conf
/etc/php/8.2/fpm/pool.d/site.conf:38:php_admin_value[memory_limit] = 256M

Significado: Este pool impone 256M, y los scripts no pueden subirlo porque es un admin value.

Decisión: Si WordPress pide 512M pero aún muere a 256M, esta línea es la razón. Cámbiala aquí y recarga FPM.

Tarea 7: Valida si el error está en logs de PHP (no solo en el navegador)

cr0x@server:~$ sudo tail -n 50 /var/log/php8.2-fpm.log
[27-Dec-2025 10:38:55] WARNING: [pool site] child 2517 said into stderr: "PHP Fatal error:  Allowed memory size of 268435456 bytes exhausted (tried to allocate 1048576 bytes) in /var/www/html/wp-includes/class-wpdb.php on line 2345"

Significado: Este es el registro autorizador: techo de 256M (268,435,456 bytes) alcanzado durante trabajo de base de datos.

Decisión: Sube el memory_limit web (o arregla la consulta/plugin). También considera por qué wpdb está cargando tanto (informes, búsqueda, opciones autoload gigantes).

Tarea 8: Revisa kills de OOM del kernel (el escenario “sin fatal de PHP”)

cr0x@server:~$ sudo dmesg -T | tail -n 20
[Sat Dec 27 10:41:02 2025] Out of memory: Killed process 2517 (php-fpm8.2) total-vm:1462256kB, anon-rss:912324kB, file-rss:0kB, shmem-rss:0kB, UID:33 pgtables:3112kB oom_score_adj:0
[Sat Dec 27 10:41:02 2025] oom_reaper: reaped process 2517 (php-fpm8.2), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

Significado: El kernel mató un worker PHP-FPM. Esto no es un evento de memory_limit de PHP; es presión de memoria del nodo/contenedor.

Decisión: Reduce concurrencia (pm.max_children), aumenta memoria del host, baja el memory_limit por worker, o arregla el uso de memoria de la petición. Si estás en contenedores, sube los límites de cgroup también.

Tarea 9: Inspecciona el límite de memoria del cgroup del contenedor (Docker / systemd / cgroups v2)

cr0x@server:~$ cat /sys/fs/cgroup/memory.max
536870912

Significado: El grupo de procesos está limitado a 512MB en total. Si ajustas PHP memory_limit a 512M y permites múltiples workers, obtendrás kills por OOM.

Decisión: O sube el límite de cgroup, o fija memory_limit más bajo (por ejemplo, 256M) y ajusta la concurrencia de FPM para encajar.

Tarea 10: Mide uso real de memoria por child de PHP-FPM (deja de adivinar)

cr0x@server:~$ ps -o pid,rss,cmd -C php-fpm8.2 --sort=-rss | head -n 8
  PID   RSS CMD
 2519 412980 php-fpm: pool site
 2518 356112 php-fpm: pool site
 2516 198344 php-fpm: pool site
 1042  65244 php-fpm: master process (/etc/php/8.2/fpm/php-fpm.conf)

Significado: RSS es la memoria residente real. Tienes workers en el rango de 200–400MB.

Decisión: Usa el RSS de percentil alto (no el mejor caso) para dimensionar pm.max_children. Si dos workers ya pueden alcanzar 400MB cada uno, no pongas 10 en una máquina de 2GB.

Tarea 11: Comprueba la visión de WordPress sobre la memoria (es informativa, no autoritativa)

cr0x@server:~$ wp --path=/var/www/html option get home
https://example.internal

Significado: WP-CLI puede comunicarse con el código del sitio. Ahora puedes ejecutar diagnósticos.

Decisión: Si WP-CLI falla con errores de memoria, ejecútalo temporalmente con un límite mayor para diagnóstico.

cr0x@server:~$ wp --path=/var/www/html --info
OS:     Linux 6.5.0-1022-aws #24~22.04.1-Ubuntu SMP
Shell:  /bin/bash
PHP binary:     /usr/bin/php8.2
PHP version:    8.2.12
php.ini used:   /etc/php/8.2/cli/php.ini
WP-CLI root dir:        /home/cr0x/.wp-cli
WP-CLI version: 2.10.0

Significado: Confirma de nuevo que esto es CLI. Útil, pero no confundas con el comportamiento web.

Decisión: Si la interrupción de producción es solo web, enfócate en ajustes de FPM y logs web.

Tarea 12: Identifica rápidamente plugins pesados (y desactívalos con seguridad si es necesario)

cr0x@server:~$ wp --path=/var/www/html plugin list --status=active
+--------------------------+--------+-----------+---------+
| name                     | status | update    | version |
+--------------------------+--------+-----------+---------+
| woocommerce              | active | available | 8.6.1   |
| elementor                | active | none      | 3.20.2  |
| wordfence                | active | none      | 7.11.2  |
| wp-mail-smtp             | active | none      | 4.4.0   |
+--------------------------+--------+-----------+---------+

Significado: Tienes un reparto conocido: ecommerce, un page builder, un plugin de seguridad. Cualquiera de estos puede aumentar la memoria en ciertos endpoints.

Decisión: Si necesitas recuperación de emergencia, desactiva el plugin sospechoso (uno a la vez) tras hacer snapshot/backup y comunicar el impacto.

Tarea 13: Comprueba el tamaño de opciones autoload (ladrón silencioso de memoria)

cr0x@server:~$ wp --path=/var/www/html db query "SELECT ROUND(SUM(LENGTH(option_value))/1024/1024,2) AS autoload_mb FROM wp_options WHERE autoload='yes';"
+------------+
| autoload_mb|
+------------+
| 12.47      |
+------------+

Significado: 12.47MB de opciones autoload es alto. Las opciones autoload se cargan en muchas peticiones, incrementando la memoria base.

Decisión: Audita opciones grandes, reduce autoload donde sea seguro y considera caché de objetos.

Tarea 14: Encuentra las principales opciones autoloaded (objetivos para limpieza)

cr0x@server:~$ wp --path=/var/www/html db query "SELECT option_name, ROUND(LENGTH(option_value)/1024/1024,2) AS mb FROM wp_options WHERE autoload='yes' ORDER BY LENGTH(option_value) DESC LIMIT 10;"
+------------------------------+------+
| option_name                  | mb   |
+------------------------------+------+
| _transient_timeout_wc_report | 3.21 |
| elementor_css                | 2.44 |
| wordfence_waf_options        | 1.18 |
| rewrite_rules                | 0.94 |
+------------------------------+------+

Significado: Transients y blobs generados por builders viven en autoload. Eso inflama la memoria cada vez que WordPress arranca.

Decisión: Arregla la fuente (configuración del plugin), purga/reconstruye donde proceda y asegura que los transients no se autoloaden permanentemente.

Tarea 15: Comprueba el estado de OPcache (cuando “memoria” es realmente presión del caché de código)

cr0x@server:~$ php -r 'print_r(opcache_get_status(false)["memory_usage"]);'
Array
(
    [used_memory] => 118901488
    [free_memory] => 1562032
    [wasted_memory] => 4381656
    [current_wasted_percentage] => 3.55
)

Significado: OPcache tiene ~1.5MB libres. Eso es justo. Con despliegues frecuentes o muchos plugins puedes chocar contra el muro.

Decisión: Aumenta opcache.memory_consumption y considera opcache.max_accelerated_files. Revisa también la estrategia de recarga para reducir fragmentación.

Tarea 16: Confirma ajustes del process manager de FPM actuales (matemáticas de concurrencia)

cr0x@server:~$ grep -nE "^(pm\.|php_admin_value\[memory_limit\])" /etc/php/8.2/fpm/pool.d/site.conf
23:pm = dynamic
24:pm.max_children = 20
25:pm.start_servers = 4
26:pm.min_spare_servers = 4
27:pm.max_spare_servers = 8
38:php_admin_value[memory_limit] = 256M

Significado: Puedes ejecutar 20 workers, cada uno permitido 256M. Eso es una asignación teórica de 5GB en el peor caso, más overhead.

Decisión: En un host de 2–4GB, esto es una interrupción esperando ocurrir. Reduce pm.max_children o baja la memoria por worker, luego prueba con carga.

Tarea 17: Recarga FPM de forma segura después de cambios (no reinicies en producción salvo que haga falta)

cr0x@server:~$ sudo systemctl reload php8.2-fpm

Significado: FPM recarga la configuración de forma gradual; las peticiones existentes terminan y las nuevas usan los ajustes actualizados.

Decisión: Prefiere reload sobre restart durante horas de negocio, a menos que necesites un reinicio forzoso (por ejemplo, workers bloqueados).

Tarea 18: Verifica el memory_limit efectivo mediante una petición web (la verdad en terreno)

cr0x@server:~$ curl -sS -H "Host: example.internal" http://127.0.0.1/wp-admin/admin-ajax.php?action=heartbeat | head
0

Significado: La web responde. Esto no muestra memory_limit directamente, pero es una comprobación rápida tras cambios.

Decisión: Si el error fue en endpoints de administración, reproduce la acción exacta que fallaba y monitoriza logs por fatales relacionados con memoria.

Subir límites correctamente: recetas por modelo de hosting

Aquí la regla: fija los límites lo más cerca posible de la capa que los aplica. Las constantes de WordPress sirven para solicitar. No son gobierno.

Escenario A: Controlas la VM/bare metal (Nginx/Apache + PHP-FPM)

1) Fija memory_limit por pool (preferido)

Edita tu archivo de pool (ejemplo: /etc/php/8.2/fpm/pool.d/site.conf):

  • Usa php_admin_value[memory_limit] = 256M (o 384M/512M) cuando quieras que sea obligatorio.
  • Usa php_value[memory_limit] = 256M si quieres que las apps puedan sobreescribirlo (normalmente no lo haces).

Luego recarga FPM y confirma que los logs dejan de mostrar agotamientos.

2) Afina la concurrencia con pm.max_children

Subir memory_limit sin ajustar concurrencia es cómo conviertes un fallo por una sola petición en un OOM de nodo completo. Haz las cuentas:

  • Mide el RSS real por worker (Tarea 10).
  • Elige un presupuesto seguro: deja espacio para cache del SO, base de datos y servidor web.
  • Fija pm.max_children para que RSS peor-caso × children quepa en el presupuesto.

3) Mantén las constantes de WordPress modestas (y honestas)

En wp-config.php puedes poner:

  • define('WP_MEMORY_LIMIT', '256M');
  • define('WP_MAX_MEMORY_LIMIT', '512M');

Esto ayuda a WordPress a pedir más memoria en tareas de administración sin dar a cada petición front-end un cheque en blanco. Si tu pool de PHP-FPM impone 256M, fijar WP_MAX_MEMORY_LIMIT a 512M no servirá. Esa discrepancia es tu señal: decide si la administración realmente necesita 512M o si el plugin/informe debe arreglarse.

Escenario B: Hosting compartido (estilo cPanel) con control limitado

Aquí es donde los consejos de Internet mueren. Puede que no controles pools de PHP-FPM. Tu proveedor puede usar suPHP, LSAPI o su propio wrapper.

Orden de operaciones:

  1. Comprueba qué puedes cambiar (MultiPHP INI Editor, ajustes PHP por dominio, etc.).
  2. Intenta subir memory_limit en la UI del proveedor (lo mejor).
  3. Si está permitido, añade o edita .user.ini con memory_limit = 256M. Atención: los cambios pueden tardar minutos en aplicarse.
  4. Si usas Apache mod_php y está permitido, usa directivas en .htaccess (frecuentemente deshabilitadas en hosts modernos).
  5. Si nada de lo anterior funciona, acepta la realidad: has chocado con los límites del plan. Optimiza o migra/sube de plan.

Dos advertencias:

  • No pongas 1024M a ciegas por haber leído un post de 2016. No solucionará un tope del proveedor y ocultará una fuga de plugin hasta que tu cuenta sea sancionada.
  • No asumas que los logs de errores son completos. El hosting compartido puede suprimir señales clave. Usa logging a nivel de aplicación cuando sea posible.

Escenario C: Docker / Compose

En contenedores tienes al menos dos límites: el memory_limit de PHP y el límite de memoria del contenedor. Deben estar alineados.

Cómo debería ser:

  • Límite de memoria del contenedor dimensionado para manejar la concurrencia pico.
  • Número de workers de PHP-FPM limitado para evitar OOMKills por cgroup.
  • memory_limit de PHP por debajo del umbral “demasiado grande para fallar” por worker.

Si tu contenedor está limitado a 512MB totales, fijar memory_limit de PHP a 512M es como comprar una maleta exactamente del tamaño del límite de la aerolínea y luego meter un ladrillo. Cierra justo hasta que no lo hace.

Escenario D: Kubernetes (WordPress detrás de un Ingress)

En Kubernetes debes tratar la memoria como un contrato de recursos:

  • Requests/limits determinan programación y aplicación. Si el limit es bajo, el pod será OOMKilled bajo carga.
  • Fija la concurrencia de PHP-FPM para que coincida con lo que tu pod puede soportar.
  • Monitorea eventos OOMKilled, bucles de reinicio y picos de 502 por timeouts desde upstream.

Si no puedes hacer predecible la memoria, probablemente tienes un endpoint de plugin descontrolado: demasiadas filas cargadas, HTML generado en exceso o una exportación que intenta construir un archivo completo en RAM.

Cuando subir límites es la solución equivocada: plugins, temas y fugas reales

La mayoría de incidentes “memory exhausted” comienzan con un cambio: actualización de un plugin, un nuevo informe, una importación masiva o una función de tema que se ejecuta en cada petición.

Cómo saber que estás enmascarando una fuga

  • El uso de memoria crece gradualmente con el tiempo entre workers, no solo durante un endpoint.
  • La misma petición a veces tiene éxito y a veces falla, dependiendo del estado cacheado o de rutas de código en OPcache.
  • Subir la memoria “arregla” el problema durante días y luego vuelve peor.

Culpables típicos (y el patrón detrás de ellos)

  • Constructores de informes: cargan tablas enteras en arrays PHP. La solución es paginación, límites en consultas, exportaciones por streaming o mover trabajo a jobs asíncronos.
  • Importadores: leen CSV/XML completos en memoria. La solución es procesamiento por trozos y tamaños de lote menores.
  • Constructores de página: meta de post y blobs JSON masivos. La solución es recortar plantillas, caché y controlar revisiones.
  • Plugins de seguridad: escaneos pesados, reglas WAF, escrituras frecuentes en options. La solución es ajustar configuración, programar escaneos fuera de pico y evitar almacenar caches en autoload.
  • Transients sin límite: datos “temporales” que se vuelven permanentes. La solución es limpieza y expiraciones correctas.

Enfoque práctico: estabilizar, luego aislar

En producción no empiezas desinstalando la mitad de los plugins. Empiezas haciendo el sistema lo bastante estable como para depurarlo.

  1. Sobe el límite efectivo lo justo para detener el crash.
  2. Reduce la concurrencia si el host está en riesgo de OOM.
  3. Reproduce la acción fallida en staging con el mismo tamaño de datos.
  4. Desactiva plugins sospechosos uno a la vez, midiendo memoria y tiempo de petición.
  5. Arregla la causa raíz o reemplaza el plugin. “Pero lo pagamos” no es una estrategia de rendimiento.

OPcache y falsos positivos de “memory exhausted”

A veces los usuarios reportan “memory exhausted” pero lo que ves son 502s, administración lenta y mensajes en logs sobre OPcache. Síntoma diferente, misma clase: presión de memoria.

Modo de fallo de OPcache que debes reconocer

  • Tras muchos despliegues o actualizaciones de plugins, OPcache se fragmenta.
  • Los nuevos archivos PHP no se cachean, el rendimiento cae y la CPU de los workers sube.
  • Recargar FPM lo “arregla” temporalmente porque resetea OPcache.

OPcache no es opcional en sitios WordPress con tráfico. Pero necesita suficiente memoria y slots de archivos para cachear tu base de código. Un WordPress moderno con WooCommerce y unas decenas de plugins puede fácilmente agotar valores conservadores por defecto.

Tres microhistorias corporativas desde el frente

1) El incidente causado por una suposición equivocada

Acababan de migrar una cartera de sitios WordPress desde un host compartido viejo a una nueva VM brillante. El equipo hizo lo obvio: puso define('WP_MEMORY_LIMIT','512M'); en wp-config.php en todos lados. También escribieron una nota interna rápida: “Problemas de memoria resueltos.”

Dos semanas después, durante una ventana programada de actualización de plugins, la mitad de los sitios empezaron a devolver errores 502. El on-call no vio mensajes “Allowed memory size exhausted” en el navegador y empezó a perseguir timeouts de Nginx, ajustes de keepalive upstream y DNS. Fue un tour completo por todo excepto el problema.

La causa raíz fue banal: los pools de PHP-FPM estaban configurados con php_admin_value[memory_limit] = 128M, heredado de una baseline de hardening. WordPress no pudo sobreescribirlo. Las peticiones de actualización administrativas agotaban la memoria y mataban workers. Nginx reportó 502 porque el upstream murió a mitad de petición.

La solución tomó diez minutos una vez que miraron en el lugar correcto: subir el límite del pool a 256M para los sitios afectados, recargar FPM y volver a ejecutar actualizaciones. La lección real tomó más tiempo: nunca trates las constantes de WordPress como la fuente de la verdad. Son una petición; la plataforma decide.

2) La optimización que resultó contraproducente

Un manager de ingeniería quería más throughput durante un lanzamiento de producto. El equipo aumentó pm.max_children de forma agresiva. Más workers significa más peticiones en paralelo, ¿no? La CPU estaba bien en pruebas y la página principal se sentía más rápida. Victoria, por cerca de un día.

Entonces salió la campaña de marketing. El tráfico de checkout se disparó y también la memoria. Algunos endpoints de WooCommerce alcanzaban varios cientos de megabytes por petición con carritos reales y plugins reales. Con el mayor número de children, el nodo se quedó sin RAM rápidamente. El kernel comenzó a matar workers. Las peticiones se reintentaron. La carga aumentó. El sistema se hundió en una denegación de servicio auto-infligida.

Revirtieron pm.max_children y subieron PHP memory_limit “por seguridad”. Eso ayudó un poco, pero solo porque redujo la frecuencia de fatales de PHP mientras mantenía el nodo en zona peligrosa.

La configuración estable eventual fue aburrida: medir el RSS pico por worker, fijar pm.max_children según presupuesto del host y limitar memory_limit para que una sola petición descontrolada no se coma el nodo. También movieron generación de informes pesados fuera de peticiones síncronas de admin a jobs en cola.

3) La práctica aburrida pero correcta que salvó el día

Otra compañía ejecutaba WordPress como parte de una plataforma mayor. Nada glamuroso: pools separados por sitio, valores conservadores y un proceso de cambios que requería una cosa antes de afinar—medición.

Tenían un script pequeño que registraba marcas máximas de memoria para peticiones lentas y las correlacionaba con rutas de endpoint. No era un APM sofisticado. Sirvió. Cuando una nueva versión de un plugin empezó a consumir más memoria en wp-admin/admin-ajax.php, el equipo detectó el cambio en horas.

En lugar de subir todos los sitios a 512M, aumentaron la memoria solo para el pool afectado, redujeron su concurrencia y abrieron un ticket al proveedor del plugin con un dataset reproducible. Mientras tanto, el resto de la flota se mantuvo estable y barato.

En el día del lanzamiento, cuando el tráfico se duplicó, no descubrieron comportamientos nuevos de memoria. Ya tenían el sobre. El sitio se mantuvo activo. Nadie escribió un postmortem heroico, que es el mayor elogio en operaciones.

Errores comunes: síntoma → causa raíz → arreglo

1) “Puse WP_MEMORY_LIMIT a 512M pero sigue muriendo a 128M”

Síntoma: El fatal dice 134,217,728 bytes exhausted (128M) a pesar de la configuración de WordPress.

Causa raíz: El pool PHP-FPM usa php_admin_value[memory_limit] o el php.ini global es más bajo; los cambios en tiempo de ejecución están bloqueados.

Arreglo: Eleva memory_limit en la capa que lo controla (pool FPM/php.ini/UI del host), recarga FPM, confirma vía logs.

2) “Recibimos 502/504, sin fatal de PHP”

Síntoma: Nginx reporta errores upstream; logs de PHP están tranquilos.

Causa raíz: Worker matado por OOM del SO, FPM colapsó o upstream hizo timeout. No es un evento de memory_limit de PHP.

Arreglo: Revisa dmesg para kills OOM, revisa logs de FPM, reduce pm.max_children, alinea límites de contenedor y arregla endpoints de alta memoria.

3) “Solo falla wp-admin, el front-end está bien”

Síntoma: Actualizaciones/imports/informes administrativos fallan; los visitantes pueden navegar.

Causa raíz: Tareas administrativas usan datasets mayores y pueden solicitar WP_MAX_MEMORY_LIMIT, pero el límite del pool es demasiado bajo o la ruta admin activa plugins pesados.

Arreglo: Asigna un presupuesto de memoria mayor para administración si está justificado y optimiza el flujo administrativo específico (batching, paginación, exportaciones).

4) “Empezó después de activar un plugin de seguridad”

Síntoma: Fatales de memoria aleatorios, peticiones lentas, tabla de options grande.

Causa raíz: Funciones WAF/escaneo aumentan overhead por petición; options/transients almacenados de forma inadecuada.

Arreglo: Ajusta configuración del plugin, programa escaneos fuera de horas pico, mueve caches fuera de autoload y considera caché de objetos.

5) “Subir memoria lo arregló… hasta el día siguiente”

Síntoma: Incidentes recurrentes con línea base de memoria en aumento o nuevas páginas que lo disparan.

Causa raíz: Comportamiento tipo fuga (arrays sin límite, opciones autoload enormes, cron desbocado) o crecimiento del dataset.

Arreglo: Identifica el endpoint, perfila memoria, arregla crecimiento de datos (limpieza de autoload) y pon guardrails en importaciones/informes.

6) “WP-CLI funciona, el sitio web sigue fallando”

Síntoma: Comandos CLI exitosos; peticiones web fallan.

Causa raíz: php.ini y memory_limit diferentes entre CLI y FPM.

Arreglo: Ajusta FPM pool/php.ini para la web; trata CLI como tiempo de ejecución separado.

Segundo chiste corto, y volvamos al trabajo: Poner memory_limit a 2G sin medir es como arreglar una tubería con goteras comprando un sótano más grande.

Listas de verificación / plan paso a paso

Recuperación de emergencia (restaura el sitio sin empeorarlo)

  1. Confirma el modo de fallo. Fatal de PHP vs OOMKilled del SO/contenedor vs timeout.
  2. Sube el límite efectivo en la capa que lo aplica. Para FPM: config del pool. Para contenedores: cgroup más PHP.
  3. Recarga, no reinicies, cuando sea posible. Reduce impacto al cliente.
  4. Reduce la concurrencia si estás cerca de límites del host. Baja pm.max_children para evitar OOM del nodo.
  5. Captura evidencia. Línea de log con bytes agotados, endpoint y contexto del plugin.

Estabilización (hazlo predecible)

  1. Mide la distribución de RSS de workers. Usa ps para obtener marcas máximas reales.
  2. Dimensiona el pool FPM. Fija pm.max_children según memoria, no según esperanza.
  3. Fija un memory_limit sensato. Valores típicos en producción: 256M–512M según carga, pero solo después de medir.
  4. Audita opciones autoload. Reduce la inflación de memoria base.
  5. Revisa el tamaño de OPcache. Evita la inanición del caché de código.

Arreglos de causa raíz (donde realmente ganas)

  1. Localiza el endpoint. ¿Admin-ajax? ¿Informes? ¿Import? ¿Búsqueda de productos?
  2. Prueba desactivar plugins sospechosos. Uno a la vez, con rollback controlado.
  3. Optimiza acceso a datos. Añade paginación, limita exportaciones, evita cargar sets completos.
  4. Mueve trabajo pesado a asíncrono. Jobs en background en lugar de peticiones síncronas de admin.
  5. Revisa restricciones del hosting. Si estás en hosting compartido y chocas con límites invisibles, planifica una migración.

Preguntas frecuentes

1) ¿Siempre se arregla “Allowed memory size exhausted” subiendo memory_limit?

No. Se arregla asegurando que la petición use menos memoria que el límite. Subir el límite puede ser un parche válido, pero las fugas y cargas no acotadas necesitan arreglos de código/datos.

2) ¿Cuál es un memory_limit razonable para WordPress en 2025?

256M es una base común para sitios sin ecommerce. 384M–512M es común para WooCommerce y tareas administrativas intensas. Si necesitas 1G de forma rutinaria, trátalo como síntoma e investiga.

3) ¿Por qué wp-config.php a veces no funciona?

Porque PHP-FPM (o el host) puede imponer memory_limit con php_admin_value o políticas del proveedor. WordPress solo puede solicitar aumentos si PHP lo permite.

4) ¿Por qué el fatal muestra bytes en lugar de megabytes?

PHP reporta memoria en bytes. 128M son 134,217,728 bytes; 256M son 268,435,456 bytes; 512M son 536,870,912 bytes. Úsalo para identificar el techo real.

5) El sitio solo falla durante importaciones. ¿Debería subir memoria permanentemente?

Mejor ejecuta importaciones en lotes más pequeños o via CLI con un límite temporal mayor. Límites altos permanentes aumentan el radio de daño de bugs y picos de tráfico.

6) ¿Cómo sé si el límite del contenedor es el problema real?

Si ves eventos OOMKilled, reinicios de contenedor o logs del kernel sin fatales de PHP, estás chocando con el límite de cgroup/OS. Alinea memoria del contenedor, memory_limit de PHP y concurrencia de FPM.

7) ¿OPcache puede causar errores “memory exhausted”?

El agotamiento de memoria de OPcache es distinto del memory_limit de la petición PHP, pero puede causar inestabilidad y colapso de rendimiento que parece fallos aleatorios. Dimensiona OPcache al footprint de plugins/tema.

8) ¿Debo poner WP_MAX_MEMORY_LIMIT más alto que WP_MEMORY_LIMIT?

Sí, a menudo. Las peticiones front-end deben estar limitadas; las tareas administrativas pueden necesitar más margen. Pero solo funciona si PHP lo permite y tu host tiene RAM disponible.

9) ¿Qué hago si estoy en hosting compartido y no puedo cambiar memory_limit?

En ese caso tu límite real es tu plan. Optimiza la carga (desactiva plugins pesados, reduce autoload, segmenta importaciones) y planifica una subida o migración si el negocio lo requiere.

10) ¿Subir pm.max_children es una buena solución para errores de memoria?

Generalmente al contrario. Más children aumenta consumo total de memoria. Sube ese valor solo cuando hayas medido el RSS por worker y tengas presupuesto RAM disponible.

Próximos pasos que puedes hacer hoy

  1. Decide qué fallo tienes: fatal de PHP memory_limit vs OOM del SO/contenedor vs timeout.
  2. Encuentra la capa que impone el límite: pool PHP-FPM, php.ini, límite de contenedor o tope del hosting.
  3. Sube el límite correcto, mínimamente: lo justo para restaurar el servicio, no lo suficiente para ocultar una fuga meses.
  4. Haz las cuentas de concurrencia: RSS por worker × max_children debe caber en la máquina con margen para respirar.
  5. Busca al culpable real: plugins pesados, autoload inflado, informes/importaciones sin límite o comportamiento de cron.

Si no haces otra cosa, haz esto: deja de tratar configuraciones de memoria de WordPress como autoritativas. La plataforma decide. Tu trabajo es hacer esa decisión explícita, medida y segura.

← Anterior
Ruleta del cable HDMI: idénticos por fuera, diferentes por dentro
Siguiente →
OpenVPN en Windows: problemas con el controlador TAP y cómo repararlos

Deja un comentario