Optimización de PHP-FPM: Configuración de php.ini y www.conf para PHP 8

pexels photo 4584830 4584830

PHP-FPM (FastCGI Process Manager) es la implementación de PHP diseñada para manejar grandes cargas de tráfico con eficiencia, y desde hace años es el estándar de facto para servir aplicaciones PHP detrás de Nginx o Apache en modo proxy. Configurar adecuadamente PHP-FPM puede marcar una diferencia enorme en el rendimiento de tus aplicaciones web: la misma máquina, con los mismos recursos, puede pasar de saturarse con 40 usuarios concurrentes a absorber varios cientos simplemente ajustando dos archivos.

En esta guía repasamos, parámetro a parámetro, cómo ajustar php.ini y www.conf para exprimir PHP 8 en producción. Los ejemplos están escritos sobre PHP 8.4, pero todo lo que verás aquí es aplicable a cualquier rama 8.x en soporte (8.2, 8.3, 8.4 y 8.5): solo cambian las rutas y el nombre del socket.

Qué hace realmente PHP-FPM (y por qué la configuración por defecto se queda corta)

Cuando un visitante pide una página, Nginx no ejecuta PHP: recibe la petición, decide que es dinámica y se la pasa a PHP-FPM a través de un socket Unix (o TCP). Del otro lado hay un proceso master que no atiende peticiones, sino que vigila y gestiona un pool de procesos hijo (los workers). Cada worker atiende una petición completa de principio a fin y luego queda libre para la siguiente.

De ahí salen las dos consecuencias que gobiernan todo el tuning:

  • El número de peticiones PHP simultáneas que puedes servir es exactamente el número de workers disponibles. Si tienes 10 workers y llegan 40 peticiones dinámicas a la vez, 30 esperan en cola. Cuando la cola se llena, Nginx devuelve un 502 o un 504.
  • Cada worker ocupa memoria de forma independiente. Si configuras más workers de los que caben en RAM, el kernel empieza a hacer swap y, en el peor caso, el OOM killer mata procesos. Un servidor con cola es lento; un servidor con OOM killer está caído.

La configuración que traen las distribuciones está pensada para no romper nada en una máquina genérica, no para tu carga concreta. Por eso merece la pena revisarla siempre.

Configuración del archivo php.ini

El archivo php.ini define el comportamiento general del intérprete: cuántos recursos puede consumir un script, cómo se gestionan los errores y qué extensiones se activan. Es el archivo que limita una petición; www.conf, que veremos después, es el que limita el conjunto.

Ojo con un detalle que causa muchas horas perdidas: PHP-FPM y la CLI usan archivos distintos. Cambiar /etc/php/8.4/cli/php.ini no afecta a la web. El que sirve tus páginas es el de FPM.

Ruta del archivo: /etc/php/8.4/fpm/php.ini

; PHP General Settings
memory_limit = 256M
max_execution_time = 60
max_input_time = 60
post_max_size = 16M
upload_max_filesize = 16M

; Error Handling
error_reporting = E_ALL & ~E_DEPRECATED
display_errors = Off
log_errors = On
error_log = /var/log/php8.4-fpm/error.log

; Data Handling
max_input_vars = 3000

; Session Settings
session.save_handler = files
session.save_path = /var/lib/php/sessions
session.gc_maxlifetime = 1440
session.gc_probability = 1
session.gc_divisor = 100

; File Upload Settings
file_uploads = On
upload_tmp_dir = /var/lib/php/uploads
max_file_uploads = 20

; OPcache Settings
opcache.enable = 1
opcache.memory_consumption = 192
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.revalidate_freq = 2
opcache.validate_timestamps = On
opcache.jit = tracing
opcache.jit_buffer_size = 64M

; Security Settings
expose_php = Off
cgi.fix_pathinfo = 0

Explicación de las configuraciones

