Desde hace unos días Linuxito está hospedado en un VPS de RamNode y dado que estoy muy familiarizado con Apache, instalé un clásico esquema LAMP (Linux Apache PHP MySQL). Pero compré un VPS con poca memoria RAM (256 MB, por cuestiones de costo), y el servidor está muy al límite, consumiendo incluso algo de swap. Por ello tomé la decisión de migrar a Nginx con PHP en modo CGI, un esquema mucho más eficiente en lo que a consumo de memoria respecta.
Nginx se vende como un servidor HTTP de alto rendimiento, estable y con muy bajo consumo de recursos (algo que me han resaltado varios colegas). Su alto rendimiento se debe a que, a diferencia de Apache que utiliza threads (o procesos, depende cómo se lo configure), Nginx posee una arquitectura asincrónica mucho más escalable y basada en eventos, lo que permite utilizar pequeñas cantidades de memoria.
Por otro lado, el hecho de correr PHP como un servicio separado (en lugar de un módulo de Apache) utilizando PHP-FPM (FastCGI Process Manager) mejora mucho la eficiencia de memoria. Cuando se incluye a PHP como módulo de Apache, cada proceso worker (trabajado en modo MPM prefork) es una copia del espacio de memoria (fork) del proceso maestro. Esto implica que en cada worker se replica el espacio de memoria necesario para correr PHP, aunque no sea necesario (por ejemplo si se están sirviendo recursos no ejecutables como imágenes, archivos HTML, u otros). El resultado es que el consumo de memoria de PHP se replica en cada worker. En definitiva el footprint de memoria de PHP es mucho más grande.
Al utilizar PHP-FPM, el motor intérprete de PHP corre como un servicio aparte, el cual puede recibir peticiones a través de un socket TCP/IP o UNIX tradicional. De esta forma se tienen dos servicios: Nginx para manejar el protocolo HTTP y PHP-FPM para interpretar código PHP. Lo cual resulta más eficiente, ya que se invoca a PHP sólo cuando es necesario.
Este artículo explica detalladamente como instalar y configurar Nginx con PHP-FPM. Específicamente instalando php5-fpm
desde los repositorios, pero compilando Nginx desde sus fuentes. El sistema operativo es Debian 7.
Instalación y configuración de PHP5 FPM
Instalar PHP-FPM desde los repositorios de Debian. Opcionalmente instalar el driver para MySQL (si se van a utilizar bases de datos MySQL):
# apt-get install php5-fpm php5-mysql
Editar el archivo de configuración de php5-fpm
:
# nano /etc/php5/fpm/php-fpm.conf
Especificar el socket sobre el que el servidor FPM escuchará peticiones. Puede ser un socket TCP/IP o un socket UNIX. En este caso se utiliza un socket UNIX.
[...] listen = /var/run/php5-fpm.sock [...]
Luego, configurar el pool por defecto. La configuración varía de acuerdo a las necesidades del servidor. En este caso se utilizan como mínimo 3, y como máximo 10, procesos servidores spare (listos para procesar solicitudes); y un máximo de 75, trabajando en modo dinámico (los procesos hijos se crean/matan a medida que son necesarios).
Además se debe especificar el usuario y grupo con los que correrán los procesos hijos (utilizar el usuario "www-data" por defecto de sistemas Debian y derivados).
# nano /etc/php5/fpm/pool.d/www.conf
Los parámetros de configuración principales quedan de la siguiente forma:
[...] user = www-data group = www-data [...] listen.owner = www-data listen.group = www-data listen.mode = 0660 [...] pm = dynamic pm.max_children = 75 pm.start_servers = 3 pm.min_spare_servers = 3 pm.max_spare_servers = 10 [...]
Para terminar, reiniciar el servicio:
# service php5-fpm restart
Instalación y configuración de Nginx
Instalar las dependencias necesarias para compilar Nginx:
# apt-get install gcc make libpcre3-dev zlib1g-dev git
Descargar los fuentes de Nginx (1.8.0):
# wget http://nginx.org/download/nginx-1.8.0.tar.gz
Descomprimir el paquete:
# tar xf nginx-1.8.0.tar.gz # cd nginx-1.8.0
Configurar, compilar e instalar Nginx 1.8.0:
# ./configure --with-http_ssl_module # make # make install
Descargar e instalar un script de inicio de servicio System V para Nginx:
# git clone https://github.com/Fleshgrinder/nginx-sysvinit-script.git # cd nginx-sysvinit-script # make
Crear un enlace simbólico al binario nginx
dentro del directorio /sbin
:
# ln -s /usr/local/nginx/sbin/nginx /sbin/nginx
Iniciar el servicio:
# service nginx start
Se observa que ha iniciado el proceso maestro y un proceso worker en modo spare (listo para empezar a procesar peticiones). Además, se encuentra abierto el puerto 80, ligado al proceso maestro:

