Nginx en Debian 13 para producción (Parte 2): seguridad avanzada, PHP-FPM, Fail2ban, respaldos y monitoreo

En la primera parte de nuestro tutorial aprendimos a instalar y configurar Nginx en Debian 13, crear múltiples sitios web mediante Server Blocks, utilizar dominios locales con /etc/hosts, configurar dominios reales, ejecutar PHP mediante PHP-FPM y habilitar HTTPS con Let’s Encrypt.
Pero publicar una página web no significa que nuestro servidor esté completamente preparado para producción.
Cuando administramos un VPS que aloja varias aplicaciones, especialmente si pertenecen a diferentes clientes, debemos considerar la seguridad de los archivos, el aislamiento de procesos, la prevención de ataques, los respaldos y la supervisión del sistema.
En esta segunda parte aprenderemos a fortalecer Nginx y Debian 13 mediante configuraciones prácticas.
Nuestro objetivo es comprender no solamente qué comandos ejecutar, sino también por qué los utilizamos, cómo verificar que funcionan y qué precauciones debemos tomar.
1. ¿Qué aprenderemos?
Trabajaremos con cinco áreas fundamentales:
- Aislamiento de PHP-FPM: crear usuarios y pools independientes para cada aplicación.
- Protección contra ataques: configurar Fail2ban, limitar solicitudes con Nginx y estudiar ModSecurity con OWASP CRS.
- Respaldos automáticos: proteger archivos, bases de datos y configuraciones, almacenar copias externas y comprobar su restauración.
- Monitoreo: detectar fallas de Nginx, PHP-FPM, almacenamiento, certificados y recursos.
- Administración segura: fortalecer SSH, configurar el firewall, proteger credenciales y preparar procedimientos de recuperación.
Sistemas operativos compatibles
Utilizaremos Debian 13 con PHP 8.4.
Gran parte de los procedimientos también puede adaptarse a:
- Debian 12.
- Ubuntu Server 24.04 LTS y versiones compatibles.
- Linux Mint, principalmente para laboratorios.
- Otras distribuciones Linux, ajustando paquetes, rutas y servicios.
En distribuciones como Fedora, Rocky Linux y AlmaLinux, la organización de algunos archivos y las herramientas de administración pueden ser diferentes.
Advertencia: antes de aplicar cambios a un VPS que ya aloja sitios reales, debemos disponer de respaldos verificados y acceso a la consola de recuperación del proveedor.
PARTE I. AISLAMIENTO DE APLICACIONES CON PHP-FPM
2. ¿Por qué debemos aislar las aplicaciones?
Imaginemos que administramos tres sitios:
/var/www/
├── empresa-a/
├── empresa-b/
└── empresa-c/
Nginx puede tener un Server Block para cada dominio.
Sin embargo, si todas las aplicaciones PHP utilizan el mismo pool PHP-FPM, sus procesos pueden ejecutarse bajo el mismo usuario.
Por ejemplo:
empresa-a.com ─┐
│
empresa-b.com ─┼── PHP-FPM (www-data)
│
empresa-c.com ─┘
Si una aplicación resulta comprometida, podría intentar acceder a archivos de las demás cuando los permisos del sistema lo permitan.
Una medida para reducir este riesgo consiste en asignar un usuario y un pool PHP-FPM diferente a cada sitio.
empresa-a.com
|
v
PHP-FPM empresa-a
|
v
Usuario sitio-a
empresa-b.com
|
v
PHP-FPM empresa-b
|
v
Usuario sitio-b
Esto permite separar las identidades utilizadas por PHP.
Importante: un pool independiente no equivale a una máquina virtual o un contenedor aislado. Los procesos siguen compartiendo el mismo sistema operativo.
3. Crear usuarios independientes
Trabajaremos con dos sitios de ejemplo:
empresa-a.test
empresa-b.test
Primero creamos usuarios de sistema:
sudo adduser --system \
--group \
--home /var/www/empresa-a \
--no-create-home \
sitio-a
sudo adduser --system \
--group \
--home /var/www/empresa-b \
--no-create-home \
sitio-b
Estas cuentas se utilizarán para ejecutar los procesos PHP correspondientes.
No necesitamos permitir que inicien sesión por SSH.
Comprobamos:
id sitio-a
id sitio-b
Crear directorios
sudo mkdir -p /var/www/empresa-a/public
sudo mkdir -p /var/www/empresa-b/public
Asignamos propietarios:
sudo chown -R sitio-a:sitio-a /var/www/empresa-a
sudo chown -R sitio-b:sitio-b /var/www/empresa-b
Establecemos permisos iniciales:
sudo find /var/www/empresa-a -type d -exec chmod 750 {} +
sudo find /var/www/empresa-a -type f -exec chmod 640 {} +
sudo find /var/www/empresa-b -type d -exec chmod 750 {} +
sudo find /var/www/empresa-b -type f -exec chmod 640 {} +
Los directorios tendrán permisos 750 y los archivos 640.
De esta manera evitamos conceder acceso general a todos los usuarios del sistema.
4. Permitir que Nginx lea los archivos públicos
En Debian, los procesos trabajadores de Nginx normalmente utilizan el usuario www-data.
Debemos permitir que Nginx lea los archivos públicos, sin concederle acceso innecesario a todo el proyecto.
Podemos utilizar ACL.
Instalamos:
sudo apt install acl
Concedemos permisos de lectura y recorrido:
sudo setfacl -R -m u:www-data:rX \
/var/www/empresa-a/public
sudo setfacl -R -m u:www-data:rX \
/var/www/empresa-b/public
Permitimos atravesar el directorio padre:
sudo setfacl -m u:www-data:--x /var/www/empresa-a
sudo setfacl -m u:www-data:--x /var/www/empresa-b
¿Qué significa rX?
r concede lectura.
X permite recorrer directorios y ejecutar archivos cuando corresponda según sus permisos existentes.
Esto resulta útil porque Nginx necesita atravesar los directorios para encontrar los archivos públicos.
Podemos comprobar las ACL:
getfacl /var/www/empresa-a/public
Las ACL aplicadas con -R afectan los elementos existentes. Los archivos creados posteriormente podrían necesitar ajustes adicionales mediante ACL predeterminadas o mediante el procedimiento de despliegue.
5. Instalar PHP-FPM
En Debian 13 podemos instalar:
sudo apt update
sudo apt install php8.4-fpm php8.4-cli
Comprobamos:
systemctl status php8.4-fpm
Los archivos de configuración de los pools suelen encontrarse en:
/etc/php/8.4/fpm/pool.d/
Consultamos:
ls /etc/php/8.4/fpm/pool.d/
Normalmente encontraremos:
www.conf
Este es el pool predeterminado.
Crearemos otros pools sin modificar inicialmente el archivo original.
6. Crear un pool PHP-FPM para empresa A
Creamos:
sudo nano /etc/php/8.4/fpm/pool.d/empresa-a.conf
Contenido:
[empresa-a]
user = sitio-a
group = sitio-a
listen = /run/php/empresa-a.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[expose_php] = Off
Explicación de las directivas
[empresa-a] identifica el pool.
user y group establecen la identidad de los procesos PHP.
listen define el socket Unix utilizado para comunicarse con Nginx.
listen.owner y listen.group determinan quién puede acceder al socket.
listen.mode = 0660 concede permisos al propietario y al grupo, pero no a otros usuarios.
pm = dynamic administra dinámicamente la cantidad de procesos.
pm.max_children establece el máximo de procesos trabajadores para este pool.
pm.max_requests permite reciclar procesos después de atender determinado número de solicitudes.
Los valores son ejemplos para un laboratorio. En producción deben ajustarse según el consumo de memoria de las aplicaciones.
7. Crear el pool para empresa B
Creamos:
sudo nano /etc/php/8.4/fpm/pool.d/empresa-b.conf
Contenido:
[empresa-b]
user = sitio-b
group = sitio-b
listen = /run/php/empresa-b.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
pm.max_requests = 500
php_admin_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[expose_php] = Off
Validamos:
sudo php-fpm8.4 -t
Si no hay errores:
sudo systemctl restart php8.4-fpm
Comprobamos los sockets:
sudo ls -l /run/php/
Deberíamos encontrar:
empresa-a.sock
empresa-b.sock
8. Conectar cada sitio de Nginx con su pool
Editamos el Server Block de empresa A:
sudo nano /etc/nginx/sites-available/empresa-a.test
Contenido:
server {
listen 80;
server_name empresa-a.test;
root /var/www/empresa-a/public;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/empresa-a.sock;
}
location ~ /\.(?!well-known/) {
deny all;
}
access_log /var/log/nginx/empresa-a-access.log;
error_log /var/log/nginx/empresa-a-error.log;
}
¿Qué hace fastcgi_pass?
La directiva:
fastcgi_pass unix:/run/php/empresa-a.sock;
indica a Nginx que envíe las solicitudes PHP al pool de empresa A.
Para empresa B:
server {
listen 80;
server_name empresa-b.test;
root /var/www/empresa-b/public;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/empresa-b.sock;
}
location ~ /\.(?!well-known/) {
deny all;
}
access_log /var/log/nginx/empresa-b-access.log;
error_log /var/log/nginx/empresa-b-error.log;
}
Guardamos el segundo archivo en:
/etc/nginx/sites-available/empresa-b.test
Habilitamos ambos sitios:
sudo ln -s /etc/nginx/sites-available/empresa-a.test \
/etc/nginx/sites-enabled/empresa-a.test
sudo ln -s /etc/nginx/sites-available/empresa-b.test \
/etc/nginx/sites-enabled/empresa-b.test
Validamos:
sudo nginx -t
Recargamos:
sudo systemctl reload nginx
Comprobar el aislamiento
Podemos crear temporalmente un archivo PHP en cada sitio:
<?php
echo "UID del proceso: " . posix_geteuid();
Después comparamos el resultado con:
id -u sitio-a
id -u sitio-b
Cada sitio debería mostrar el UID correspondiente a su cuenta.
Eliminamos los archivos de prueba cuando terminemos.
Una limitación importante
El aislamiento mediante pools reduce determinados riesgos, pero no evita por sí solo que una aplicación comprometida modifique sus propios archivos.
Para entornos con clientes diferentes también conviene separar las cuentas de despliegue, las credenciales, las bases de datos y los procesos de mantenimiento.
PARTE II. PROTEGER NGINX CONTRA ATAQUES
9. Configurar Fail2ban
Fail2ban analiza registros y puede bloquear temporalmente direcciones IP cuando detecta patrones definidos.
Es especialmente útil para reducir intentos repetidos de autenticación SSH.
Instalamos:
sudo apt install fail2ban
Creamos:
sudo nano /etc/fail2ban/jail.d/oplinux.local
Contenido:
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true backend = systemd port = ssh
Explicación
bantime establece la duración del bloqueo.
findtime determina el periodo de observación.
maxretry establece el máximo de coincidencias antes de aplicar el bloqueo.
backend = systemd permite consultar registros del journal.
Validamos:
sudo fail2ban-client -t
Reiniciamos:
sudo systemctl restart fail2ban
Comprobamos:
sudo fail2ban-client status
Y:
sudo fail2ban-client status sshd
Precaución: antes de habilitar reglas de bloqueo debemos conservar una sesión administrativa abierta y disponer de acceso a la consola del VPS.
10. Proteger aplicaciones Nginx con Fail2ban
Fail2ban también puede utilizar registros HTTP para detectar determinados comportamientos.
Sin embargo, no existe una regla universal que identifique todos los ataques.
Por ejemplo, podríamos crear filtros para detectar intentos repetidos contra rutas administrativas específicas.
Debemos tener cuidado porque un filtro demasiado amplio puede bloquear visitantes legítimos.
Ejemplo: detectar intentos repetidos contra una ruta administrativa
Supongamos que tenemos una aplicación propia con una ruta:
/admin/login
Podemos crear un filtro:
sudo nano /etc/fail2ban/filter.d/nginx-admin-login.conf
Contenido:
[Definition]
failregex = ^<HOST> - .* "(GET|POST) /admin/login(?:\?[^"]*)? HTTP/[0-9.]+" 401
ignoreregex =
Este filtro es un ejemplo para registros con formato combined convencional.
Busca respuestas HTTP 401 asociadas a la ruta indicada.
No funcionará correctamente si la aplicación devuelve siempre 200 o utiliza otro formato de registro.
Tampoco debemos utilizarlo sin comprobar que el patrón corresponde a nuestro caso real.
Crear la jail
Agregamos:
[nginx-admin-login]
enabled = true
filter = nginx-admin-login
port = http,https
logpath = /var/log/nginx/empresa-a-access.log
maxretry = 5
findtime = 10m
bantime = 1h
Probamos el filtro:
sudo fail2ban-regex \
/var/log/nginx/empresa-a-access.log \
/etc/fail2ban/filter.d/nginx-admin-login.conf
Después validamos y reiniciamos Fail2ban.
¿Qué sucede si utilizamos Cloudflare?
Debemos configurar correctamente la identificación de la IP real del visitante.
Si Nginx registra la dirección del proxy, Fail2ban podría bloquear una IP compartida por muchos usuarios.
Las cabeceras de IP real solamente deben aceptarse desde proxies de confianza.
PARTE III. LIMITAR SOLICITUDES CON NGINX
11. ¿Qué es rate limiting?
Rate limiting significa limitar la frecuencia de determinadas solicitudes.
Puede resultar útil para reducir abuso en rutas como:
- Formularios de inicio de sesión.
- Endpoints de API.
- Formularios públicos.
- Recursos que consumen muchos procesos.
- Rutas susceptibles de automatización abusiva.
Nginx dispone del módulo ngx_http_limit_req_module para controlar la tasa de procesamiento de solicitudes.
12. Configurar limit_req_zone
Editamos:
sudo nano /etc/nginx/nginx.conf
Dentro del bloque http, agregamos:
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
Explicación
$binary_remote_addr identifica al cliente mediante su dirección IP en formato binario.
zone=login:10m crea una zona de memoria compartida de 10 MiB.
rate=5r/m establece una tasa de cinco solicitudes por minuto por clave.
Este límite se aplica según el algoritmo de Nginx, que permite gestionar solicitudes y ráfagas.
No significa necesariamente que toda sexta solicitud dentro de un minuto vaya a rechazarse en cualquier circunstancia.
13. Aplicar el límite a una ruta
Dentro del bloque server de una aplicación:
location = /admin/login {
limit_req zone=login burst=3 nodelay;
proxy_pass http://127.0.0.1:3000;
}
Este ejemplo supone que la aplicación funciona mediante un servicio HTTP local en el puerto 3000.
burst=3 permite gestionar una ráfaga limitada de solicitudes adicionales.
nodelay evita retrasar las solicitudes aceptadas dentro de la ráfaga.
Las solicitudes que exceden el límite pueden rechazarse.
¿Qué ocurre si utilizamos PHP?
En una aplicación PHP debemos conservar el procesamiento FastCGI y adaptar la ubicación al framework.
No debemos copiar una configuración con proxy_pass si nuestra aplicación utiliza fastcgi_pass.
Para una aplicación con controlador frontal, podemos aplicar límites en una ubicación adecuada y comprobar cómo Nginx selecciona las ubicaciones.
También debemos considerar que varios usuarios pueden compartir una misma IP pública.
Un límite demasiado estricto puede afectar redes escolares, empresariales o conexiones móviles.
14. Registrar respuestas de limitación
Podemos configurar:
limit_req_status 429;
El código HTTP 429 indica que se han realizado demasiadas solicitudes.
También podemos revisar los registros de errores:
sudo tail -f /var/log/nginx/error.log
Antes de activar límites estrictos en producción debemos realizar pruebas con tráfico legítimo.
PARTE IV. MODSECURITY Y OWASP CRS
15. ¿Qué es ModSecurity?
ModSecurity es un firewall de aplicaciones web, conocido como WAF.
Permite inspeccionar solicitudes HTTP y aplicar reglas para detectar determinados patrones de ataques.
Entre ellos:
- Inyección SQL.
- Cross-Site Scripting.
- Intentos de ejecutar comandos.
- Solicitudes anómalas.
- Acceso a recursos sensibles.
OWASP Core Rule Set, conocido como CRS, proporciona reglas genéricas que pueden utilizarse con motores compatibles.
16. ¿Cómo se utiliza ModSecurity con Nginx?
Aquí existe una diferencia importante respecto a Apache.
En Apache, ModSecurity puede integrarse mediante un módulo disponible en los repositorios.
En Nginx, la integración depende del conector ModSecurity-nginx, de la versión del motor y de los módulos compatibles con la compilación instalada.
No debemos asumir que instalar libapache2-mod-security2 habilita ModSecurity para Nginx.
Ese paquete corresponde a Apache.
Para utilizar ModSecurity con Nginx debemos verificar:
- La versión de Nginx instalada.
- La disponibilidad de un módulo conector compatible.
- La versión de ModSecurity utilizada.
- La compatibilidad con OWASP CRS.
- Los procedimientos de actualización y mantenimiento.
Podemos consultar los módulos cargados:
sudo nginx -T
Y los paquetes disponibles:
apt search modsecurity
Si la distribución no proporciona una integración compatible y mantenida, podemos evaluar un WAF externo o un proxy especializado.
No recomiendo compilar e instalar un módulo de seguridad directamente sobre un VPS de producción sin haberlo probado previamente en un entorno equivalente.
17. ¿Cómo se configura un WAF correctamente?
Una estrategia recomendable consiste en comenzar con reglas en modo detección.
Primero observamos qué solicitudes generan coincidencias.
Después revisamos posibles falsos positivos.
Finalmente activamos el bloqueo de manera gradual.
Esto es especialmente importante para aplicaciones como:
- WordPress.
- WooCommerce.
- Laravel.
- APIs REST.
- Formularios con contenido HTML.
- Sistemas de subida de archivos.
Un WAF mal configurado puede bloquear solicitudes legítimas.
También debemos mantener actualizadas las reglas.
Un WAF no sustituye la validación de entradas, las consultas parametrizadas ni las actualizaciones de seguridad de la aplicación.
PARTE V. RESPALDOS AUTOMÁTICOS
18. ¿Qué debemos respaldar?
Un servidor Nginx puede contener varios elementos importantes:
/etc/nginx/
/etc/php/
/etc/ssh/
/etc/fail2ban/
/etc/letsencrypt/
/var/www/
Bases de datos
Configuraciones de aplicaciones
Tareas programadas
Debemos identificar qué información es necesaria para reconstruir el servicio.
No basta con guardar los archivos HTML o PHP.
También podemos necesitar bases de datos, certificados, credenciales y configuraciones.
19. Crear un script de respaldo
Crearemos un script Bash para respaldar archivos y configuraciones.
sudo nano /usr/local/sbin/respaldo-nginx.sh
Contenido:
#!/bin/bash
set -Eeuo pipefail
umask 077
DESTINO="/var/backups/oplinux"
FECHA=$(date '+%Y-%m-%d_%H-%M-%S')
mkdir -p "$DESTINO"
ARCHIVO="$DESTINO/nginx_$FECHA.tar.gz"
tar -czf "$ARCHIVO" \
-C / \
etc/nginx \
etc/php \
etc/ssh \
etc/fail2ban \
etc/letsencrypt \
var/www
echo "Respaldo generado: $ARCHIVO"
Explicación
set -Eeuo pipefail ayuda a detectar errores durante la ejecución.
umask 077 establece permisos restrictivos para los archivos nuevos.
DESTINO indica dónde almacenaremos los respaldos.
FECHA permite identificar cada copia.
tar -czf crea un archivo comprimido.
Proteger el script
sudo chown root:root /usr/local/sbin/respaldo-nginx.sh
sudo chmod 700 /usr/local/sbin/respaldo-nginx.sh
Ejecutarlo
sudo /usr/local/sbin/respaldo-nginx.sh
Comprobamos:
sudo ls -lh /var/backups/oplinux/
Este procedimiento es un punto de partida. Para aplicaciones que modifican archivos constantemente, debemos considerar mecanismos que garanticen consistencia durante la copia.
20. Respaldar MariaDB
Si utilizamos MariaDB, podemos generar una exportación lógica.
sudo apt install mariadb-client
Para un servidor local con autenticación administrativa mediante socket Unix:
sudo mariadb-dump \
--all-databases \
--single-transaction \
--routines \
--events \
--triggers \
> /var/backups/oplinux/bases.sql
¿Qué hace –single-transaction?
Permite obtener una vista consistente de tablas transaccionales compatibles, como InnoDB, bajo determinadas condiciones.
No garantiza consistencia para todos los motores ni frente a cambios simultáneos de esquema.
Debemos adaptar el procedimiento al motor y al tipo de aplicación.
Evitar contraseñas expuestas
No debemos incluir contraseñas directamente en la línea de comandos.
Podemos utilizar autenticación mediante socket Unix, archivos de opciones protegidos o herramientas de administración de secretos.
21. Automatizar respaldos con cron
Editamos:
sudo crontab -e
Agregamos:
0 2 * * * /usr/bin/flock -n /run/lock/respaldo-nginx.lock /usr/local/sbin/respaldo-nginx.sh >> /var/log/respaldo-nginx.log 2>&1
Esto ejecutará el respaldo todos los días a las 02:00.
flock evita que dos ejecuciones se superpongan cuando el bloqueo ya está ocupado.
Podemos programar un segundo trabajo para las bases de datos después de crear y probar su script correspondiente.
22. ¿Por qué necesitamos respaldos externos?
Si almacenamos todas las copias en el mismo VPS, una falla completa del servidor puede destruir tanto los datos originales como los respaldos.
Por eso debemos conservar al menos una copia fuera del servidor principal.
Podemos utilizar herramientas como:
- Restic.
- BorgBackup.
- rsync sobre SSH.
- Almacenamiento de objetos compatible.
- Servicios especializados de respaldo.
Para respaldos cifrados y deduplicados, restic es una alternativa interesante.
23. Instalar restic
sudo apt install restic
Para un laboratorio creamos un repositorio local:
sudo mkdir -p /var/backups/restic-lab
Creamos un archivo de contraseña protegido:
sudo install -m 600 -o root -g root /dev/null /root/.restic-password
Editamos:
sudo nano /root/.restic-password
Introducimos una contraseña larga y única.
Inicializamos:
sudo restic \
-r /var/backups/restic-lab \
--password-file /root/.restic-password \
init
Creamos una copia:
sudo restic \
-r /var/backups/restic-lab \
--password-file /root/.restic-password \
backup /var/backups/oplinux
Consultamos:
sudo restic \
-r /var/backups/restic-lab \
--password-file /root/.restic-password \
snapshots
Para producción debemos sustituir el destino local por un repositorio externo.
También debemos conservar la contraseña fuera del VPS, proteger el acceso al repositorio y definir una política de retención.
24. Comprobar y restaurar respaldos
Un respaldo no debe considerarse confiable solamente porque existe.
Debemos comprobar que puede restaurarse.
Para un archivo tar:
sudo tar -tzf /ruta/al/respaldo.tar.gz | head
Para un archivo SQL comprimido:
sudo gzip -t /ruta/al/respaldo.sql.gz
Para restic:
sudo restic \
-r /var/backups/restic-lab \
--password-file /root/.restic-password \
check
Restaurar en un directorio de prueba
sudo mkdir -p /root/prueba-restauracion
sudo restic \
-r /var/backups/restic-lab \
--password-file /root/.restic-password \
restore latest \
--target /root/prueba-restauracion
Después debemos comprobar que los archivos restaurados corresponden a lo esperado.
Las bases de datos deben probarse en una instancia de laboratorio, no directamente sobre la producción.
PARTE VI. MONITOREO DE NGINX Y PHP-FPM
25. ¿Por qué necesitamos monitoreo?
Un servidor puede comenzar a fallar sin detenerse completamente.
Por ejemplo:
- Nginx está activo, pero devuelve errores 502.
- PHP-FPM alcanza su límite de procesos.
- El disco está casi lleno.
- La memoria se agota.
- Un certificado está próximo a vencer.
- La base de datos deja de responder.
El monitoreo permite detectar problemas y generar alertas.
26. Consultar el estado de Nginx
systemctl status nginx
Podemos comprobar si está activo:
systemctl is-active nginx
Para PHP-FPM:
systemctl is-active php8.4-fpm
Sin embargo, que ambos servicios estén activos no garantiza que las aplicaciones funcionen correctamente.
También debemos realizar solicitudes HTTP.
27. Crear un script de monitoreo
Instalamos:
sudo apt install curl mailutils
Creamos:
sudo nano /usr/local/sbin/monitoreo-nginx.sh
Contenido:
#!/bin/bash
set -Eeuo pipefail
ALERTAS=""
FECHA=$(date '+%Y-%m-%d %H:%M:%S')
if ! systemctl is-active --quiet nginx; then
ALERTAS+="Nginx no está activo.\n"
fi
if ! systemctl is-active --quiet php8.4-fpm; then
ALERTAS+="PHP-FPM no está activo.\n"
fi
USO=$(df -P / | awk 'NR==2 {gsub(/%/, "", $5); print $5}')
if [ "$USO" -ge 90 ]; then
ALERTAS+="El disco raíz está al ${USO}%.\n"
fi
if ! curl \
--silent \
--show-error \
--fail \
--max-time 10 \
--output /dev/null \
https://ejemplo.com/; then
ALERTAS+="La página web no respondió correctamente.\n"
fi
if [ -n "$ALERTAS" ]; then
{
echo "Fecha: $FECHA"
echo
printf "%b" "$ALERTAS"
} | mail -s "ALERTA: Nginx" root
fi
Debemos sustituir https://ejemplo.com/ por una dirección real.
¿Qué hace el script?
Comprueba que Nginx esté activo.
Verifica PHP-FPM.
Consulta el porcentaje de ocupación del disco raíz.
Realiza una solicitud HTTPS.
Si detecta algún problema, intenta enviar un correo local a root.
Para recibir realmente las alertas debemos configurar un sistema de correo funcional.
También debemos evitar alertas repetitivas mediante mecanismos de control de estado.
28. Programar el monitoreo
Establecemos permisos:
sudo chown root:root /usr/local/sbin/monitoreo-nginx.sh
sudo chmod 700 /usr/local/sbin/monitoreo-nginx.sh
Probamos:
sudo /usr/local/sbin/monitoreo-nginx.sh
Editamos:
sudo crontab -e
Agregamos:
*/5 * * * * /usr/local/sbin/monitoreo-nginx.sh >> /var/log/monitoreo-nginx.log 2>&1
El script se ejecutará cada cinco minutos.
Una limitación importante
Si el VPS se apaga completamente, el script también dejará de funcionar.
Por eso es recomendable utilizar monitoreo externo que consulte el servidor desde otra infraestructura.
29. Monitorear certificados HTTPS
Podemos consultar las fechas de un certificado:
echo | openssl s_client \
-connect ejemplo.com:443 \
-servername ejemplo.com \
2>/dev/null \
| openssl x509 -noout -dates
Para comprobar si seguirá siendo válido durante los próximos treinta días:
echo | openssl s_client \
-connect ejemplo.com:443 \
-servername ejemplo.com \
2>/dev/null \
| openssl x509 -checkend 2592000 -noout
También debemos probar Certbot:
sudo certbot renew --dry-run
Y revisar sus temporizadores:
systemctl list-timers | grep certbot
30. Monitorear CPU, RAM y almacenamiento
Memoria
free -h
Procesos
top
Almacenamiento
df -h
Inodos
df -i
Carga del sistema
uptime
Podemos instalar herramientas adicionales:
sudo apt install htop sysstat
htop permite observar procesos de forma interactiva.
sysstat proporciona herramientas para consultar estadísticas del sistema.
Debemos establecer umbrales de alerta según los recursos y el comportamiento real del VPS.
31. Monitorear las conexiones de Nginx
Nginx dispone del módulo stub_status, que permite consultar estadísticas básicas.
Podemos crear una ubicación restringida:
location = /nginx-status {
stub_status;
allow 127.0.0.1;
deny all;
}
Este bloque debe colocarse dentro de un bloque server apropiado.
La directiva stub_status requiere que el módulo correspondiente esté disponible.
Podemos comprobar la compilación y los módulos instalados mediante:
nginx -V
Consultar desde el servidor
Si el bloque escucha en localhost y corresponde al host utilizado, podemos consultar:
curl http://127.0.0.1/nginx-status
La respuesta puede mostrar:
Active connections: 3
server accepts handled requests
...
Reading: 0 Writing: 1 Waiting: 2
Estas estadísticas permiten observar conexiones y solicitudes.
No debemos exponer públicamente esta página de estado.
Para monitoreo avanzado podemos utilizar herramientas externas compatibles con Nginx.
32. Monitorear PHP-FPM
PHP-FPM también permite consultar información de sus pools.
En el archivo:
/etc/php/8.4/fpm/pool.d/empresa-a.conf
Podemos agregar:
pm.status_path = /fpm-status
Después debemos configurar cuidadosamente el acceso desde Nginx.
La página de estado debe permanecer restringida a herramientas y usuarios autorizados.
También podemos revisar los registros:
sudo journalctl -u php8.4-fpm --since "1 hour ago"
Debemos prestar atención a advertencias sobre límites de procesos, solicitudes lentas y consumo excesivo de recursos.
PARTE VII. ADMINISTRACIÓN SEGURA DEL VPS
33. Crear una cuenta administrativa
No conviene realizar todas las tareas cotidianas iniciando sesión directamente como root.
Creamos una cuenta:
sudo adduser administrador
La agregamos al grupo sudo:
sudo usermod -aG sudo administrador
Comprobamos:
id administrador
Esta cuenta podrá realizar operaciones administrativas según los permisos configurados.
34. Configurar SSH mediante claves públicas
En nuestra computadora local generamos una clave Ed25519:
ssh-keygen -t ed25519 -C "administracion-vps"
Instalamos la clave pública:
ssh-copy-id administrador@IP_DEL_SERVIDOR
Probamos:
ssh administrador@IP_DEL_SERVIDOR
Debemos confirmar que el acceso funciona antes de deshabilitar la autenticación mediante contraseña.
35. Fortalecer SSH
Creamos:
sudo nano /etc/ssh/sshd_config.d/99-oplinux-hardening.conf
Contenido:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
Explicación
PermitRootLogin no impide iniciar sesión directamente como root mediante SSH.
PasswordAuthentication no deshabilita la autenticación SSH mediante contraseña.
KbdInteractiveAuthentication no deshabilita la autenticación interactiva por teclado.
PubkeyAuthentication yes permite autenticación mediante claves públicas.
Validamos:
sudo sshd -t
Revisamos la configuración efectiva:
sudo sshd -T | grep -E \
'permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication'
Recargamos:
sudo systemctl reload ssh
Importante: mantenemos abierta la sesión actual y comprobamos una nueva conexión antes de cerrar la anterior.
36. Configurar el firewall
Instalamos UFW:
sudo apt install ufw
Permitimos SSH:
sudo ufw allow 22/tcp
Permitimos HTTP:
sudo ufw allow 80/tcp
Permitimos HTTPS:
sudo ufw allow 443/tcp
Configuramos políticas predeterminadas:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Revisamos:
sudo ufw show added
Habilitamos:
sudo ufw enable
Comprobamos:
sudo ufw status verbose
Si utilizamos un puerto SSH diferente, debemos permitirlo antes de habilitar el firewall.
También debemos revisar las reglas del proveedor del VPS.
37. Proteger bases de datos y servicios internos
No debemos exponer innecesariamente servicios como:
3306 - MySQL/MariaDB
6379 - Redis
11211 - Memcached
Normalmente estos servicios deben escuchar únicamente en interfaces internas o redes autorizadas.
Por ejemplo, una base de datos utilizada solamente por aplicaciones locales puede configurarse para aceptar conexiones desde localhost.
Debemos revisar también los puertos publicados por Docker.
Algunas configuraciones de contenedores pueden interactuar con las reglas del firewall de forma diferente a la esperada.
38. Configurar actualizaciones de seguridad
Instalamos:
sudo apt install unattended-upgrades
Configuramos:
sudo dpkg-reconfigure unattended-upgrades
Esta herramienta permite instalar automáticamente determinadas actualizaciones según los repositorios y políticas habilitadas.
Sin embargo, debemos supervisar su funcionamiento.
Algunas actualizaciones pueden requerir reiniciar servicios o el sistema completo.
Consultamos paquetes pendientes:
apt list --upgradable
También podemos revisar los registros de actualizaciones automáticas.
39. Proteger credenciales
Las aplicaciones pueden utilizar archivos que contienen:
- Contraseñas de bases de datos.
- Claves de API.
- Tokens de autenticación.
- Credenciales SMTP.
- Secretos de sesión.
No debemos colocarlos dentro de directorios públicos.
Por ejemplo, Laravel utiliza:
.env
Este archivo debe permanecer fuera del DocumentRoot.
También debemos evitar subirlo a repositorios públicos.
Ejemplo de permisos
Si PHP se ejecuta con el usuario sitio-a:
sudo chown root:sitio-a /var/www/empresa-a/.env
sudo chmod 640 /var/www/empresa-a/.env
Esta configuración permite que el grupo correspondiente lea el archivo, sin conceder acceso general.
Debemos adaptar los permisos al modelo real de despliegue.
40. Preparar un procedimiento de recuperación
Un servidor puede fallar incluso cuando aplicamos medidas de seguridad.
Por eso debemos establecer un procedimiento de recuperación.
Por ejemplo:
Si SSH deja de funcionar: utilizar la consola de recuperación del proveedor.
Si Nginx no inicia: revisar nginx -t, los registros y los cambios recientes.
Si PHP-FPM falla: comprobar el servicio, los sockets y los límites de procesos.
Si una base de datos se pierde: restaurar una copia verificada en un entorno controlado.
Si el VPS es comprometido: aislar el sistema, investigar el incidente, rotar credenciales y considerar reconstruirlo desde una base confiable.
No debemos asumir que eliminar un archivo sospechoso significa que el servidor vuelve a ser seguro.
PARTE VIII. COMPROBACIONES FINALES
41. Verificar los servicios
Nginx:
systemctl status nginx
PHP-FPM:
systemctl status php8.4-fpm
SSH:
systemctl status ssh
Fail2ban:
systemctl status fail2ban
Firewall:
sudo ufw status verbose
42. Validar configuraciones
Nginx:
sudo nginx -t
PHP-FPM:
sudo php-fpm8.4 -t
SSH:
sudo sshd -t
Fail2ban:
sudo fail2ban-client -t
43. Comprobar los sitios web
Realizamos solicitudes:
curl -I https://ejemplo.com
Comprobamos los certificados:
sudo certbot certificates
Probamos la renovación:
sudo certbot renew --dry-run
También debemos revisar formularios, sesiones, conexiones a bases de datos y otras funciones de cada aplicación.
44. Lista final para producción
Antes de alojar sitios reales debemos comprobar:
| Área | Verificación |
|---|---|
| Nginx | Configuración válida y servicios activos |
| Server Blocks | Dominios y directorios correctos |
| PHP-FPM | Pools separados cuando corresponda |
| Permisos | Acceso mínimo necesario |
| SSH | Claves públicas y acceso probado |
| Firewall | Puertos estrictamente necesarios |
| Fail2ban | Filtros probados y bloqueos funcionales |
| Rate limiting | Límites ajustados al tráfico legítimo |
| WAF | Integración compatible y reglas verificadas |
| HTTPS | Certificados válidos y renovación comprobada |
| Respaldos | Copias externas y restauración probada |
| Monitoreo | Alertas internas y externas |
| Actualizaciones | Política de mantenimiento |
| Recuperación | Procedimiento documentado |
45. ¿Nuestro servidor ya está completamente seguro?
No existe una configuración que garantice seguridad absoluta.
Incluso un servidor con Nginx, PHP-FPM, Fail2ban, HTTPS y firewall puede ser vulnerable debido a una aplicación desactualizada, un plugin inseguro, una contraseña comprometida o permisos incorrectos.
Además, el aislamiento mediante pools PHP-FPM no equivale a ejecutar cada aplicación dentro de una máquina virtual independiente.
Si administramos aplicaciones de diferentes clientes, debemos evaluar el nivel de aislamiento necesario y considerar tecnologías adicionales cuando corresponda.
La seguridad es un proceso continuo de actualización, monitoreo, revisión y recuperación.
46. Conclusión
En la primera parte aprendimos a instalar y configurar Nginx en Debian 13, crear Server Blocks, publicar múltiples sitios y habilitar PHP y HTTPS.
En esta segunda parte avanzamos hacia la administración de un servidor de producción.
Aprendimos a separar aplicaciones mediante usuarios y pools PHP-FPM, configurar Fail2ban, limitar solicitudes con Nginx, comprender la integración de ModSecurity y OWASP CRS, crear respaldos automáticos y supervisar los servicios.
También revisamos medidas fundamentales para proteger SSH, el firewall, las credenciales y el sistema operativo.
La enseñanza principal es que administrar un servidor web no consiste solamente en conseguir que una página funcione.
También debemos proteger sus datos, mantener sus servicios, supervisar sus recursos y saber cómo recuperarlo cuando algo falle.
Un servidor de producción requiere mantenimiento constante, procedimientos documentados y pruebas periódicas.
OPERSLINUX — Aprende Linux comprendiendo cómo funciona.
Comprender cada configuración nos permite administrar nuestros servidores con mayor seguridad, identificar problemas y continuar desarrollando habilidades prácticas en Linux.