Cada parámetro tiene un propósito concreto y un efecto medible sobre el rendimiento:

  • memory_limit: límite de memoria por petición. 256M es holgado para un WordPress o un PrestaShop medio; subirlo a 512M o 1G «por si acaso» es contraproducente, porque multiplica el riesgo de que varios workers a la vez agoten la RAM del servidor. Si un script necesita más de 256M, casi siempre el problema es el script, no el límite.
  • max_execution_time y max_input_time: evitan que un script defectuoso retenga un worker indefinidamente. Un worker bloqueado 300 segundos es un worker que no atiende a nadie más durante cinco minutos. Para tareas largas (importaciones, informes) lo correcto es sacarlas a un cron o a una cola, no subir este valor.
  • post_max_size y upload_max_filesize: deben ir coordinados entre sí (post_max_size siempre igual o mayor) y con la directiva client_max_body_size de Nginx. Si no coinciden, el error que ve el usuario cambia según cuál sea el más restrictivo, lo que despista mucho al depurar.
  • max_input_vars: el valor por defecto de 1000 se queda corto en paneles de administración con formularios grandes (menús de WordPress, configuradores de producto). Cuando se supera, PHP descarta campos en silencio: el formulario se guarda a medias sin ningún error visible. 3000 es un punto de partida razonable.
  • opcache: es la optimización con mejor relación esfuerzo/resultado de toda la lista. OPcache guarda el bytecode ya compilado en memoria compartida, de modo que PHP no vuelve a parsear los mismos archivos en cada petición. En una aplicación con miles de archivos el ahorro es del 30-50 % del tiempo de CPU.
  • opcache.validate_timestamps: en producción pura se puede poner a 0 para que PHP ni siquiera compruebe si los archivos han cambiado, pero entonces cada despliegue exige recargar PHP-FPM. Si no tienes un pipeline de despliegue que lo haga automáticamente, déjalo en On con revalidate_freq = 2.
  • expose_php: desactivarlo elimina la cabecera X-Powered-By, que revela tu versión exacta de PHP a cualquiera que mire las cabeceras. No frena a un atacante decidido, pero sí te saca de los escaneos masivos que buscan versiones concretas con CVE conocidos. En la misma línea van otras medidas básicas que repasamos en nuestra guía de ciberseguridad para servidores con poco presupuesto.
Diagrama de la arquitectura de PHP-FPM: Nginx, socket Unix, proceso master y pool de workers, con los parámetros de php.ini y www.conf
Recorrido de una petición: Nginx entrega al socket, el master reparte entre los workers del pool. php.ini limita cada petición; www.conf limita el pool completo.

Configuración del archivo www.conf

El archivo www.conf define el pool: con qué usuario corre PHP, por dónde escucha y —lo más importante— cuántos procesos hijo se mantienen vivos. Es aquí donde se gana o se pierde la capacidad real de tu servidor.

Ruta del archivo: /etc/php/8.4/fpm/pool.d/www.conf

[www]
user = www-data
group = www-data
listen = /run/php/php8.4-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
listen.backlog = 1024

; Gestión de procesos hijo
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
pm.max_spawn_rate = 32
pm.process_idle_timeout = 10s

; Timeouts y diagnóstico
request_terminate_timeout = 120
request_slowlog_timeout = 5s
slowlog = /var/log/php8.4-fpm/www-slow.log

; Página de estado (protegida en Nginx)
pm.status_path = /fpm-status
ping.path = /fpm-ping

; Logging
access.log = /var/log/php8.4-fpm/www-access.log
catch_workers_output = yes
decorate_workers_output = no

