Seguridad PHP · 14 min read · Jan 22, 2026

Seguridad de PHP-FPM/Nginx en Entornos de Alojamiento Compartido (Debian/Ubuntu)

Seguridad de PHP-FPM/Nginx en Entornos de Alojamiento Compartido (Debian/Ubuntu)

Versión 1.0
Autor: Falko Timme
Sígueme en Twitter

Si deseas usar nginx y PHP-FPM para entornos de alojamiento compartido, debes tomar decisiones sobre seguridad. En entornos Apache/PHP, puedes usar suExec y/o suPHP para hacer que PHP se ejecute bajo cuentas de usuario individuales en lugar de un usuario del sistema como www-data. No existe algo similar para PHP-FPM, pero afortunadamente PHP-FPM nos permite configurar un “pool” para cada sitio web que hace que los scripts PHP se ejecuten como el usuario/grupo definido en ese pool. Esto te brinda todos los beneficios de suPHP, y además no tienes problemas de transferencia FTP o SCP porque los scripts PHP no necesitan ser propiedad de un usuario/grupo específico para ser ejecutados como el usuario/grupo definido en el pool.

¡No emito ninguna garantía de que esto funcione para ti!

1 Nota Preliminar

Utilizo un vhost llamado www.example.com / example.com aquí con la raíz del documento /var/www/www.example.com/web.

Deberías tener una instalación LEMP funcionando, como se muestra en estos tutoriales:

  • Instalando Nginx con soporte para PHP5 y MySQL en Debian Squeeze
  • Instalando Nginx con soporte para PHP5 (y PHP-FPM) y MySQL en Ubuntu 11.04

Una nota para los usuarios de Ubuntu:

Debido a que debemos ejecutar todos los pasos de este tutorial con privilegios de root, podemos anteponer todos los comandos en este tutorial con la cadena sudo, o podemos convertirnos en root ahora mismo escribiendo

sudo su

2 Lo Que Tenemos Hasta Ahora

En Debian/Ubuntu, el directorio del pool de PHP-FPM es /etc/php5/fpm/pool.d/ - aquí es donde se crearán nuevos pools. El php.ini utilizado por PHP-FPM es /etc/php5/fpm/php.ini. Ya hay un pool, www.conf - echemos un vistazo:

vi /etc/php5/fpm/pool.d/www.conf

