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: ...)
NotaInstalar el paquete
php8.3-fpmen lugar dephpevita que apt instale Apache ylibapache2-mod-phpcomo dependencia.
Paso 2: Conocer los archivos de configuración
PHP-FPM reparte su configuración en varios archivos. Estos son los que vas a tocar:
| Ruta | Para qué sirve |
|---|---|
/etc/php/8.3/fpm/php-fpm.conf | Configuración global del gestor (logs, reinicio de emergencia) |
/etc/php/8.3/fpm/pool.d/*.conf | Un archivo por pool; www.conf es el pool por defecto |
/etc/php/8.3/fpm/php.ini | Opciones de PHP para FPM (distinto del php.ini de la CLI) |
/run/php/php8.3-fpm.sock | Socket Unix del pool www |
/var/log/php8.3-fpm.log | Log 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 awww-datapara que Nginx pueda conectarse a él.pm.*: controla cuántos procesos hay (se ajusta en el paso 6).pm.max_requestsrecicla cada proceso tras 500 peticiones para contener fugas de memoria.pm.status_pathyping.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 conini_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:
| Modo | Cuándo usarlo |
|---|---|
dynamic | Opción general. Mantiene entre min_spare y max_spare procesos ociosos. |
ondemand | Muchos sitios con poco tráfico. Crea procesos al recibir peticiones y los cierra tras pm.process_idle_timeout. |
static | Servidor 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. Subepm.max_childrensi 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.