Explicación de las configuraciones

  • listen: un socket Unix es más rápido que TCP cuando Nginx y PHP-FPM están en la misma máquina, porque evita la pila de red completa. Solo tiene sentido usar 127.0.0.1:9000 si vas a separar servidores o a contenerizar. Y un aviso: listen.allowed_clients solo aplica a sockets TCP; con socket Unix no hace absolutamente nada, aunque aparezca en muchos tutoriales copiados unos de otros.
  • pm.max_children: el techo de peticiones PHP simultáneas. Es el parámetro más importante del archivo y el que más se configura a ojo. En la siguiente sección vemos cómo calcularlo con datos reales.
  • pm.start_servers, pm.min_spare_servers, pm.max_spare_servers: definen el colchón de procesos ociosos listos para absorber un pico sin pagar el coste de arrancar procesos nuevos. Como regla práctica, start_servers en torno al 20 % de max_children y max_spare_servers en torno al 40 %.
  • pm.max_requests: recicla cada worker tras N peticiones. No es una optimización de velocidad, sino una red de seguridad frente a fugas de memoria en extensiones de terceros. Valores entre 500 y 1000 son habituales; ponerlo demasiado bajo desperdicia CPU reiniciando procesos.
  • pm.max_spawn_rate: disponible desde PHP 8.1, controla cuántos hijos puede crear el master de golpe cuando llega un pico. Por defecto son 32. Subirlo acelera la respuesta ante ráfagas bruscas; bajarlo protege a servidores modestos de una tormenta de procesos que dispare el uso de CPU.
  • request_terminate_timeout: el corte definitivo. Debe ser mayor que max_execution_time de php.ini, porque este último no cuenta el tiempo en llamadas externas bloqueantes (una consulta a una API lenta, por ejemplo). Si lo pones por debajo, PHP mata el proceso sin dejar rastro en el log de errores de la aplicación.
  • slowlog y request_slowlog_timeout: probablemente la herramienta de diagnóstico más infrautilizada de PHP-FPM. Registra el backtrace completo de cualquier petición que supere el umbral, así que te dice exactamente en qué función se está atascando la web, sin instalar nada.

dynamic, static u ondemand: cuál elegir

El valor de pm cambia por completo la estrategia del pool:

  • dynamic — mantiene un mínimo de procesos ociosos y crea o destruye según demanda. Es el equilibrio correcto para la mayoría de sitios con tráfico variable y es lo que deberías usar salvo que tengas un motivo claro para lo contrario.
  • static — arranca siempre pm.max_children procesos y los mantiene. Elimina por completo la latencia de crear procesos, a costa de reservar la memoria desde el minuto uno. Tiene sentido en servidores dedicados a una sola aplicación con tráfico alto y sostenido, donde ya sabes exactamente cuánta RAM puedes comprometer.
  • ondemand — no mantiene ningún proceso ocioso: los crea cuando llega la petición y los apaga tras pm.process_idle_timeout. Ideal para servidores con muchos pools pequeños (hosting multi-sitio, entornos de staging) donde la mayoría están inactivos casi todo el tiempo. La primera petición tras un rato de inactividad paga un pequeño sobrecoste.

Un patrón muy útil en máquinas compartidas: un pool por sitio, cada uno con su propio usuario del sistema y su propio www.conf. Así un pico —o un ataque— en un sitio no se lleva por delante a los demás, y los logs quedan separados.

Tabla para calcular pm.max_children en PHP-FPM según la RAM libre del servidor y la memoria media por proceso
El cálculo de pm.max_children parte de la RAM realmente libre, no de la RAM total del servidor.

Cómo calcular pm.max_children con datos reales

La fórmula es simple, pero hay que alimentarla con medidas propias y no con valores copiados de un blog:

pm.max_children = RAM libre para PHP / RAM media por proceso PHP-FPM

Paso 1 — Mide la memoria media por proceso. Con el sitio en carga normal:

ps -ylC php-fpm8.4 --sort:rss | awk '{sum+=$8; n++} END {print sum/n/1024 " MB"}'

Un WordPress ligero ronda los 30-50 MB por proceso; un PrestaShop o un Magento con muchos módulos puede irse a 80-150 MB. Toma el valor medio real, no el pico.

Paso 2 — Calcula la RAM realmente disponible. Del total del servidor, resta lo que consumen el sistema operativo, Nginx, MySQL/MariaDB y Redis, y deja además un margen de seguridad del 15-20 %. En un servidor de 8 GB con base de datos local es habitual que a PHP le queden 3-4 GB, no 8.

