PHP-FPM: El portero que decide cuántos usuarios puede atender tu WordPress al mismo tiempo

La serie arrancó con Redis y Memcached, siguió con el servidor web como reverse proxy y cerró con la caché en el edge. Tres capas, tres artículos. Y una que se quedó fuera.

No fue deliberado. Simplemente, al recorrer el stack de afuera hacia adentro, PHP-FPM quedó en el tintero. Y es precisamente la capa que vive entre Nginx y WordPress, la que decide cuántos procesos PHP pueden ejecutarse al mismo tiempo. Sin entenderla, el resto del stack optimizado puede seguir colapsando bajo carga.

Este es ese capítulo que faltaba.

0. El síntoma que esta vez sí es urgente

Todo parece bien configurado. Redis está activo y los cache hits son altos. Nginx está delante filtrando tráfico. Y aun así, ocurre esto:

  • Bajo carga, el servidor responde con 502 Bad Gateway o 504 Gateway Timeout
  • La CPU apenas llega al 40%… pero el sitio colapsa igual
  • WooCommerce funciona bien con 5 usuarios simultáneos, pero con 20 todo se degrada
  • El panel de administración se vuelve inutilizable en horas pico

Si ese es tu escenario, el cuello de botella probablemente no está en la base de datos ni en el proxy. Está en cuántos procesos PHP pueden ejecutarse en paralelo.

1. Dónde encaja PHP-FPM en el stack

La serie ha ido construyendo el stack de afuera hacia adentro. Mirándolo completo:

graph TD
    A[Cliente] --> B[CDN / Edge Cache]
    B --> C[Nginx / Apache]
    C --> D[PHP-FPM]
    D --> E[WordPress]
    E --> F[(MySQL / Redis)]
    

Ya conocemos el papel de cada capa. El CDN resuelve peticiones desde el edge sin tocar el servidor. Nginx filtra, cachea y protege antes de que PHP intervenga. Redis evita que WordPress bombardee MySQL con consultas repetitivas.

Pero cuando una petición pasa todos esos filtros y llega a PHP, un login, una carga del carrito, una llamada a la REST API, cualquier contenido dinámico que no esté cacheado, PHP-FPM es quien decide si hay capacidad para atenderla o no.

2. Los workers: el concepto que lo explica todo

PHP-FPM significa PHP FastCGI Process Manager. Su trabajo es mantener un grupo de procesos PHP listos para ejecutar código, denominados workers.

Cuando Nginx le entrega una petición dinámica, un worker libre la toma y ejecuta el ciclo completo de WordPress:

  1. Carga el núcleo, los plugins y el tema activo.
  2. Inicializa hooks y funciones.
  3. Consulta Redis o MySQL según lo que necesite.
  4. Renderiza el HTML final.
  5. Devuelve la respuesta a Nginx para enviarla al usuario.

Cada worker es un proceso independiente que consume RAM, CPU y tiempo de ejecución. Y solo puede atender una petición a la vez.

La consecuencia directa: el número de workers define cuántas peticiones dinámicas puede procesar tu servidor en paralelo. No más.

3. El parámetro que más importa: pm.max_children

El valor pm.max_children en la configuración de PHP-FPM define el número máximo de workers que pueden existir al mismo tiempo.

Con pm.max_children = 10, tu servidor puede procesar exactamente 10 peticiones dinámicas en paralelo.

¿Qué pasa con la petición número 11? Entra en una cola de espera controlada por listen.backlog, un parámetro que en muchos sistemas tiene un valor sorprendentemente bajo, a veces 511 o incluso 65. Cuando esa cola también se llena, el servidor empieza a rechazar peticiones directamente. Ahí aparecen los errores 502 y 504.

Este es el fenómeno más engañoso del diagnóstico de WordPress bajo carga: un servidor puede tener la CPU al 40% y aun así estar colapsado. El cuello de botella no es capacidad de cómputo, es falta de workers disponibles.

4. Más workers no es siempre la solución

La reacción habitual cuando el servidor colapsa es subir pm.max_children a un número grande. El problema es que cada worker consume entre 40 MB y 120 MB de RAM dependiendo de la complejidad del sitio, y si creas más workers de los que tu RAM puede sostener, el servidor empieza a usar swap en disco.

Cuando eso ocurre, la latencia se dispara. Y en muchos VPS modernos donde el swap está desactivado, el kernel directamente mata procesos, el temido OOM Killer, con resultados más destructivos que el problema original.

La fórmula correcta es:

pm.max_children = RAM disponible para PHP ÷ consumo promedio por worker

Ejemplo: 2 GB asignados a PHP, con workers que consumen ~80 MB cada uno:

2048 MB ÷ 80 MB ≈ 25 workers

Pero para aplicar esa fórmula necesitas saber cuánto consume realmente cada worker en tu instalación. El comando para medirlo:

ps --no-headers -o rss -C php-fpm | \
awk '{sum += $1} END { 
    if (NR > 0) 
        print sum/NR/1024 " MB"; 
    else 
        print "0 MB (no hay procesos php-fpm)"
}'

