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 su2 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 reloadAhora 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 reload3.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-fpmPara 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 reloadEcha un vistazo al directorio /var/run/php5-fpm:
ls -l /var/run/php5-fpmDeberí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
- PHP-FPM: http://php-fpm.org/
- nginx: http://nginx.org/
- Wiki de nginx: http://wiki.nginx.org/
- Debian: http://www.debian.org/
- Ubuntu: http://www.ubuntu.com/
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”.
Recibe nuevas publicaciones en tu bandeja de entrada.
No spam. Cancela la suscripción en cualquier momento.