Paso 3 — Divide y contrasta con la CPU. Si la fórmula te da 160 workers pero tienes 4 vCPU, ese número es teórico: 160 procesos compitiendo por 4 núcleos generan tanta contención que la latencia empeora. Un tope práctico razonable es del orden de 15-25 workers por núcleo para cargas normales de aplicación web, y mucho menos si tus peticiones son intensivas en CPU.

Paso 4 — Verifica con la página de estado. Activa pm.status_path, protégelo en Nginx para que solo sea accesible desde localhost y observa dos campos: max children reached (si crece, te faltan workers) y listen queue (si es mayor que cero de forma sostenida, estás encolando peticiones). Ajusta y vuelve a medir. Este mismo enfoque de «medir antes de tocar» es el que aplicamos al optimizar el rendimiento de tiendas PrestaShop y de instalaciones Drupal con Varnish y BigPipe.

OPcache y JIT: lo que sí mueve la aguja

Si solo pudieras tocar una cosa de todo este artículo, sería OPcache. Merece la pena entender los tres parámetros que realmente importan:

  • opcache.memory_consumption: el tamaño de la caché de bytecode. Si se queda pequeña, OPcache empieza a expulsar archivos y a recompilarlos constantemente, con lo que pierdes gran parte del beneficio. Comprueba el estado con opcache_get_status(): si cache_full es true, súbelo.
  • opcache.max_accelerated_files: número máximo de archivos cacheados. Una instalación de WordPress con 30 plugins supera fácilmente los 15.000 archivos, y un Magento pasa de 40.000. El valor por defecto (10.000) se queda corto en casi cualquier proyecto real.
  • opcache.interned_strings_buffer: memoria para cadenas internas compartidas entre procesos. Subirlo a 16-32 MB reduce el consumo total de RAM del pool, porque los workers dejan de duplicar las mismas cadenas.

Sobre el JIT conviene ser honesto: en aplicaciones web típicas —donde el tiempo se va en base de datos, red y E/S— el JIT aporta muy poco, a veces nada, y añade consumo de memoria. Donde sí destaca es en cálculo intensivo en PHP puro: procesamiento de imágenes, criptografía, análisis numérico. Actívalo si tienes ese perfil de carga, mídelo, y si no ves diferencia, desactívalo sin remordimientos.

Directivas de Nginx que acompañan a PHP-FPM

Estos parámetros no van en php.ini ni en www.conf —son directivas de Nginx— pero se ajustan siempre en pareja con PHP-FPM, porque un desajuste entre ambos produce errores difíciles de diagnosticar:

client_body_timeout 30s;
client_header_timeout 30s;
keepalive_timeout 65s;
send_timeout 30s;

client_body_buffer_size 16k;
client_header_buffer_size 4k;
client_max_body_size 16m;
large_client_header_buffers 4 8k;

fastcgi_read_timeout 120s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_connect_timeout 10s;

Dos avisos concretos. Primero: client_max_body_size debe coincidir con upload_max_filesize de PHP; si Nginx es más restrictivo, el usuario recibe un 413 antes de que PHP llegue a enterarse. Segundo: fastcgi_read_timeout debe ser coherente con request_terminate_timeout; si Nginx corta antes, verás 504 en el log de Nginx sin ninguna entrada correspondiente en el de PHP, y pasarás un buen rato buscando en el sitio equivocado.

Si quieres profundizar en el ajuste del servidor web, tenemos una entrada dedicada a la optimización de Nginx y sus configuraciones para web y ecommerce.

Monitorización: saber si tu configuración funciona

Configurar sin medir es adivinar. Con muy poco esfuerzo puedes tener visibilidad real del pool:

  • Página de estado de FPMpm.status_path = /fpm-status, restringida en Nginx a allow 127.0.0.1; deny all;. Añadiendo ?full lista el estado de cada worker y la petición que está sirviendo.
  • Slowlog — revisa www-slow.log una vez por semana. Los backtraces repetidos señalan directamente las funciones que hay que optimizar o cachear.
  • Métricas de negocio — el tiempo de respuesta del servidor influye en el TTFB y, a través de él, en la experiencia percibida y en las métricas que Google usa para posicionar. Lo desarrollamos en el artículo sobre Core Web Vitals e INP en 2026.
  • Visibilidad de red — si el pico de carga viene de tráfico anómalo y no de usuarios reales, el ajuste de workers no lo va a resolver. En esos casos hace falta ver qué entra por la red, algo que tratamos en la serie sobre monitorización de red en empresas.