Es posible comprobar el funcionamiento conectándose al puerto 80 con netcat
:

Sino directamente utilizando wget
:

# less index.html

Aunque lo más simple es acceder desde un navegador:

Creo que eso es suficiente para asegurarme de que funciona.
Ahora que Nginx está funcionando, es posible crear un directorio base de trabajo diferente al que utiliza la configuración por defecto:
# mkdir /var/www # chown www-data:www-data /var/www/
Crear un archivo index.html
para verificar el correcto funcionamiento de Nginx:
# nano /var/www/index.html
<html> <head <title>Hola Mundo</title> </head> <body> <h2>Hola Mundo</h2> <p><i>Hola Mundo!!!</i></p> </body> </html>
También crear un archivo index.php
para verificar el funcionamiento de Nginx junto con PHP-FPM:
# nano /var/www/index.php
<?php echo "Hola Mundo!!!\n"; ?>
Verificar la correctitud del script PHP:
root@devuan:~# php /var/www/index.php Hola Mundo!!!
Cambiar el dueño y grupo de estos nuevos archivos:
# chown www-data:www-data /var/www/index.*
Crear un archivo personalizado para responder ante errores 404:
# nano /var/www/404.html
Responder con el mensaje y formato que se desee (por ejemplo un "NOT FOUND" a secas):
NOT FOUND
En el directorio de instalación de Nginx, dentro del directorio html
, crear un archivo info.php
para mostrar la salida clásica de phpinfo();
:
# nano /usr/local/nginx/html/info.php
<?php phpinfo();
Configuración de Nginx
A continuación, configurar el servidor Nginx (la parte más importante):
# nano /usr/local/nginx/conf/nginx.conf
La configuración de Nginx no se parece en nada a la configuración de Apache. Está estructurada de forma totalmente diferente (a simple vista mejor ordenada que la de Apache). Se comienza agrupando por protocolos, donde cada protocolo puede contener varios servidores (los cuales, por ejemplo, escuchan en diferentes puertos). Dentro de cada servidor se definen diferentes ubicaciones (similar a los aliases o VirtualHosts de Apache).
user www-data www-data; worker_processes auto; worker_rlimit_nofile 2048; pcre_jit on; pid /var/run/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; sendfile on; keepalive_timeout 30; gzip on; upstream php5-fpm-sock { server unix:/var/run/php5-fpm.sock; } server { listen 80; server_name localhost; server_tokens off; root /var/www; location / { root /var/www; index index.html index.htm; location ~ \.php { root /var/www; fastcgi_pass php5-fpm-sock; fastcgi_param SCRIPT_FILENAME $request_filename; include fastcgi_params; } } error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/local/nginx/html; } location ~ \..*/.*\.php$ { return 403; } location /info.php { root /usr/local/nginx/html; fastcgi_pass php5-fpm-sock; fastcgi_param SCRIPT_FILENAME $request_filename; include fastcgi_params; } location ~ /\.ht { access_log off; log_not_found off; deny all; } include sites-enabled/*.conf; } }
Esta configuración simple posee un único protocolo ("http") el cual posee un único servidor (escuchando peticiones en el puerto 80
) y diferentes locaciones, las cuales poseen diferentes directorios de trabajo. Pero además, el protocolo "http" esta asociado a un servidor upstream llamado "php5-fpm", que atiende en el socket UNIX /var/run/php5-fpm.sock
. De esta forma (junto con la ubicación ~ \.php
), se interpretan los archivos con extensión .php
utilizando PHP-FPM.
Para configurar Nginx con SSL acceder al artículo Cómo compilar Nginx con soporte para SSL.
A nivel servicio Nginx, se indica que utilice el usuario y grupo "www-data"; que guarde el ID del proceso padre en el archivo /var/run/nginx.pid
(para que el script de servicio /etc/init.d/nginx
sepa qué proceso debe terminar cuando se envía la orden "stop"); y que utilice compilación just in time de expresiones regulares (PCRE JIT), lo cual acelera el procesamiento de expresiones regulares de forma significativa.
Como la primera vez que se inició Nginx se estaba utilizando la configuración por defecto, el script de gestión del servicio no conoce el PID que debe terminar. Por ello es necesario terminarlo manualmente enviando SIGTERM
utilizando el comando kill
:
# ps ax | grep nginx
# kill PID
Luego iniciar el servicio utilizando el script:
# service nginx start

Verificación
Con Nginx y PHP-FPM levantados, verificar el funcionamiento. Primero, acceder a la raíz del servidor:

Se observa que tanto el protocolo, como el servidor escuchando en el puerto 80
, como la sentencia "index" han funcionado correctamente, pues se ha servido el archivo /var/www/index.html
.
Luego, acceder a una URI inválida (un recurso inexistente):

Se redirecciona correctamente a la página de error 404 personalizada.
Verificar el funcionamiento de PHP-FPM accediendo al recurso /info.php
:

No sólo funciona correctamente PHP, sino que además se ha accedido correctamente a URI /info.php
, la cual tiene como base al directorio /usr/local/nginx/html
.
Finalmente, verificar la ejecución del script index.php
:

¡Éxito!
¿Vas a utilizar HTTPS? Cómo compilar Nginx con soporte para SSL/TLS. Además puede interesarte: Cómo habilitar soporte para HTTP/2 en Nginx.
Para finalizar este artículo dejo una muestra del uso de memoria actual en el servidor:
root@linuxito:~# date Thu Aug 20 11:46:19 EDT 2015 root@linuxito:~# ps axl | grep apache 1 0 1857 1 20 0 158864 980 poll_s Ss ? 0:05 /usr/sbin/apache2 -k start 5 33 8112 1857 20 0 162228 11144 semtim S ? 0:01 /usr/sbin/apache2 -k start 5 33 8114 1857 20 0 168116 17248 semtim S ? 0:00 /usr/sbin/apache2 -k start 5 33 8123 1857 20 0 167876 16488 semtim S ? 0:00 /usr/sbin/apache2 -k start 5 33 8125 1857 20 0 159216 2436 poll_s S ? 0:00 /usr/sbin/apache2 -k start 5 33 8129 1857 20 0 159184 2396 semtim S ? 0:00 /usr/sbin/apache2 -k start 5 33 8146 1857 20 0 159184 2192 semtim S ? 0:00 /usr/sbin/apache2 -k start 5 33 8147 1857 20 0 158896 880 semtim S ? 0:00 /usr/sbin/apache2 -k start 5 33 8148 1857 20 0 158896 900 inet_c S ? 0:00 /usr/sbin/apache2 -k start 0 0 8151 8137 20 0 7724 936 pipe_w S+ pts/0 0:00 grep apache
Se observa que Apache utilizaba en ese momento exactamente un total de 54664
KB residentes en memoria física (algo más de 53 MB). En un próximo artículo, y luego de migrar a Nginx con PHP-FPM, haré la comparación correspondiente en lo que a consumo de memoria respecta.
Referencias
PHP-FPM - A simple and robust FastCGI Process Manager for PHP
FastCGI Process Manager (FPM) Configuration
ACTUALIZACIÓN (2015-09-01): Nginx VS. Apache ¿Cuál consume menos recursos? (mi experiencia)