PHP-FPM ejecuta el código PHP en un conjunto de procesos (pool) que el servidor web, Nginx o Apache, llama a través de FastCGI. El tamaño de ese pool decide cuántas peticiones PHP se atienden a la vez: si es pequeño, las peticiones se quedan en cola y la web va lenta; si es demasiado grande, el servidor se queda sin memoria y empieza a usar swap o el kernel mata procesos. En este tutorial calcularás el tamaño correcto del pool de PHP-FPM 8.3 en Ubuntu 24.04 a partir de la memoria real que consumen tus procesos, elegirás el gestor de procesos adecuado y activarás las herramientas para comprobar que la configuración funciona bajo carga.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • PHP 8.3 con PHP-FPM instalado (sudo apt install php8.3-fpm) y un servidor web que ya sirva tu aplicación PHP a través del socket /run/php/php8.3-fpm.sock.
  • La aplicación funcionando y recibiendo tráfico real o de prueba, para poder medir cuánta memoria usan los procesos.

Paso 1: Revisar la configuración actual del pool

Cada pool se define en un fichero dentro de /etc/php/8.3/fpm/pool.d/. Ubuntu instala uno llamado www en www.conf. Muestra sus directivas activas del gestor de procesos:

grep -E '^(user|group|listen|pm)' /etc/php/8.3/fpm/pool.d/www.conf
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3

Con pm.max_children = 5 el pool atiende como máximo cinco peticiones PHP simultáneas. Es un valor pensado para máquinas mínimas y casi siempre se queda corto. Comprueba también si ya se ha alcanzado ese límite:

sudo grep 'max_children' /var/log/php8.3-fpm.log
[25-Sep-2026 10:41:12] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it

Si ves este aviso, las peticiones se han quedado esperando un proceso libre.

Paso 2: Medir la memoria de cada proceso PHP-FPM

El valor de pm.max_children sale de dividir la memoria que puedes dedicar a PHP entre lo que ocupa cada proceso. Con la aplicación recibiendo tráfico, calcula la memoria media (RSS) de los procesos php-fpm8.3:

ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1} END {printf "%d procesos, media %.0f MB\n", NR, s/NR/1024}'
6 procesos, media 58 MB

La cifra incluye al proceso maestro, que ocupa poco, así que la media real de los workers es algo mayor. Mide varias veces a lo largo del día y quédate con el valor más alto. Una web WordPress típica ronda los 40-80 MB por proceso; aplicaciones Laravel o Magento pueden superar los 100 MB.

Después, mira la memoria total y la que usan los demás servicios:

free -m
               total        used        free      shared  buff/cache   available
Mem:            3915        1210         512          45        2193        2402
Swap:              0           0           0

Paso 3: Calcular pm.max_children

La fórmula es:

pm.max_children = (RAM total - RAM para el resto de servicios) / RAM media por proceso

Ejemplo con un servidor de 4 GB en el que MySQL, Nginx, Redis y el sistema necesitan unos 1.500 MB:

(4000 - 1500) / 60 = 41  ->  pm.max_children = 40

Redondea hacia abajo y deja margen: los procesos crecen con peticiones pesadas. Si el servidor solo ejecuta PHP-FPM y un Nginx, puedes reservar menos memoria para otros servicios, pero nunca asignes el 100 % de la RAM.

Paso 4: Elegir el gestor de procesos

La directiva pm define cómo se crean los procesos:

ModoComportamientoCuándo usarlo
dynamicMantiene un número de procesos en espera entre pm.min_spare_servers y pm.max_spare_servers, hasta pm.max_childrenOpción por defecto, válida para la mayoría de servidores
ondemandNo mantiene procesos en espera; los crea al llegar peticiones y los cierra tras pm.process_idle_timeoutServidores con poca RAM o muchos pools con poco tráfico
staticMantiene siempre pm.max_children procesosServidores dedicados a una aplicación con tráfico alto y constante

ondemand ahorra memoria pero añade la latencia de crear un proceso a la primera petición tras un periodo inactivo. static elimina esa latencia a cambio de ocupar la memoria de forma permanente.

Paso 5: Aplicar la nueva configuración

Abre el fichero del pool:

sudo nano /etc/php/8.3/fpm/pool.d/www.conf

Localiza las directivas pm y ajústalas. Este es un ejemplo con dynamic para el servidor de 4 GB del paso 3:

pm = dynamic
pm.max_children = 40
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500

Una regla práctica para dynamic es poner pm.start_servers en torno al 25 % de pm.max_children, pm.min_spare_servers al 10-15 % y pm.max_spare_servers al 35-40 %. pm.start_servers debe estar entre el mínimo y el máximo de procesos en espera, o PHP-FPM no arrancará.

pm.max_requests recicla cada proceso tras atender 500 peticiones, lo que contiene fugas de memoria de extensiones o de la propia aplicación. Con 0 los procesos no se reciclan nunca.

Si prefieres ondemand, la configuración equivalente es:

pm = ondemand
pm.max_children = 40
pm.process_idle_timeout = 10s
pm.max_requests = 500

Comprueba la sintaxis antes de aplicar nada:

sudo php-fpm8.3 -t
[25-Sep-2026 11:02:31] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful

