{"id":319,"date":"2026-05-16T06:13:19","date_gmt":"2026-05-16T10:13:19","guid":{"rendered":"https:\/\/juredev.com\/blog\/?p=319"},"modified":"2026-05-16T06:15:16","modified_gmt":"2026-05-16T10:15:16","slug":"php-fpm-decide-cuantos-usuarios-atender-tu-wordpress","status":"publish","type":"post","link":"https:\/\/juredev.com\/blog\/2026\/05\/php-fpm-decide-cuantos-usuarios-atender-tu-wordpress\/","title":{"rendered":"PHP-FPM: El portero que decide cu\u00e1ntos usuarios puede atender tu WordPress al mismo tiempo"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">La serie arranc\u00f3 con <a href=\"https:\/\/juredev.com\/blog\/2026\/04\/heroes-invisibles-wordpress-redis-memcached-salvan-servidor-del-colapso\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redis y Memcached<\/a>, sigui\u00f3 con <a href=\"https:\/\/juredev.com\/blog\/2026\/04\/la-capa-wordpress-el-servidor-web-como-proxy\/\" target=\"_blank\" rel=\"noreferrer noopener\">el servidor web como reverse proxy<\/a> y cerr\u00f3 con <a href=\"https:\/\/juredev.com\/blog\/2026\/05\/cache-edge-como-cdn-protege-wordpress-antes-que-la-peticion-toque-servidor\/\" target=\"_blank\" rel=\"noreferrer noopener\">la cach\u00e9 en el edge<\/a>. Tres capas, tres art\u00edculos. Y una que se qued\u00f3 fuera.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No fue deliberado. Simplemente, al recorrer el stack de afuera hacia adentro, PHP-FPM qued\u00f3 en el tintero. Y es precisamente la capa que vive entre Nginx y WordPress, la que decide cu\u00e1ntos procesos PHP pueden ejecutarse al mismo tiempo. Sin entenderla, el resto del stack optimizado puede seguir colapsando bajo carga.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Este es ese cap\u00edtulo que faltaba.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">0. El s\u00edntoma que esta vez s\u00ed es urgente<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Todo parece bien configurado. Redis est\u00e1 activo y los cache hits son altos. Nginx est\u00e1 delante filtrando tr\u00e1fico. Y aun as\u00ed, ocurre esto:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Bajo carga, el servidor responde con <strong>502 Bad Gateway<\/strong> o <strong>504 Gateway Timeout<\/strong><\/li>\n\n\n\n<li>La CPU apenas llega al 40%\u2026 pero el sitio colapsa igual<\/li>\n\n\n\n<li>WooCommerce funciona bien con 5 usuarios simult\u00e1neos, pero con 20 todo se degrada<\/li>\n\n\n\n<li>El panel de administraci\u00f3n se vuelve inutilizable en horas pico<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Si ese es tu escenario, el cuello de botella probablemente no est\u00e1 en la base de datos ni en el proxy. Est\u00e1 en <strong>cu\u00e1ntos procesos PHP pueden ejecutarse en paralelo<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. D\u00f3nde encaja PHP-FPM en el stack<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La serie ha ido construyendo el stack de afuera hacia adentro. Mir\u00e1ndolo completo:<\/p>\n\n\n\n<script type=\"module\">\n  import mermaid from 'https:\/\/cdn.jsdelivr.net\/npm\/mermaid@10\/dist\/mermaid.esm.min.mjs';\n  mermaid.initialize({ \n    startOnLoad: true, \n    theme: 'dark'\n  });\n<\/script>\n<div style=\"display: flex; justify-content: center; align-items: center; width: 100%; margin: 20px 0;\">\n    <pre class=\"mermaid\" style=\"background: transparent; border: none; color: transparent;\">\ngraph TD\n    A[Cliente] --> B[CDN \/ Edge Cache]\n    B --> C[Nginx \/ Apache]\n    C --> D[PHP-FPM]\n    D --> E[WordPress]\n    E --> F[(MySQL \/ Redis)]\n    <\/pre>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pero cuando una petici\u00f3n pasa todos esos filtros y llega a PHP, un login, una carga del carrito, una llamada a la REST API, cualquier contenido din\u00e1mico que no est\u00e9 cacheado, PHP-FPM es quien decide si hay capacidad para atenderla o no.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Los workers: el concepto que lo explica todo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PHP-FPM significa <em>PHP FastCGI Process Manager<\/em>. Su trabajo es mantener un grupo de procesos PHP listos para ejecutar c\u00f3digo, denominados <strong>workers<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando Nginx le entrega una petici\u00f3n din\u00e1mica, un worker libre la toma y ejecuta el ciclo completo de WordPress:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Carga el n\u00facleo, los plugins y el tema activo.<\/li>\n\n\n\n<li>Inicializa hooks y funciones.<\/li>\n\n\n\n<li>Consulta Redis o MySQL seg\u00fan lo que necesite.<\/li>\n\n\n\n<li>Renderiza el HTML final.<\/li>\n\n\n\n<li>Devuelve la respuesta a Nginx para enviarla al usuario.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Cada worker es un proceso independiente que consume <strong>RAM<\/strong>, <strong>CPU<\/strong> y <strong>tiempo de ejecuci\u00f3n<\/strong>. Y solo puede atender una petici\u00f3n a la vez.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La consecuencia directa: <strong>el n\u00famero de workers define cu\u00e1ntas peticiones din\u00e1micas puede procesar tu servidor en paralelo<\/strong>. No m\u00e1s.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">3. El par\u00e1metro que m\u00e1s importa: <code>pm.max_children<\/code><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El valor <code>pm.max_children<\/code> en la configuraci\u00f3n de PHP-FPM define el n\u00famero m\u00e1ximo de workers que pueden existir al mismo tiempo.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Con <code>pm.max_children = 10<\/code>, tu servidor puede procesar exactamente <strong>10 peticiones din\u00e1micas en paralelo<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u00bfQu\u00e9 pasa con la petici\u00f3n n\u00famero 11? Entra en una cola de espera controlada por <code>listen.backlog<\/code>, un par\u00e1metro que en muchos sistemas tiene un valor sorprendentemente bajo, a veces 511 o incluso 65. Cuando esa cola tambi\u00e9n se llena, el servidor empieza a rechazar peticiones directamente. Ah\u00ed aparecen los errores 502 y 504.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Este es el fen\u00f3meno m\u00e1s enga\u00f1oso del diagn\u00f3stico de WordPress bajo carga: <strong>un servidor puede tener la CPU al 40% y aun as\u00ed estar colapsado<\/strong>. El cuello de botella no es capacidad de c\u00f3mputo, es falta de workers disponibles.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. M\u00e1s workers no es siempre la soluci\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La reacci\u00f3n habitual cuando el servidor colapsa es subir <code>pm.max_children<\/code> a un n\u00famero 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\u00e1s workers de los que tu RAM puede sostener, el servidor empieza a usar swap en disco.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Cuando eso ocurre, la latencia se dispara. Y en muchos VPS modernos donde el swap est\u00e1 desactivado, el kernel directamente mata procesos, el temido <strong>OOM Killer<\/strong>, con resultados m\u00e1s destructivos que el problema original.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La f\u00f3rmula correcta es:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pm.max_children = RAM disponible para PHP \u00f7 consumo promedio por worker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ejemplo: 2 GB asignados a PHP, con workers que consumen ~80 MB cada uno:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>2048 MB \u00f7 80 MB \u2248 25 workers<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Pero para aplicar esa f\u00f3rmula necesitas saber cu\u00e1nto consume realmente cada worker en tu instalaci\u00f3n. El comando para medirlo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ps --no-headers -o rss -C php-fpm | \\\nawk '{sum += $1} END { \n    if (NR &gt; 0) \n        print sum\/NR\/1024 \" MB\"; \n    else \n        print \"0 MB (no hay procesos php-fpm)\"\n}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">El resultado es un promedio, as\u00ed que cuanto m\u00e1s carga haya en el momento de ejecutarlo, m\u00e1s representativo ser\u00e1. Idealmente, ejec\u00fatalo  durante horas pico o mientras simulas tr\u00e1fico real, un promedio calculado con un solo worker activo puede subestimar significativamente el consumo bajo carga completa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Importante<\/strong>: Recuerda reservar margen para el resto del stack: Nginx, MySQL, Redis y el propio sistema operativo tambi\u00e9n consumen memoria. El 100% de la RAM no est\u00e1 disponible para PHP.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. Los tres modos de gesti\u00f3n de procesos<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">PHP-FPM ofrece tres estrategias para administrar el ciclo de vida de los workers. Cada una tiene su caso de uso.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>ondemand<\/code> &#8211; Para recursos muy limitados<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Los workers se crean solo cuando llegan peticiones y se destruyen tras un per\u00edodo de inactividad (<code>pm.process_idle_timeout<\/code>).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Consume casi nada de RAM cuando el sitio est\u00e1 inactivo, pero introduce latencia en la primera petici\u00f3n tras ese silencio: el sistema operativo necesita arrancar el proceso desde cero. \u00datil en desarrollo o en VPS peque\u00f1os con muchos sitios de bajo tr\u00e1fico. No recomendado para producci\u00f3n con usuarios activos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>dynamic<\/code> &#8211; El equilibrio para la mayor\u00eda de sitios en producci\u00f3n<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Mantiene un n\u00famero base de workers activos (<code>pm.start_servers<\/code>) y crea o destruye procesos din\u00e1micamente seg\u00fan la demanda, entre los l\u00edmites que definen <code>pm.min_spare_servers<\/code> y <code>pm.max_spare_servers<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Es el modo m\u00e1s vers\u00e1til y el recomendado por defecto para WordPress en producci\u00f3n. Responde bien a picos moderados sin desperdiciar RAM en momentos de baja demanda.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Una configuraci\u00f3n de partida razonable para un VPS de 2 GB:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pm = dynamic\npm.max_children = 20\npm.start_servers = 4\npm.min_spare_servers = 2\npm.max_spare_servers = 6<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><code>static<\/code> &#8211; Para servidores dedicados con tr\u00e1fico constante<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Mantiene siempre el mismo n\u00famero fijo de workers, sin importar si hay tr\u00e1fico o no. M\u00e1ximo rendimiento t\u00e9cnico, m\u00ednima sobrecarga del proceso maestro. La contrapartida: esa RAM queda bloqueada permanentemente. Solo tiene sentido en servidores dedicados a una sola aplicaci\u00f3n con carga predecible y constante.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">6. Lo que Redis y OPcache no pueden resolver<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">En el art\u00edculo sobre Redis vimos que el object cache reduce dr\u00e1sticamente las consultas repetitivas a MySQL. Hay una capa similar para el c\u00f3digo PHP: <strong>OPcache<\/strong>, que almacena el bytecode precompilado de los scripts en memoria, evitando que el motor tenga que recompilar el c\u00f3digo en cada petici\u00f3n.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ambas herramientas son indispensables. Pero ninguna cambia el n\u00famero de workers disponibles.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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\u00e1s r\u00e1pido vuelve al pool antes y puede atender la siguiente petici\u00f3n en cola. Eso aumenta el rendimiento total sin a\u00f1adir ni un worker m\u00e1s.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En otras palabras: Redis y OPcache aumentan <strong>el rendimiento por worker<\/strong>. PHP-FPM <strong>define cu\u00e1ntos workers existen<\/strong>. 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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">7. C\u00f3mo diagnosticar si PHP-FPM es tu problema<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Antes de tocar cualquier configuraci\u00f3n, confirma que el cuello de botella est\u00e1 ah\u00ed.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Se\u00f1ales claras de saturaci\u00f3n de workers<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Errores 502 o 504 bajo carga, especialmente en horas pico<\/li>\n\n\n\n<li>CPU baja pero latencia alta y tiempos de respuesta largos<\/li>\n\n\n\n<li>Los errores desaparecen con poco tr\u00e1fico y reaparecen al aumentar usuarios simult\u00e1neos<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">C\u00f3mo verlo en tiempo real<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code># Contar procesos PHP-FPM activos\nwatch -n 1 \"ps aux | grep php-fpm | grep -v grep | wc -l\"<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Habilitar el slowlog para encontrar qu\u00e9 retiene a los workers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">En tu configuraci\u00f3n de PHP-FPM, a\u00f1ade:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>slowlog = \/var\/log\/php-fpm\/slow.log\nrequest_slowlog_timeout = 2s<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Cualquier petici\u00f3n que tarde m\u00e1s de 2 segundos quedar\u00e1 registrada con su stack trace completo. Es el diagn\u00f3stico m\u00e1s directo para identificar qu\u00e9 est\u00e1 reteniendo workers m\u00e1s tiempo del necesario, un plugin con llamadas a APIs lentas, una consulta SQL sin \u00edndice, un proceso externo que no responde.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">8. Ahora ya sabes por qu\u00e9 el servidor colapsa aunque la CPU no llegue al 100%<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Puedes tener Redis configurado, OPcache activo, Nginx filtrando tr\u00e1fico y un CDN delante absorbiendo el grueso de las peticiones.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si PHP-FPM no est\u00e1 bien dimensionado, el servidor seguir\u00e1 colapsando cuando el tr\u00e1fico din\u00e1mico supere la capacidad de los workers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">No porque le falte potencia. Sino porque todos los workers est\u00e1n ocupados y las peticiones que llegan no tienen d\u00f3nde ir.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Mide el consumo real por worker, calcula cu\u00e1ntos puede sostener tu RAM, elige el modo de gesti\u00f3n adecuado para tu caso y valida los cambios bajo carga real.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Eso es lo que separa una infraestructura que escala de una que simplemente aguanta\u2026 hasta que no puede m\u00e1s.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">PHP-FPM es el cap\u00edtulo que faltaba en una serie sobre las capas que determinan el rendimiento real de WordPress en producci\u00f3n. Si llegaste aqu\u00ed directamente, puedes leer el resto en cualquier orden: <a href=\"https:\/\/juredev.com\/blog\/2026\/04\/heroes-invisibles-wordpress-redis-memcached-salvan-servidor-del-colapso\/\">Redis y Memcached como object cache<\/a> &#8211; <a href=\"https:\/\/juredev.com\/blog\/2026\/04\/la-capa-wordpress-el-servidor-web-como-proxy\/\">El servidor web como reverse proxy<\/a> &#8211; <a href=\"https:\/\/juredev.com\/blog\/2026\/05\/cache-edge-como-cdn-protege-wordpress-antes-que-la-peticion-toque-servidor\/\">La cach\u00e9 en el edge con CDN<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>La serie arranc\u00f3 con Redis y Memcached, sigui\u00f3 con el servidor web como reverse proxy y cerr\u00f3 con la cach\u00e9 en el edge. Tres capas, tres art\u00edculos. Y una que se qued\u00f3 fuera. No fue deliberado. Simplemente, al recorrer el stack de afuera hacia adentro, PHP-FPM qued\u00f3 en el tintero. Y es precisamente la capa [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[15,19],"class_list":["post-319","post","type-post","status-publish","format-standard","hentry","category-nota","tag-php","tag-wordpress"],"_links":{"self":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/319","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/comments?post=319"}],"version-history":[{"count":0,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/319\/revisions"}],"wp:attachment":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/media?parent=319"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/categories?post=319"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/tags?post=319"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}