PHP-FPM (FastCGI Process Manager) es el gestor de procesos que ejecuta PHP detrás de servidores web como Nginx. Mantiene un conjunto de procesos (un pool) listos para atender peticiones y permite aislar cada sitio con su propio usuario, socket y límites. En este tutorial instalarás PHP-FPM 8.3 en Ubuntu 24.04, crearás un pool dedicado para un sitio, lo conectarás a Nginx y ajustarás el número de procesos según la memoria del servidor.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
  • Un usuario no root con privilegios sudo.
  • Nginx instalado (sudo apt install nginx) y el puerto 80 abierto en el cortafuegos (sudo ufw allow 'Nginx HTTP').
  • Un dominio o subdominio apuntando a la IP del servidor si quieres probar con un nombre real. En los ejemplos se usa your_domain; sustitúyelo por el tuyo.

Paso 1: Instalar PHP-FPM

Ubuntu 24.04 incluye PHP 8.3 en sus repositorios oficiales. Instala PHP-FPM junto con las extensiones que usan la mayoría de aplicaciones (MySQL, cURL, XML, mbstring, zip, intl y GD):

sudo apt update
sudo apt install php8.3-fpm php8.3-cli php8.3-opcache php8.3-mysql php8.3-curl php8.3-xml php8.3-mbstring php8.3-zip php8.3-intl php8.3-gd

El paquete crea y arranca el servicio php8.3-fpm. Comprueba que está activo:

systemctl status php8.3-fpm
● php8.3-fpm.service - The PHP 8.3 FastCGI Process Manager
     Loaded: loaded (/usr/lib/systemd/system/php8.3-fpm.service; enabled; preset: enabled)
     Active: active (running) since ...

Verifica también la versión del binario FPM:

php-fpm8.3 -v
PHP 8.3.6 (fpm-fcgi) (built: ...)

Paso 2: Conocer los archivos de configuración

PHP-FPM reparte su configuración en varios archivos. Estos son los que vas a tocar:

RutaPara qué sirve
/etc/php/8.3/fpm/php-fpm.confConfiguración global del gestor (logs, reinicio de emergencia)
/etc/php/8.3/fpm/pool.d/*.confUn archivo por pool; www.conf es el pool por defecto
/etc/php/8.3/fpm/php.iniOpciones de PHP para FPM (distinto del php.ini de la CLI)
/run/php/php8.3-fpm.sockSocket Unix del pool www
/var/log/php8.3-fpm.logLog del gestor de procesos

Antes de reiniciar el servicio tras cualquier cambio, valida la sintaxis:

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

Paso 3: Ajustar php.ini para FPM

Abre el php.ini que usa FPM:

sudo nano /etc/php/8.3/fpm/php.ini

Busca y ajusta estas directivas. Los valores son un punto de partida razonable para una aplicación web típica:

memory_limit = 256M
upload_max_filesize = 32M
post_max_size = 32M
max_execution_time = 60
expose_php = Off
date.timezone = Europe/Madrid

opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 1

post_max_size debe ser igual o mayor que upload_max_filesize. OPcache guarda el código PHP compilado en memoria y es la mejora de rendimiento más barata que puedes aplicar. Si despliegas código siempre reiniciando PHP-FPM, puedes poner opcache.validate_timestamps = 0 para ahorrar comprobaciones de disco.

Aplica los cambios:

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

Paso 4: Crear un pool dedicado para tu sitio

El pool www ejecuta todo como www-data. Si alojas varios sitios, dale a cada uno su propio usuario y pool: si un sitio queda comprometido, no puede leer los archivos de los demás.

Crea un usuario de sistema sin shell para el sitio y su directorio web:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin your_site
sudo mkdir -p /var/www/your_domain/public
sudo chown -R your_site:your_site /var/www/your_domain

Crea el archivo del pool:

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

Añade este contenido:

[your_site]
user = your_site
group = your_site

listen = /run/php/php8.3-fpm-your_site.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500

pm.status_path = /fpm-status
ping.path = /fpm-ping

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

php_admin_value[error_log] = /var/log/php8.3-fpm-your_site-error.log
php_admin_flag[log_errors] = on

Qué hace cada bloque:

  • user/group: los procesos PHP se ejecutan con este usuario.
  • listen.*: el socket pertenece a www-data para que Nginx pueda conectarse a él.
  • pm.*: controla cuántos procesos hay (se ajusta en el paso 6). pm.max_requests recicla cada proceso tras 500 peticiones para contener fugas de memoria.
  • pm.status_path y ping.path: activan la página de estado y el endpoint de salud.
  • slowlog: registra la traza de cualquier petición que tarde más de 5 segundos.
  • php_admin_value: fija valores que el código de la aplicación no puede sobrescribir con ini_set().

Valida y recarga:

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

Comprueba que el nuevo socket existe:

ls -l /run/php/*.sock
srw-rw---- 1 www-data www-data 0 Sep 25 10:05 php8.3-fpm-your_site.sock
srw-rw---- 1 www-data www-data 0 Sep 25 10:05 php8.3-fpm.sock

Paso 5: Conectar Nginx al pool

Crea el bloque de servidor del sitio:

sudo nano /etc/nginx/sites-available/your_domain
server {
    listen 80;
    listen [::]:80;
    server_name your_domain;

    root /var/www/your_domain/public;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm-your_site.sock;
    }

    location ~ ^/(fpm-status|fpm-ping)$ {
        allow 127.0.0.1;
        deny all;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $fastcgi_script_name;
        fastcgi_pass unix:/run/php/php8.3-fpm-your_site.sock;
    }

    location ~ /\.(?!well-known) {
        deny all;
    }
}

El snippet snippets/fastcgi-php.conf que trae Ubuntu ya define SCRIPT_FILENAME y devuelve 404 si el archivo .php no existe, lo que evita que se ejecuten rutas arbitrarias como PHP. La página de estado solo responde a peticiones desde el propio servidor.

Activa el sitio, valida la configuración y recarga Nginx:

sudo ln -s /etc/nginx/sites-available/your_domain /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Crea un script de prueba que muestre con qué usuario se ejecuta PHP:

echo '<?php echo "PHP " . PHP_VERSION . " como " . posix_getpwuid(posix_geteuid())["name"] . "\n";' | sudo -u your_site tee /var/www/your_domain/public/test.php > /dev/null
curl -H "Host: your_domain" http://127.0.0.1/test.php
PHP 8.3.6 como your_site

Si ves el nombre del usuario del pool, Nginx está usando el socket correcto. Borra el archivo de prueba:

sudo rm /var/www/your_domain/public/test.php

Paso 6: Calcular pm.max_children

pm.max_children es el número máximo de procesos PHP simultáneos del pool. Si es demasiado bajo, las peticiones hacen cola; si es demasiado alto, el servidor se queda sin RAM y empieza a usar swap o el OOM killer mata procesos.

La fórmula es:

pm.max_children = (RAM disponible para PHP) / (memoria media por proceso)

Con la aplicación recibiendo tráfico real, mide la memoria media (RSS) de los procesos hijos de PHP-FPM en MB:

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

Mira la memoria total y la que ya consumen otros servicios (base de datos, Nginx, caché):

free -m

Ejemplo: un servidor de 4 GB donde MySQL y el sistema usan unos 1.500 MB deja unos 2.500 MB para PHP. Con procesos de 48 MB, 2500 / 48 ≈ 52. Redondea hacia abajo y deja margen: pm.max_children = 45.

Elige además el modo del gestor:

ModoCuándo usarlo
dynamicOpción general. Mantiene entre min_spare y max_spare procesos ociosos.
ondemandMuchos sitios con poco tráfico. Crea procesos al recibir peticiones y los cierra tras pm.process_idle_timeout.
staticServidor dedicado a una sola aplicación con tráfico alto y constante. Siempre hay max_children procesos.

Para dynamic, una regla habitual es pm.start_servers entre min_spare_servers y max_spare_servers, con max_spare_servers alrededor del 25 % de max_children. Por ejemplo, para 45 procesos:

pm = dynamic
pm.max_children = 45
pm.start_servers = 8
pm.min_spare_servers = 5
pm.max_spare_servers = 12

Aplica los valores en /etc/php/8.3/fpm/pool.d/your_site.conf y recarga:

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

Paso 7: Consultar la página de estado y el slowlog

La página de estado indica si el pool se está quedando corto. Consúltala desde el propio servidor:

curl -s -H "Host: your_domain" http://127.0.0.1/fpm-status
pool:                 your_site
process manager:      dynamic
start time:           25/Sep/2026:10:05:12 +0000
accepted conn:        1824
listen queue:         0
max listen queue:     0
idle processes:       4
active processes:     1
total processes:      5
max active processes: 9
max children reached: 0
slow requests:        2

Los dos campos importantes son:

  • max children reached: si es mayor que 0, el pool llegó al límite y hubo peticiones esperando. Sube pm.max_children si tienes RAM libre.
  • listen queue: peticiones esperando en este momento. Debería ser 0 casi siempre.

Añade ?full para ver cada proceso o ?json para integrarlo en un sistema de monitorización.

El endpoint de salud devuelve pong si el pool responde:

curl -s -H "Host: your_domain" http://127.0.0.1/fpm-ping
pong

Cuando slow requests crece, el slowlog muestra en qué función estaba cada petición lenta:

sudo tail -n 30 /var/log/php8.3-fpm-your_site-slow.log
[25-Sep-2026 10:32:41]  [pool your_site] pid 21457
script_filename = /var/www/your_domain/public/index.php
[0x00007f...] curl_exec() /var/www/your_domain/public/lib/Api.php:88
[0x00007f...] fetchRates() /var/www/your_domain/public/index.php:14

En este ejemplo la lentitud viene de una llamada HTTP externa en Api.php, no de PHP-FPM.

Solución de problemas

502 Bad Gateway en Nginx. Nginx no puede hablar con el socket. Revisa que PHP-FPM está activo y que la ruta de fastcgi_pass coincide con listen del pool:

sudo tail -n 20 /var/log/nginx/error.log

Un error connect() to unix:/run/php/... failed (2: No such file or directory) indica una ruta incorrecta o un pool que no arrancó. (13: Permission denied) indica que listen.owner/listen.group no son www-data.

PHP-FPM no arranca tras un cambio. Ejecuta sudo php-fpm8.3 -t para ver la línea con el error y consulta el journal:

sudo journalctl -u php8.3-fpm -n 50 --no-pager

Aviso "server reached pm.max_children setting". Aparece en /var/log/php8.3-fpm.log. Repite el cálculo del paso 6 y sube el valor, o busca en el slowlog qué hace que las peticiones tarden tanto.

Archivos subidos que fallan con 413. Nginx limita el cuerpo de la petición a 1 MB por defecto. Añade client_max_body_size 32M; al bloque server para que coincida con upload_max_filesize.

Conclusión

Tienes PHP-FPM 8.3 funcionando en Ubuntu 24.04 con un pool aislado por sitio, conectado a Nginx por socket Unix, con OPcache activo y un número de procesos calculado según la RAM real. La página de estado y el slowlog te dicen cuándo ajustar esos valores.

Como siguientes pasos puedes proteger el sitio con un certificado TLS de Let's Encrypt usando Certbot, instalar otra versión de PHP en paralelo para aplicaciones antiguas, o desplegar una aplicación como WordPress sobre este pool.