Errores frecuentes al configurar PHP-FPM

  • Subir pm.max_children hasta que desaparezcan los 502. Los 502 desaparecen y aparece el swap. Si el error es «server reached max_children», el diagnóstico correcto pasa por medir la memoria por proceso antes de subir nada.
  • Repetir directivas en www.conf. Si un parámetro aparece dos veces, gana el último y el primero se ignora en silencio. Es un fallo clásico al pegar bloques de configuración de distintas fuentes.
  • Editar el php.ini de la CLI. Comprueba siempre cuál está activo con php-fpm8.4 -i | grep "Loaded Configuration File".
  • Olvidar recargar tras el cambio. systemctl reload php8.4-fpm aplica la configuración sin cortar las peticiones en curso; usa restart solo si has cambiado el socket o el usuario.
  • Validar antes de aplicar. php-fpm8.4 -t comprueba la sintaxis. Un typo en www.conf impide que el servicio arranque, y descubrirlo tras un reload en producción no es agradable.

PHP-FPM en 2026: versiones y soporte

El calendario de versiones condiciona cualquier plan de optimización, porque no tiene sentido afinar un servidor sobre una rama que va a quedarse sin parches de seguridad:

  • PHP 8.5 (noviembre de 2025) es la rama más reciente. Incorpora el operador pipe |>, la sintaxis clone with, una extensión URI integrada y el atributo #[\NoDiscard]. Soporte activo hasta finales de 2027 y correcciones de seguridad hasta finales de 2029.
  • PHP 8.4 (noviembre de 2024) introdujo los property hooks, la visibilidad asimétrica y funciones como array_find(). Su soporte activo termina a finales de 2026 y pasa entonces a fase de solo seguridad.
  • PHP 8.3 está ya en soporte de solo seguridad hasta finales de 2027.
  • PHP 8.2 llega al final de su vida el 31 de diciembre de 2026. Si tu servidor todavía va con 8.2, la migración debería estar planificada ya.
  • PHP 8.1 y anteriores están fuera de soporte. Seguir en producción con esas ramas implica exponerse a vulnerabilidades sin parche oficial.

En cuanto a PHP-FPM propiamente dicho, la gestión de procesos lleva años estable: los parámetros de esta guía funcionan igual en 8.2 que en 8.5. Lo que sí cambia entre ramas es el rendimiento del propio intérprete, así que una actualización de versión suele dar mejoras «gratis» antes incluso de tocar la configuración.

¿Tu servidor se queda corto en los picos de tráfico?

En Keliam revisamos la configuración de PHP-FPM, Nginx y base de datos con datos reales de tu carga, y dejamos el servidor dimensionado para el tráfico que de verdad tienes.

Solicita tu consulta gratuita →

Conclusión

La correcta configuración de PHP-FPM tiene un impacto directo en el rendimiento y la estabilidad de tus aplicaciones web, y casi siempre es la optimización más barata disponible: no requiere cambiar de servidor ni reescribir código, solo ajustar dos archivos con criterio.

El resumen práctico cabe en cuatro ideas. Calcula pm.max_children a partir de la memoria real de tus procesos y de la RAM que de verdad está libre. Dimensiona OPcache para que quepan todos los archivos de tu aplicación. Mantén coherentes los timeouts de PHP y de Nginx para no perseguir errores fantasma. Y activa el slowlog y la página de estado, porque sin medir solo estás probando combinaciones.

Con esos cuatro puntos cubiertos, la mayoría de servidores absorben varias veces el tráfico que gestionaban antes, con los mismos recursos y con una latencia notablemente más estable.

Scroll al inicio