En resumen: cada sitio web de un servidor debería tener su propio pool de PHP-FPM con usuario de sistema propio y socket propio. La separación solo es eficaz con los detalles: permisos del socket solo para el servidor web, security.limit_extensions en .php, sin páginas de estado accesibles públicamente, límites propios de procesos y de tiempo de ejecución, y logs separados. Frente a PHP2Shell en sí solo protege la actualización; los pools limitan hasta dónde llega un ataque.
Un servidor, muchos sitios web, un riesgo compartido
En muchos servidores ha crecido con los años algo que nadie planeó así. El sitio web de la empresa, la tienda, una página de campaña de 2019 y un sistema de pruebas funcionan uno junto a otro, a menudo con el mismo usuario y en el mismo pool. Si se compromete la página de campaña olvidada, también quedan expuestos los datos de clientes de la tienda.
PHP-FPM, el gestor de procesos FastCGI de PHP, lo resuelve con pools: grupos de procesos worker con ajustes propios. Cómo funciona el principio básico lo explica PHP-FPM explicado: gestor de procesos, pools y workers. La construcción de pools separados con usuario propio, carpeta temporal propia y open_basedir la describe la guía open_basedir y aislamiento por sitio web. Este artículo va un paso más allá: ¿cómo se revisan los pools existentes y qué ajustes hacen fiable la separación?
Una precisión importante: en PHP2Shell, las vulnerabilidades CVE-2026-109618 y CVE-2026-109619 están en el intérprete de PHP, que comparten todos los pools de una versión de PHP. Un pool propio no impide la explotación. Pero decide si, después, un atacante alcanza un sitio web o todos.
Pools de PHP-FPM con usuario propio: revisar lo existente
Antes de cambiar nada, hágase una idea general. Los comandos siguientes solo leen. Las rutas y los números de versión varían según la distribución:
# Todos los pools con usuario y socket
grep -HE '^\s*(\[|user|group|listen)\s*' /etc/php/*/fpm/pool.d/*.conf
# Pools en ejecución y sus usuarios
ps -eo user,pid,cmd | grep '[p]hp-fpm: pool'
# Sockets y sus permisos
ls -l /run/php/
# Comprobar errores en la configuración (no cambia nada)
sudo php-fpm8.3 -t
Fíjese en tres patrones: varios sitios web en el mismo pool, pools que se ejecutan como www-data o incluso como root, y sockets con permiso de escritura para todos los usuarios. Cada uno de ellos socava la separación.
Si en el servidor se ejecutan varias versiones de PHP, cada una tiene su propio servicio FPM con sus propios directorios de pools. Revise todas, no solo la versión que configuró en último lugar. Precisamente las versiones antiguas con pools olvidados son un riesgo, porque a menudo ni se mantienen ni se reinician tras las actualizaciones. Encontrará indicaciones en Varias versiones de PHP en un servidor. Anote el resultado en una pequeña tabla: sitio web, pool, usuario, socket, versión de PHP. Este resumen es además la base de cualquier cambio posterior.
Los ajustes más importantes por pool
El resumen siguiente se apoya en la configuración de FPM del manual de PHP. Los valores de procesos y tiempos dependen de su carga y de su aplicación:
| Ajuste | Finalidad | Recomendación |
|---|---|---|
user, group |
usuario de los workers | usuario propio por sitio web, nunca root |
listen.owner, listen.group, listen.mode |
permisos del socket | solo el grupo del servidor web, modo 0660 |
listen.acl_users |
acceso por ACL en lugar de propietario | registrar solo el usuario del servidor web |
security.limit_extensions |
extensiones ejecutables | limitar a .php (por defecto .php .phar) |
clear_env |
vaciar variables de entorno | dejar el valor por defecto yes |
pm.max_children |
número máximo de workers | limitar de modo que un sitio web no sature el servidor |
request_terminate_timeout |
cancelación de peticiones bloqueadas | ajustar a la petición legítima más larga |
pm.status_path, ping.path |
páginas de estado y de ping | fijar solo si se necesitan y protegerlas internamente |
slowlog, request_slowlog_timeout |
registro de peticiones lentas | en un archivo propio por pool |
Un extracto de un pool reforzado tiene este aspecto:
; /etc/php/8.3/fpm/pool.d/shop.conf (extracto)
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
request_terminate_timeout = 60s
security.limit_extensions = .php
clear_env = yes
slowlog = /var/log/php/shop.slow.log
request_slowlog_timeout = 10s
php_admin_value[error_log] = /var/log/php/shop.error.log
Ajustes como memory_limit o session.upload_progress.enabled se fijan por pool con php_admin_value y php_admin_flag. Según el manual, estos valores no pueden sobrescribirse con ini_set(). Más información en php_admin_value: bloquear ajustes frente a la sobrescritura.
Valorar bien las páginas de estado y el reciclaje de workers
pm.status_path y ping.path solo están activos si se han fijado. La página de estado muestra peticiones en curso, procesos y carga. Para la monitorización es útil, pero accesible públicamente es una fuente de información innecesaria para los atacantes. Redirija estas rutas en el servidor web solo para direcciones internas. Cómo cooperan de forma segura Nginx y PHP-FPM lo describe Proteger Nginx con PHP-FPM.
pm.max_requests define tras cuántas peticiones se reinicia un worker; por defecto es 0, es decir, nunca. Un valor mayor que cero ayuda contra un consumo de memoria que crece lentamente. No está pensado como protección frente a PHP2Shell: php.net no lo menciona entre las medidas provisionales y subraya que solo la actualización protege por completo. Tras la actualización, además, vale esto: deben reiniciarse todos los workers de todos los pools, porque de lo contrario los procesos antiguos siguen funcionando con el código vulnerable.
Mantenga una plantilla para pools nuevos y cree con ella cada sitio web nuevo. Así la separación surge de forma automática, en lugar de añadirla años después.
Errores típicos en el cambio
El error más frecuente es un pool nuevo que se ejecuta con usuario propio, pero cuyos archivos siguen perteneciendo a www-data. Entonces el pool no puede escribir donde debe, y alguien concede los permisos con demasiada generosidad. Aclare antes qué carpetas deben ser escribibles. El segundo error son las tareas programadas que siguen ejecutándose con el usuario antiguo y crean archivos con propietario equivocado. El tercero es una carpeta de sesiones común para todos los pools, que vuelve a anular la separación.
Después de cada cambio, pruebe el inicio de sesión, las subidas, las cachés y las tareas programadas del sitio web, y observe el log de errores del pool.
¿Cómo ayuda PHP SOS en concreto?
- Registramos todos los pools, usuarios, sockets y ajustes y mostramos dónde los sitios web comparten hoy procesos o permisos.
- Configuramos por sitio web un pool reforzado, con permisos de socket, límite de extensiones, límites de recursos y logs propios.
- Protegemos las páginas de estado, reiniciamos todos los workers de forma controlada y probamos cada sitio web.
- En el marco del refuerzo de servidores documentamos la separación como plantilla para futuros proyectos.
Preguntas frecuentes
¿Por qué debería tener cada sitio web su propio pool de PHP-FPM?
Un pool propio con usuario de sistema propio separa los sitios web a nivel de procesos y de permisos de archivos. Si un sitio web es comprometido, su código no puede leer sin más los archivos de los demás. Además, los límites y ajustes pueden fijarse por separado para cada sitio.
¿Protege un pool propio frente a PHP2Shell?
No. Las vulnerabilidades están en el propio intérprete de PHP, que comparten todos los pools. Solo protege la actualización a una versión corregida con reinicio de todos los workers. Pero los pools separados limitan hasta dónde llega un ataque con éxito.
¿Qué efecto tiene security.limit_extensions?
Según el manual de PHP, el ajuste limita qué extensiones de archivo ejecuta PHP-FPM como script principal. Por defecto son .php y .phar. Limitarlo a .php evita que, por ejemplo, imágenes subidas se ejecuten como PHP si el servidor web está mal configurado.
¿Es peligrosa la página de estado de PHP-FPM?
Revela detalles sobre los procesos y peticiones en curso. Si es accesible públicamente sin protección, facilita la orientación a los atacantes. Según el manual, solo está activa si se ha fijado pm.status_path. Entonces el servidor web debería limitar la ruta a direcciones internas.