El resultado es un promedio, así que cuanto más carga haya en el momento de ejecutarlo, más representativo será. Idealmente, ejecútalo durante horas pico o mientras simulas tráfico real, un promedio calculado con un solo worker activo puede subestimar significativamente el consumo bajo carga completa.

Importante: Recuerda reservar margen para el resto del stack: Nginx, MySQL, Redis y el propio sistema operativo también consumen memoria. El 100% de la RAM no está disponible para PHP.

5. Los tres modos de gestión de procesos

PHP-FPM ofrece tres estrategias para administrar el ciclo de vida de los workers. Cada una tiene su caso de uso.

ondemand – Para recursos muy limitados

Los workers se crean solo cuando llegan peticiones y se destruyen tras un período de inactividad (pm.process_idle_timeout).

Consume casi nada de RAM cuando el sitio está inactivo, pero introduce latencia en la primera petición tras ese silencio: el sistema operativo necesita arrancar el proceso desde cero. Útil en desarrollo o en VPS pequeños con muchos sitios de bajo tráfico. No recomendado para producción con usuarios activos.

dynamic – El equilibrio para la mayoría de sitios en producción

Mantiene un número base de workers activos (pm.start_servers) y crea o destruye procesos dinámicamente según la demanda, entre los límites que definen pm.min_spare_servers y pm.max_spare_servers.

Es el modo más versátil y el recomendado por defecto para WordPress en producción. Responde bien a picos moderados sin desperdiciar RAM en momentos de baja demanda.

Una configuración de partida razonable para un VPS de 2 GB:

pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6

static – Para servidores dedicados con tráfico constante

Mantiene siempre el mismo número fijo de workers, sin importar si hay tráfico o no. Máximo rendimiento técnico, mínima sobrecarga del proceso maestro. La contrapartida: esa RAM queda bloqueada permanentemente. Solo tiene sentido en servidores dedicados a una sola aplicación con carga predecible y constante.

6. Lo que Redis y OPcache no pueden resolver

En el artículo sobre Redis vimos que el object cache reduce drásticamente las consultas repetitivas a MySQL. Hay una capa similar para el código PHP: OPcache, que almacena el bytecode precompilado de los scripts en memoria, evitando que el motor tenga que recompilar el código en cada petición.

Ambas herramientas son indispensables. Pero ninguna cambia el número de workers disponibles.

Lo que hacen es reducir el tiempo que cada worker tarda en completar su trabajo, de 400 ms a 50 ms, por ejemplo. Un worker que termina más rápido vuelve al pool antes y puede atender la siguiente petición en cola. Eso aumenta el rendimiento total sin añadir ni un worker más.

En otras palabras: Redis y OPcache aumentan el rendimiento por worker. PHP-FPM define cuántos workers existen. Son capas complementarias, no alternativas. Igual que Nginx protege a PHP-FPM, y el CDN protege a Nginx, cada capa de la serie hace un trabajo que las otras no pueden hacer.

7. Cómo diagnosticar si PHP-FPM es tu problema

Antes de tocar cualquier configuración, confirma que el cuello de botella está ahí.

Señales claras de saturación de workers

  • Errores 502 o 504 bajo carga, especialmente en horas pico
  • CPU baja pero latencia alta y tiempos de respuesta largos
  • Los errores desaparecen con poco tráfico y reaparecen al aumentar usuarios simultáneos

Cómo verlo en tiempo real

# Contar procesos PHP-FPM activos
watch -n 1 "ps aux | grep php-fpm | grep -v grep | wc -l"

Habilitar el slowlog para encontrar qué retiene a los workers

En tu configuración de PHP-FPM, añade:

slowlog = /var/log/php-fpm/slow.log
request_slowlog_timeout = 2s

Cualquier petición que tarde más de 2 segundos quedará registrada con su stack trace completo. Es el diagnóstico más directo para identificar qué está reteniendo workers más tiempo del necesario, un plugin con llamadas a APIs lentas, una consulta SQL sin índice, un proceso externo que no responde.

8. Ahora ya sabes por qué el servidor colapsa aunque la CPU no llegue al 100%

Puedes tener Redis configurado, OPcache activo, Nginx filtrando tráfico y un CDN delante absorbiendo el grueso de las peticiones.

Si PHP-FPM no está bien dimensionado, el servidor seguirá colapsando cuando el tráfico dinámico supere la capacidad de los workers.

No porque le falte potencia. Sino porque todos los workers están ocupados y las peticiones que llegan no tienen dónde ir.

Mide el consumo real por worker, calcula cuántos puede sostener tu RAM, elige el modo de gestión adecuado para tu caso y valida los cambios bajo carga real.

Eso es lo que separa una infraestructura que escala de una que simplemente aguanta… hasta que no puede más.


PHP-FPM es el capítulo que faltaba en una serie sobre las capas que determinan el rendimiento real de WordPress en producción. Si llegaste aquí directamente, puedes leer el resto en cualquier orden: Redis y Memcached como object cacheEl servidor web como reverse proxyLa caché en el edge con CDN

Tema Relacionado:

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.