Recarga el servicio. reload reinicia los workers de forma ordenada sin cortar las peticiones en curso:

sudo systemctl reload php8.3-fpm

Paso 6: Limitar la duración de las peticiones y activar el slowlog

Una petición bloqueada (una consulta lenta, una API externa que no responde) ocupa un proceso indefinidamente. Añade estas líneas al final de www.conf para cortar las peticiones que superen 60 segundos y registrar la traza de las que tarden más de 5:

request_terminate_timeout = 60s
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-slow.log

request_terminate_timeout debe ser igual o mayor que el max_execution_time de PHP y que el timeout de tu servidor web (fastcgi_read_timeout en Nginx), o los errores serán confusos.

Comprueba la sintaxis y recarga:

sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm

Cuando una petición supere los 5 segundos, verás en el slowlog la pila de llamadas que indica dónde estaba el script:

sudo tail -n 20 /var/log/php8.3-fpm-slow.log
[25-Sep-2026 11:20:04]  [pool www] pid 31877
script_filename = /var/www/html/informe.php
[0x00007f3a1c2130a0] sleep() /var/www/html/informe.php:12

Paso 7: Activar la página de estado del pool

La página de estado muestra en tiempo real cuántos procesos están activos, si hay peticiones en cola y cuántas veces se ha alcanzado pm.max_children. Edita www.conf y descomenta la línea pm.status_path:

pm.status_path = /status

Recarga PHP-FPM:

sudo systemctl reload php8.3-fpm

Para consultarla sin exponerla en el servidor web, instala cgi-fcgi, que habla FastCGI directamente con el socket:

sudo apt install libfcgi-bin
sudo SCRIPT_NAME=/status SCRIPT_FILENAME=/status REQUEST_METHOD=GET cgi-fcgi -bind -connect /run/php/php8.3-fpm.sock
Content-type: text/plain;charset=UTF-8

pool:                 www
process manager:      dynamic
start time:           25/Sep/2026:11:05:12 +0000
start since:          812
accepted conn:        15230
listen queue:         0
max listen queue:     0
listen queue len:     0
idle processes:       12
active processes:     3
total processes:      15
max active processes: 27
max children reached: 0
slow requests:        0

Los campos que importan:

  • listen queue: peticiones esperando un proceso libre ahora mismo. Debe ser 0 casi siempre.
  • max active processes: el máximo de procesos ocupados a la vez desde el arranque. Si se acerca a pm.max_children, estás cerca del límite.
  • max children reached: cuántas veces se ha llegado al límite. Si crece y aún tienes memoria libre, sube pm.max_children; si no tienes memoria, necesitas más RAM u optimizar la aplicación.
  • slow requests: peticiones que han superado request_slowlog_timeout.

Paso 8: Separar aplicaciones en pools distintos

Si alojas varias aplicaciones, dales un pool propio a cada una. Así una web con un pico de tráfico no deja sin procesos a las demás y cada una se ejecuta con su propio usuario. Crea un pool nuevo para your_app:

sudo nano /etc/php/8.3/fpm/pool.d/your_app.conf
[your_app]
user = your_user
group = your_user
listen = /run/php/php8.3-fpm-your_app.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 500
pm.status_path = /status

request_terminate_timeout = 60s

listen.owner y listen.group son www-data para que Nginx o Apache puedan escribir en el socket, mientras el código PHP se ejecuta como your_user. Ten en cuenta que la suma de pm.max_children de todos los pools es la que debe respetar el cálculo del paso 3.

Comprueba y recarga:

sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm
ls -l /run/php/
srw-rw---- 1 www-data www-data 0 Sep 25 11:30 php8.3-fpm-your_app.sock
srw-rw---- 1 www-data www-data 0 Sep 25 11:30 php8.3-fpm.sock

Por último, apunta el virtual host de esa aplicación al nuevo socket. En Nginx:

fastcgi_pass unix:/run/php/php8.3-fpm-your_app.sock;

Solución de problemas

  • 502 Bad Gateway tras recargar: el socket no existe o el servidor web no tiene permisos. Comprueba ls -l /run/php/ y que la ruta en fastcgi_pass coincide con listen.
  • PHP-FPM no arranca: revisa sudo journalctl -u php8.3-fpm -n 50. Con dynamic, un error frecuente es que pm.start_servers quede fuera del rango entre pm.min_spare_servers y pm.max_spare_servers.
  • El servidor usa swap o aparece el OOM killer: pm.max_children es demasiado alto para la memoria disponible. Vuelve a medir la RAM por proceso y rebaja el valor.
  • 504 Gateway Timeout: el servidor web se ha cansado de esperar antes que PHP. Revisa el slowlog para encontrar el código lento y alinea fastcgi_read_timeout con request_terminate_timeout.

Conclusión

Has dimensionado el pool de PHP-FPM a partir de la memoria real de tus procesos, has elegido el gestor de procesos adecuado y tienes la página de estado y el slowlog para confirmar que no hay peticiones en cola ni scripts bloqueados. Como siguientes pasos, activa y ajusta OPcache para reducir el tiempo de CPU de cada petición, añade una caché de datos con Memcached o Redis y mide el resultado con Apache Bench antes y después de cada cambio.