| ; Iniciar un nuevo pool llamado 'www'. ; la variable $pool puede ser utilizada en cualquier directiva y será reemplazada por el ; nombre del pool ('www' aquí) [www] ; Prefijo por pool ; Solo se aplica en las siguientes directivas: ; - 'slowlog' ; - 'listen' (unixsocket) ; - 'chroot' ; - 'chdir' ; - 'php_values' ; - 'php_admin_values' ; Cuando no se establece, se aplica el prefijo global (o /usr). ; Nota: Esta directiva también puede ser relativa al prefijo global. ; Valor por defecto: ninguno ;prefix = /path/to/pools/$pool ; La dirección en la que aceptar solicitudes FastCGI. ; Las sintaxis válidas son: ; 'ip.add.re.ss:port' - para escuchar en un socket TCP a una dirección específica en ; un puerto específico; ; 'port' - para escuchar en un socket TCP a todas las direcciones en un ; puerto específico; ; '/path/to/unix/socket' - para escuchar en un socket unix. ; Nota: Este valor es obligatorio. listen = 127.0.0.1:9000 ; Establecer backlog de listen(2). Un valor de '-1' significa ilimitado. ; Valor por defecto: 128 (-1 en FreeBSD y OpenBSD) ;listen.backlog = -1 ; Lista de direcciones ipv4 de clientes FastCGI que están permitidos para conectarse. ; Equivalente a la variable de entorno FCGI_WEB_SERVER_ADDRS en el original ; PHP FCGI (5.2.2+). Tiene sentido solo con un socket TCP de escucha. Cada dirección ; debe estar separada por una coma. Si este valor se deja en blanco, se aceptarán ; conexiones de cualquier dirección IP. ; Valor por defecto: cualquier ;listen.allowed_clients = 127.0.0.1 ; Establecer permisos para el socket unix, si se utiliza uno. En Linux, los permisos de ; lectura/escritura deben establecerse para permitir conexiones desde un servidor web. Muchos ; sistemas derivados de BSD permiten conexiones independientemente de los permisos. ; Valores por defecto: el usuario y grupo se establecen como el usuario en ejecución ; el modo se establece en 0666 ;listen.owner = www-data ;listen.group = www-data ;listen.mode = 0666 ; Usuario/grupo unix de procesos ; Nota: El usuario es obligatorio. Si no se establece el grupo, se utilizará el grupo del usuario por defecto ; . user = www-data group = www-data ; Elegir cómo el administrador de procesos controlará el número de procesos hijo. ; Valores posibles: ; static - un número fijo (pm.max_children) de procesos hijo; ; dynamic - el número de procesos hijo se establece dinámicamente en función de las ; siguientes directivas: ; pm.max_children - el número máximo de hijos que pueden ; estar vivos al mismo tiempo. ; pm.start_servers - el número de hijos creados al inicio. ; pm.min_spare_servers - el número mínimo de hijos en estado 'inactivo' ; (esperando para procesar). Si el número ; de procesos 'inactivos' es menor que este ; número, entonces se crearán algunos hijos. ; pm.max_spare_servers - el número máximo de hijos en estado 'inactivo' ; (esperando para procesar). Si el número ; de procesos 'inactivos' es mayor que este ; número, entonces se matarán algunos hijos. ; Nota: Este valor es obligatorio. pm = dynamic ; El número de procesos hijo que se crearán cuando pm se establece en 'static' y el ; número máximo de procesos hijo que se crearán cuando pm se establece en 'dynamic'. ; Este valor establece el límite en el número de solicitudes simultáneas que se servirán. ; Equivalente a la directiva ApacheMaxClients con mpm_prefork. ; Equivalente a la variable de entorno PHP_FCGI_CHILDREN en el original PHP ; CGI. ; Nota: Usado cuando pm se establece en 'static' o 'dynamic' ; Nota: Este valor es obligatorio. pm.max_children = 50 ; El número de procesos hijo creados al inicio. ; Nota: Usado solo cuando pm se establece en 'dynamic' ; Valor por defecto: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 ;pm.start_servers = 20 ; El número mínimo deseado de procesos de servidor inactivos. ; Nota: Usado solo cuando pm se establece en 'dynamic' ; Nota: Obligatorio cuando pm se establece en 'dynamic' pm.min_spare_servers = 5 ; El número máximo deseado de procesos de servidor inactivos. ; Nota: Usado solo cuando pm se establece en 'dynamic' ; Nota: Obligatorio cuando pm se establece en 'dynamic' pm.max_spare_servers = 35 ; El número de solicitudes que cada proceso hijo debe ejecutar antes de volver a nacer. ; Esto puede ser útil para solucionar problemas de fugas de memoria en bibliotecas de terceros. Para ; el procesamiento de solicitudes infinitas especifica '0'. Equivalente a PHP_FCGI_MAX_REQUESTS. ; Valor por defecto: 0 ;pm.max_requests = 500 ; La URI para ver la página de estado de FPM. Si este valor no se establece, no se ; reconocerá ninguna URI como una página de estado. Por defecto, la página de estado muestra la siguiente ; información: ; accepted conn - el número de solicitudes aceptadas por el pool; ; pool - el nombre del pool; ; process manager - estático o dinámico; ; idle processes - el número de procesos inactivos; ; active processes - el número de procesos activos; ; total processes - el número de procesos inactivos + activos. ; max children reached - número de veces que se ha alcanzado el límite de procesos, ; cuando pm intenta iniciar más hijos (solo funciona para ; pm 'dynamic') ; Los valores de 'idle processes', 'active processes' y 'total processes' son ; actualizados cada segundo. El valor de 'accepted conn' se actualiza en tiempo real. ; Ejemplo de salida: ; accepted conn: 12073 ; pool: www ; process manager: static ; idle processes: 35 ; active processes: 65 ; total processes: 100 ; max children reached: 1 ; Por defecto, la salida de la página de estado se formatea como text/plain. Pasar ya sea ; 'html' o 'json' como una cadena de consulta devolverá la sintaxis de salida correspondiente. ; Ejemplo: ; http://www.foo.bar/status ; http://www.foo.bar/status?json ; http://www.foo.bar/status?html ; Nota: El valor debe comenzar con una barra inclinada (/). El valor puede ser ; cualquier cosa, pero puede no ser una buena idea usar la extensión .php o puede ; entrar en conflicto con un archivo PHP real. ; Valor por defecto: no establecido ;pm.status_path = /status ; La URI de ping para llamar a la página de monitoreo de FPM. Si este valor no se establece, no ; se reconocerá ninguna URI como una página de ping. Esto podría usarse para probar desde fuera ; que FPM está vivo y respondiendo, o para ; - crear un gráfico de disponibilidad de FPM (rrd o similar); ; - eliminar un servidor de un grupo si no está respondiendo (balanceo de carga); ; - activar alertas para el equipo operativo (24/7). ; Nota: El valor debe comenzar con una barra inclinada (/). El valor puede ser ; cualquier cosa, pero puede no ser una buena idea usar la extensión .php o puede ; entrar en conflicto con un archivo PHP real. ; Valor por defecto: no establecido ;ping.path = /ping ; Esta directiva puede usarse para personalizar la respuesta de una solicitud de ping. La ; respuesta se formatea como text/plain con un código de respuesta 200. ; Valor por defecto: pong ;ping.response = pong ; El tiempo de espera para servir una sola solicitud después del cual el proceso trabajador será ; asesinado. Esta opción debe usarse cuando la opción ini 'max_execution_time' no ; detiene la ejecución del script por alguna razón. Un valor de '0' significa 'apagado'. ; Unidades disponibles: s(econds)(por defecto), m(inutes), h(ours), o d(ays) ; Valor por defecto: 0 ;request_terminate_timeout = 0 ; El tiempo de espera para servir una sola solicitud después del cual un backtrace de PHP será ; volcado en el archivo 'slowlog'. Un valor de '0s' significa 'apagado'. ; Unidades disponibles: s(econds)(por defecto), m(inutes), h(ours), o d(ays) ; Valor por defecto: 0 ;request_slowlog_timeout = 0 ; El archivo de registro para solicitudes lentas ; Valor por defecto: no establecido ; Nota: slowlog es obligatorio si request_slowlog_timeout está establecido ;slowlog = log/$pool.log.slow ; Establecer rlimit de descriptores de archivo abiertos. ; Valor por defecto: valor definido por el sistema ;rlimit_files = 1024 ; Establecer rlimit de tamaño máximo de núcleo. ; Valores posibles: 'ilimitado' o un entero mayor o igual a 0 ; Valor por defecto: valor definido por el sistema ;rlimit_core = 0 ; Chroot a este directorio al inicio. Este valor debe definirse como una ; ruta absoluta. Cuando este valor no se establece, no se utiliza chroot. ; Nota: puedes prefijar con '$prefix' para chroot al prefijo del pool o a uno ; de sus subdirectorios. Si el prefijo del pool no está establecido, se utilizará el prefijo global ; en su lugar. ; Nota: chrooting es una gran característica de seguridad y debe usarse siempre que ; sea posible. Sin embargo, todas las rutas de PHP serán relativas al chroot ; (error_log, sessions.save_path, ...). ; Valor por defecto: no establecido ;chroot = ; Chdir a este directorio al inicio. ; Nota: se puede usar una ruta relativa. ; Valor por defecto: directorio actual o / cuando chroot chdir = / ; Redirigir stdout y stderr del trabajador al registro de errores principal. Si no se establece, stdout y ; stderr se redirigirán a /dev/null de acuerdo con las especificaciones de FastCGI. ; Nota: en un entorno de alta carga, esto puede causar algún retraso en el tiempo ; de procesamiento de la página (varios ms). ; Valor por defecto: no ;catch_workers_output = yes ; Pasar variables de entorno como LD_LIBRARY_PATH. Todos los $VARIABLEs se toman de ; el entorno actual. ; Valor por defecto: entorno limpio ;env[HOSTNAME] = $HOSTNAME env[PATH] = /usr/local/bin:/usr/bin:/bin env[TMP] = /tmp env[TMPDIR] = /tmp env[TEMP] = /tmp ; Definiciones adicionales de php.ini, específicas para este pool de trabajadores. Estas configuraciones ; sobrescriben los valores previamente definidos en el php.ini. Las directivas son las ; mismas que la SAPI de PHP: ; php_value/php_flag - puedes establecer definiciones clásicas de ini que pueden ; ser sobrescritas desde la llamada de PHP 'ini_set'. ; php_admin_value/php_admin_flag - estas directivas no serán sobrescritas por ; la llamada de PHP 'ini_set' ; Para php_*flag, los valores válidos son on, off, 1, 0, true, false, yes o no. ; Definir 'extension' cargará la extensión compartida correspondiente desde ; extension_dir. Definir 'disable_functions' o 'disable_classes' no ; sobrescribirá los valores previamente definidos en php.ini, sino que añadirá el nuevo valor ; en su lugar. ; Nota: las opciones de ruta INI pueden ser relativas y se expandirán con el prefijo ; (pool, global o /usr) ; Valor por defecto: nada está definido por defecto excepto los valores en php.ini y ; especificados al inicio con el argumento -d ;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f [email protected] ;php_flag[display_errors] = off ;php_admin_value[error_log] = /var/log/fpm-php.www.log ;php_admin_flag[log_errors] = on ;php_admin_value[memory_limit] = 32M |

