Skip to main content

Nginx, para servidores con multiples vhosts a pelo (sin panel) + PHP-FPM

Nginx

Instalado de forma oficial siguiendo la ruta de instalación estándar de Ubuntu.

Mejoras sobre la configuración original

A continuación se describen las mejoras implementadas sobre la configuración estándar. Estas optimizaciones se centran en:

  • Reducción del TTFB (Time To First Byte)
  • Refuerzo de seguridad (especialmente en ciphers)
  • Optimización de rendimiento general

Las directivas que comienzan con # comentario son personalizaciones, ya sea por adición o modificación.

user  www-data;
worker_processes  auto;
worker_rlimit_nofile    65535;  # Incrementa el límite de archivos abiertos


# load_module           modules/ngx_http_modsecurity_module.so;
error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

include                 /etc/nginx/modules-enabled/*.conf;

# Configuración optimizada de eventos
events {
    worker_connections  1024;
    use                 epoll;  # Método eficiente para Linux
    multi_accept        on;     # Acepta múltiples conexiones por proceso
}

http {

    include       /etc/nginx/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"';

    access_log  /var/log/nginx/access.log  main;

    ## Mejoras de rendimiento y seguridad probadas durante años
    sendfile                        on;     # Optimiza el envío de archivos
    tcp_nopush                      on;     # Optimiza paquetes TCP
    tcp_nodelay                     on;     # Reduce latencia
    client_header_timeout           60s;    # Tiempo máximo para recibir cabeceras
    client_body_timeout             60s;    # Tiempo máximo para recibir cuerpo
    client_header_buffer_size       2k;     # Tamaño del buffer para cabeceras
    client_body_buffer_size         256k;   # Tamaño del buffer para el cuerpo
    client_max_body_size            256m;   # Tamaño máximo de petición
    large_client_header_buffers     4 8k;   # Buffers para cabeceras grandes
    send_timeout                    60s;    # Tiempo máximo de envío
    keepalive_timeout               30s;    # Tiempo máximo de conexión persistente
    reset_timedout_connection       on;     # Libera conexiones que expiran
    server_tokens                   off;    # Oculta la versión de Nginx
    server_name_in_redirect         off;    # No incluye nombre del servidor en redirecciones
    server_names_hash_max_size      512;    # Tamaño máximo de la tabla hash
    server_names_hash_bucket_size   512;    # Tamaño del bucket hash

    # Compresión para optimizar el tráfico
    gzip                on;              # Activa la compresión
    gzip_static         on;              # Busca versiones pre-comprimidas
    gzip_vary           on;              # Añade cabecera Vary
    gzip_comp_level     6;               # Nivel de compresión (equilibrio rendimiento/tamaño)
    gzip_min_length     1024;            # Tamaño mínimo para comprimir
    gzip_buffers        16 8k;           # Buffers para compresión
    gzip_types          text/plain text/css text/javascript text/js text/xml application/json application/javascript application/x-javascript application/xml application/xml+rss application/x-font-ttf image/svg+xml font/opentype;
    gzip_proxied        any;             # Comprime respuestas proxy
    gzip_disable        "MSIE [1-6]\.";  # Desactiva para IEs antiguos

    # Configuración de proxy
    proxy_redirect      off;
    proxy_set_header    Host            $host;
    proxy_set_header    X-Real-IP       $remote_addr;
    proxy_set_header    X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass_header   Set-Cookie;
    proxy_buffers       32 4k;
    proxy_connect_timeout   30s;
    proxy_send_timeout  90s;
    proxy_read_timeout  90s;


    # Configuración para Cloudflare (actualizada: 28/09/2023)
    # Permite identificar la IP real del visitante detrás de Cloudflare
    set_real_ip_from   103.21.244.0/22;
    set_real_ip_from   103.22.200.0/22;
    set_real_ip_from   103.31.4.0/22;
    set_real_ip_from   104.16.0.0/13;
    set_real_ip_from   104.24.0.0/14;
    set_real_ip_from   108.162.192.0/18;
    set_real_ip_from   131.0.72.0/22;
    set_real_ip_from   141.101.64.0/18;
    set_real_ip_from   162.158.0.0/15;
    set_real_ip_from   172.64.0.0/13;
    set_real_ip_from   173.245.48.0/20;
    set_real_ip_from   188.114.96.0/20;
    set_real_ip_from   190.93.240.0/20;
    set_real_ip_from   197.234.240.0/22;
    set_real_ip_from   198.41.128.0/17;
    #set_real_ip_from   2400:cb00::/32;  # IPv6 (comentados por compatibilidad)
    #set_real_ip_from   2606:4700::/32;
    #set_real_ip_from   2803:f800::/32;
    #set_real_ip_from   2405:b500::/32;
    #set_real_ip_from   2405:8100::/32;
    #set_real_ip_from   2c0f:f248::/32;
    #set_real_ip_from   2a06:98c0::/29;
    real_ip_header     CF-Connecting-IP;

    # Configuración SSL que cumple con PCI Compliance
    # Basado en https://blog.ss88.us/secure-ssl-https-nginx-vestacp
    ssl_protocols TLSv1.2 TLSv1.3;  # Elimina protocolos obsoletos (SSLv3)
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !MEDIUM";
    #ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384;
    ssl_dhparam dh4096.pem;
    ssl_ecdh_curve secp384r1;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;

    # Configuración de caché
    # IMPORTANTE: Verificar permisos y propiedad de los directorios
    # Especialmente útil en entornos con alta carga de solicitudes
    proxy_cache_path /var/cache/nginx levels=2 keys_zone=cache:10m inactive=60m max_size=1024m;
    proxy_cache_key "$host$request_uri $cookie_user";
    proxy_temp_path  /var/cache/nginx/temp;
    proxy_ignore_headers Expires Cache-Control;
    proxy_cache_use_stale error timeout invalid_header http_502;
    proxy_cache_valid any 1d;

    # Bypass de caché para sesiones activas
    map $http_cookie $no_cache {
        default 0;
        ~SESS 1;
        ~wordpress_logged_in 1;
    }


    # Configuración de caché de archivos
    open_file_cache          max=10000 inactive=30s;
    open_file_cache_valid    60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors   off;

    # Configuración de cabeceras y expiración
    # Mapa de tiempos de expiración según tipo de contenido
    map $sent_http_content_type $expires {
       default                    off;
       text/html                  epoch;  # No cachear HTML (dinámico)
       text/css                   max;    # Cachear CSS al máximo
       application/javascript     max;    # Cachear JS al máximo
       ~image/                    max;    # Cachear imágenes al máximo
    }


	##
	# Configuración de Hosts Virtuales
	##

	include /etc/nginx/conf.d/*.conf;
	include /etc/nginx/sites-enabled/*;
}

Permisos y propiedad del caché de Nginx

Para que Nginx funcione correctamente con la configuración de caché definida, los directorios implicados deben tener los permisos y propiedad adecuados:

Propiedad (Owner y Grupo)

  • Propietario: El usuario con el que se ejecuta Nginx (típicamente www-data en Ubuntu)
  • Grupo: El grupo correspondiente (típicamente www-data en Ubuntu)

Permisos recomendados

  • /var/cache/nginx: 750 (drwxr-x---)
  • /var/cache/nginx/temp: 750 (drwxr-x---)

Comandos para establecer estos permisos

# Crear los directorios si no existen
mkdir -p /var/cache/nginx/temp

# Establecer la propiedad correcta
chown -R www-data:www-data /var/cache/nginx

# Establecer los permisos adecuados
chmod 750 /var/cache/nginx
chmod 750 /var/cache/nginx/temp

Estos permisos garantizan que los directorios sean accesibles únicamente por el usuario que ejecuta Nginx, mejorando la seguridad del sistema al evitar accesos no autorizados a posible información sensible almacenada en caché.

Configuración de los Virtual Hosts

Existen dos enfoques para gestionar los archivos de configuración de hosts virtuales:

1. Enfoque centralizado (recomendado para entornos de producción)

Los archivos de configuración se colocan en /etc/nginx/sites-available/ y se enlazan simbólicamente a /etc/nginx/sites-enabled/:

ln -s /etc/nginx/sites-available/domain.tld.conf /etc/nginx/sites-enabled/

Ventajas:

  • Mayor control y seguridad
  • Centralización de configuraciones
  • Facilita auditorías de seguridad
  • Previene cambios accidentales por usuarios sin privilegios

2. Enfoque por usuario (útil en entornos único administrador)

Los archivos se colocan en directorios de usuario y se enlazan a la configuración principal.

Limitaciones:

  • Los usuarios sin acceso sudo no podrán:
    • Validar configuraciones con nginx -t
    • Recargar el servicio con systemctl reload nginx
    • Reiniciar el servicio con systemctl restart nginx

Configuración inicial (pre-certificado)

Antes de obtener el certificado SSL, se debe crear un archivo de configuración sin asignación específica de puertos. Certbot (Let's Encrypt) se encargará de esto durante el proceso de certificación.

Ejemplo:

server {
    server_name domain.tld www.domain.tld;
    root        /home/user/web/domain.tld/domain/dist;  # Ruta para despliegues JS/VUE/React
    index       index.html;

    # Es ALTAMENTE RECOMENDABLE activar estas cabeceras de seguridad
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    charset utf-8;

    ## Logs ubicados en la carpeta del usuario para facilitar su acceso
    access_log  /home/user/logs/web/domain.tld.log combined;
    error_log   /home/user/logs/web/domain.tld.error.log error;

    expires $expires; # Utiliza la variable map definida en nginx.conf

    # Si has compilado y tienes operativo mod_security, descomenta la línea siguiente
    # include /etc/nginx/modsec/active.conf;

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
    }

    #error_page 404 /index.php;

    location / {
        root  /home/user/web/domain.tld/domain/dist/;
        index index.html;
        try_files $uri $uri/ /index.html;  # Configuración para SPAs (React, Vue, etc.)
        #try_files $uri $uri/ =404;        # Alternativa para sitios estáticos

        location ~* ^.+\.(jpeg|jpg|png|gif|bmp|ico|svg|css|js)$ {
            access_log        off;
            log_not_found     off;
            expires     max;
        }
    }

    # Bloqueo de archivos de configuración sensibles
    location ~* "/\.(htaccess|htpasswd|env)$" {
        deny    all;
        return  404;
    }

    # Bloqueo de archivos y directorios ocultos
    location ~ /\.(?!well-known).* {
        deny all;
    }
}
Configuración de la variable $expires

Esta variable se declara mediante la directiva map en el archivo nginx.conf. Si no está declarada, su uso generará un error.

map $sent_http_content_type $expires {
   default                    off;
   text/html                  epoch;  # No cachear HTML (fecha 1/1/1970)
   text/css                   max;    # Cachear CSS al máximo
   application/javascript     max;    # Cachear JS al máximo
   ~image/                    max;    # Cachear imágenes al máximo
}

Buena práctica: Después de cada modificación, ejecutar nginx -t para verificar la sintaxis de la configuración antes de recargar el servicio.

Certificados con Certbot

Certbot ya viene instalado en la mayoría de distribuciones modernas. Para nuevas instalaciones, su manual oficial proporciona instrucciones detalladas.

Se recomienda crear certificados específicos por dominio en lugar de certificados wildcard:

certbot --nginx -d domain.tld -d www.domain.tld

Nota: En sistemas con restricciones de red, es necesario abrir temporalmente los puertos 80 y 443 durante el proceso de renovación.

PHP

Instalado desde el repositorio de Ondrej Surý, específicamente la versión PHP-FPM.

Ubicaciones importantes:

  • Configuración de pools: /etc/php/X.X/fpm/pool.d/www.conf (donde X.X es la versión de PHP)
  • Configuración general: /etc/php/X.X/fpm/php.ini

Configuración crítica para logging

Es esencial modificar la configuración del pool para activar el registro de errores:

  • Editar /etc/php/X.X/fpm/pool.d/www.conf y cambiar:
    catch_workers_output = yes
    

Sin esta modificación, PHP-FPM no registrará correctamente los errores en los logs de los dominios virtuales, lo que puede dificultar enormemente la depuración. Para más información, consulte PHP log cuando usamos PHP-FPM con host virtuales.

JIT Compiler

PHP 8.x introduce el compilador JIT (Just-In-Time), que puede mejorar significativamente el rendimiento en determinados escenarios. Sin embargo, puede presentar desafíos durante el desarrollo activo con cambios frecuentes.

Para más información sobre la activación y configuración del JIT, consulte Activar PHP8.2 JIT Compiler.

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.