Como ves, este pool está escuchando en el puerto 9000 en localhost (127.0.0.1), y se está ejecutando como el usuario y grupo www-data.

Echemos un vistazo a la configuración de PHP en tu vhost:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

La parte importante es la línea fastcgi_pass 127.0.0.1:9000; - esto hace que nginx pase las solicitudes PHP al proceso PHP-FPM que escucha en el puerto 9000 en localhost (127.0.0.1) - como recordarás, este es nuestro pool definido en /etc/php5/fpm/pool.d/www.conf lo que significa que los scripts PHP se ejecutan como el usuario y grupo www-data.

3 Definiendo Un Pool Individual Para Cada Sitio Web

Mi sitio web example.com es propiedad del usuario web1 y del grupo client0, así que quiero que mis scripts PHP se ejecuten como ese usuario y grupo. Por lo tanto, defino un nuevo pool /etc/php5/fpm/pool.d/example.com.conf:

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Como ves, hago que este pool escuche en el puerto 9001 en lugar de 9000, y defino el usuario como web1 y el grupo como client0. Puedes definir tantos pools como desees, pero asegúrate de usar un puerto no utilizado para cada pool (9002, 9003, etc.).

Recarga PHP-FPM:

/etc/init.d/php5-fpm reload

Ahora cambiamos nuestra configuración de vhost para hacer uso del nuevo pool. Todo lo que necesitas cambiar es el puerto en la línea fastcgi_pass:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass 127.0.0.1:9001; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Recarga nginx después:

/etc/init.d/nginx reload

¡Eso es todo! Los scripts PHP ahora se están ejecutando como el usuario web1 y el grupo client0.

Puedes hacer que PHP sea aún más seguro cambiando la configuración de PHP individualmente para cada vhost. Echa un vistazo al final de /etc/php5/fpm/pool.d/www.conf, tiene algunos ejemplos de cómo lograr esto.

Por ejemplo, podrías establecer open_basedir o disable_functions en el pool /etc/php5/fpm/pool.d/example.com.conf.

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = 127.0.0.1:9001 listen.allowed_clients = 127.0.0.1 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / php_admin_value[open_basedir] = /var/www/www.example.com:/usr/share/php5:/tmp:/usr/share/phpmyadmin:/etc/phpmyadmin:/var/lib/phpmyadmin php_admin_value[disable_functions] = dl,exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source |

Recarga PHP-FPM:

/etc/init.d/php5-fpm reload

3.1 Usando Sockets En Lugar De Conexiones TCP

Hasta ahora, hemos utilizado conexiones TCP para nuestro pool de PHP-FPM (127.0.0.1:9000, 127.0.0.1:9001, etc.). Esto causa cierta sobrecarga. Afortunadamente, podemos usar sockets Unix en lugar de conexiones TCP para nuestros pools y deshacernos de esta sobrecarga. Por lo tanto, los sockets Unix son más eficientes que las conexiones TCP.

Quiero que los sockets se creen en el directorio /var/run/php5-fpm, por lo que primero debemos crear ese directorio:

mkdir /var/run/php5-fpm

Para usar un socket Unix, simplemente cambiamos la línea listen en nuestra definición de pool, comentamos o eliminamos la línea listen.allowed_clients (tiene sentido solo para conexiones TCP), y añadimos las líneas listen.owner (define el propietario del socket), listen.group (define el grupo del socket) y listen.mode (define los permisos del socket):

vi /etc/php5/fpm/pool.d/example.com.conf

| [example.com] listen = /var/run/php5-fpm/example.com.sock ;listen.allowed_clients = 127.0.0.1 listen.owner = web1 listen.group = client0 listen.mode = 0660 user = web1 group = client0 pm = dynamic pm.max_children = 50 pm.start_servers = 20 pm.min_spare_servers = 5 pm.max_spare_servers = 35 chdir = / |

Recarga PHP-FPM después:

/etc/init.d/php5-fpm reload

Echa un vistazo al directorio /var/run/php5-fpm:

ls -l /var/run/php5-fpm

Deberías encontrar el socket example.com.sock allí con los permisos 0660, propiedad del usuario web1 y del grupo client0:

root@server1:~# ls -l /var/run/php5-fpm  
 total 0  
 srw-rw---- 1 web1 client0 0 2011-09-21 11:08 example.com.sock  
 root@server1:~#

Finalmente, debemos cambiar la línea fastcgi_pass en nuestro vhost de nginx a fastcgi_pass unix:/var/run/php5-fpm/example.com.sock;:

vi /etc/nginx/sites-available/example.com.vhost

| server { [...] location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/var/run/php5-fpm/example.com.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_script_name; include /etc/nginx/fastcgi_params; } [...] } |

Recarga nginx después:

/etc/init.d/nginx reload

¡Eso es todo!

4 Enlaces

Acerca del Autor

Falko Timme es el propietario de Timme Hosting (alojamiento web nginx ultra-rápido). Es el mantenedor principal de HowtoForge (desde 2005) y uno de los desarrolladores principales de ISPConfig (desde 2000). También ha contribuido al libro de O’Reilly “Administración de Sistemas Linux”.

Share: X/Twitter LinkedIn

Recibe nuevas publicaciones en tu bandeja de entrada.

No spam. Cancela la suscripción en cualquier momento.