Mitigación de ataques DDoS en Nginx con rate limiting y geoip
Introducción
Los ataques de Denegación de Servicio Distribuido (DDoS) representan una de las amenazas más persistentes y evolucionadas para la infraestructura web. Si bien los servicios de mitigación en la capa de red (como Cloudflare, AWS Shield o Akamai) son la primera línea de defensa, un SysAdmin competente debe implementar mecanismos de contención a nivel de aplicación. Nginx, actuando como proxy inverso o servidor web, ofrece herramientas nativas y modulares para mitigar el impacto de estos ataques sin depender exclusivamente de terceros.
Este artículo se centra en dos técnicas complementarias y altamente efectivas: Rate Limiting (limitación de tasa) y GeoIP (bloqueo geográfico). No solo cubriremos la configuración, sino la lógica subyacente, los flujos de trabajo de implementación y las decisiones arquitectónicas que separan una mitigación efectiva de una configuración contraproducente.
Fundamentos de la Mitigación a Nivel de Aplicación
Antes de escribir una sola línea de configuración, debemos entender el por qué de cada decisión. Un ataque DDoS no es un monolito; se manifiesta en diferentes capas del modelo OSI.
| Tipo de Ataque | Capa OSI | Ejemplo | Mitigación en Nginx |
|---|---|---|---|
| Volumétrico | L3/L4 | Inundación SYN, UDP flood | Generalmente inefectivo sin hardware/red. Nginx puede ayudar a filtrar conexiones establecidas. |
| Protocolo | L4 | Slowloris, Ping of Death | Efectivo con limit_conn y timeouts. |
| Aplicación | L7 | HTTP flood, ataques a /wp-login.php, scraping agresivo | El punto fuerte de Nginx. limit_req, limit_conn, GeoIP, reglas de map y if. |
Nginx es excepcional para mitigar ataques de Capa 7 porque puede inspeccionar y actuar sobre el contenido de la solicitud (URI, User-Agent, Cookies, IP de origen) antes de pasarla al backend (PHP-FPM, Node.js, etc.). Esto protege el recurso más valioso: la CPU y memoria de tu servidor de aplicaciones.
Flujo de Trabajo Previo a la Configuración
- Auditoría de Tráfico: Analiza logs de acceso (
access.log) para identificar patrones. ¿Hay picos en ciertas rutas? ¿IPs de regiones específicas que no son tu público objetivo? Usa herramientas comogoaccess,awkongxtop. - Definición de Límites: No existe un "one-size-fits-all". Un sitio de noticias tendrá picos legítimos que un portal corporativo no. Define:
- RPM (Requests Per Minute): Límite por IP para rutas críticas.
- Conexiones concurrentes: Límite por IP para evitar acaparamiento.
- Burst: El tamaño del "bache" permitido antes de aplicar el límite estricto.
- Pruebas en Staging: Nunca implementes límites agresivos en producción sin probar. Un burst demasiado pequeño puede denegar servicio a usuarios legítimos (efecto "false positive").
Implementación de Rate Limiting en Nginx
El rate limiting en Nginx se basa en el módulo ngx_http_limit_req_module. Este módulo utiliza el algoritmo Leaky Bucket (o más precisamente, una variante del Generic Cell Rate Algorithm) para suavizar picos de tráfico.
Configuración Básica con limit_req_zone
Primero, definimos una "zona" de memoria compartida que almacenará el estado de cada clave (típicamente la IP del cliente).
http {
# Define una zona llamada 'login_limit' que almacena hasta 10MB de estados.
# La clave es la variable $binary_remote_addr (IP en binario, más eficiente).
# La tasa es de 1 solicitud por segundo (r/s).
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
# Zona para API pública, más permisiva (10 r/s)
limit_req_zone $binary_remote_addr zone=api_limit:20m rate=10r/s;
server {
listen 80;
server_name ejemplo.com;
location /wp-login.php {
# Aplica la zona 'login_limit'
limit_req zone=login_limit;
# Opcional: tamaño del burst (permite hasta 5 solicitudes en cola)
# nodelay: Si está presente, las solicitudes en burst se sirven inmediatamente
# hasta agotar el burst, luego se rechazan. Sin nodelay, se retrasan.
limit_req zone=login_limit burst=5 nodelay;
proxy_pass http://backend_app;
}
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend_api;
}
}
}
Explicación del por qué:
$binary_remote_addrvs$remote_addr: La versión binaria ocupa menos memoria (4 bytes vs 7-15 bytes para IPv4). Para 10MB de zona, puedes almacenar aproximadamente 160,000 estados de IPs con$binary_remote_addr, frente a muchos menos con la versión textual.rate=1r/s: No es una tasa arbitraria. Un formulario de login legítimo no debería generar más de 1 solicitud por segundo por usuario. Ataques de fuerza bruta o DDoS a login son mucho más rápidos.burst=5 nodelay: Elburstactúa como un amortiguador. Sinnodelay, las solicitudes que exceden la tasa se retrasan (lo que puede ser confuso para el cliente). Connodelay, se sirven inmediatamente hasta llenar el burst, luego se rechazan con un503 Service Unavailable. Esto es preferible para APIs.
Rate Limiting Avanzado: Claves Múltiples y Condicionales
El verdadero poder del rate limiting surge cuando combinas múltiples zonas o usas variables como clave.
Ejemplo: Limitar por IP + User-Agent
http {
# Zona que combina IP y User-Agent. Útil contra ataques que rotan IPs pero usan el mismo UA.
map $http_user_agent $limit_key {
default $binary_remote_addr;
# Si el UA es vacío (común en ataques básicos), usamos una clave fija para bloquear todos.
"" "blocked_empty_ua";
}
limit_req_zone $limit_key zone=combined_limit:10m rate=5r/s;
server {
...
location / {
limit_req zone=combined_limit burst=10 nodelay;
}
}
}
Explicación del por qué:
Usar $binary_remote_addr sola es vulnerable a ataques que utilizan botnets con miles de IPs únicas. Al combinar la IP con el User-Agent (o una cookie de sesión), creamos una huella más robusta. Si un atacante rota IPs pero mantiene el mismo User-Agent, todas esas solicitudes caerán en la misma clave, agotando el límite rápidamente.
Manejo de Respuestas de Error
Cuando se excede el límite, Nginx responde con 503. Podemos personalizar esta respuesta para que sea más informativa o redirigir el tráfico.
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429; # Cambia el código a "Too Many Requests"
# Personaliza la página de error
error_page 429 /custom_429.html;
proxy_pass http://backend_api;
}
Nota Técnica: El código HTTP 429 (Too Many Requests) es semánticamente más correcto que el 503 para rate limiting. El 503 implica que el servidor no puede procesar la solicitud, mientras que el 429 indica que el cliente no debe repetirla tan pronto.
Integración de GeoIP para Bloqueo Geográfico
El módulo GeoIP (tradicionalmente ngx_http_geoip_module, ahora reemplazado por ngx_http_geoip2_module para bases de datos MaxMind DB) permite tomar decisiones basadas en la ubicación geográfica de la IP del cliente.
Instalación y Configuración de GeoIP2
Primero, necesitas la base de datos de MaxMind (GeoLite2) y el módulo de Nginx.
Paso 1: Descargar la base de datos (automatizable con cron)
#!/bin/bash
# Script para descargar la base de datos GeoLite2 (requiere licencia gratuita de MaxMind)
# Se ejecuta semanalmente.
wget -O /usr/share/GeoIP/GeoLite2-Country.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=TU_LICENCIA&suffix=tar.gz"
tar -xzf /usr/share/GeoIP/GeoLite2-Country.tar.gz -C /usr/share/GeoIP/ --strip-components=1
Paso 2: Compilar o instalar el módulo geoip2
Si usas Nginx desde repositorios (ej. Nginx oficial), puedes necesitar compilarlo con el módulo. En Debian/Ubuntu:
# Añadir repositorio de Nginx (versión mainline)
apt update && apt install -y nginx-extras # En Debian, a veces ya incluye geoip2
# O instalar el módulo dinámico
apt install libnginx-mod-http-geoip2
Paso 3: Configurar el servidor
http {
# Cargar el módulo dinámico (si es necesario)
load_module modules/ngx_http_geoip2_module.so;
# Configurar la base de datos
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
# Define una variable $geoip_country_code que contiene el código ISO de 2 letras
$geoip_country_code source=$remote_addr country iso_code;
}
# Bloquear por defecto países no deseados
map $geoip_country_code $block_country {
default 0;
RU 1; # Rusia
CN 1; # China
KP 1; # Corea del Norte
IR 1; # Irán
}
server {
listen 80;
server_name ejemplo.com;
# Bloquear países (devuelve 403)
if ($block_country) {
return 403;
}
location / {
proxy_pass http://backend;
}
}
}
Explicación del por qué:
- Bloqueo temprano: Al colocar el
if ($block_country)en el contextoserver, el bloqueo ocurre antes de que se procese cualquierlocation. Esto ahorra ciclos de CPU y evita que la solicitud llegue al backend. - Uso de
map: La directivamapnos permite crear una lógica de bloqueo compleja sin anidar múltiplesif. Es más eficiente y mantenible. - Base de datos local: No dependes de una API externa para cada solicitud. La resolución geográfica es instantánea y no introduce latencia.
GeoIP + Rate Limiting: Una Combinación Poderosa
No siempre quieres bloquear un país por completo (puede haber usuarios legítimos). Una estrategia más inteligente es aplicar un rate limiting más estricto a regiones de alto riesgo.
http {
geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
$geoip_country_code source=$remote_addr country iso_code;
}
# Zona de rate limiting específica para países de alto riesgo
limit_req_zone $binary_remote_addr zone=high_risk_limit:10m rate=1r/s;
# Zona normal para el resto del mundo
limit_req_zone $binary_remote_addr zone=normal_limit:10m rate=10r/s;
# Variable que selecciona la zona según el país
map $geoip_country_code $rate_limit_zone {
default normal_limit;
RU high_risk_limit;
CN high_risk_limit;
}
server {
location / {
limit_req zone=$rate_limit_zone burst=5 nodelay;
proxy_pass http://backend;
}
}
}
Por qué es superior: En lugar de un bloqueo duro (403), aplicas una limitación severa. Los usuarios legítimos de esos países aún pueden acceder, pero a una tasa muy reducida, mientras que los ataques automatizados se ahogan rápidamente.
Estrategias de Mitigación Combinadas y Casos Reales
Un ataque DDoS real no es limpio. Combina múltiples vectores. Aquí tienes un flujo de trabajo completo para un servidor Nginx bajo ataque.
Flujo de Trabajo de Respuesta a Incidentes
- Detección: El monitor (Prometheus, Grafana, New Relic) alerta de un pico de 500 errores o latencia alta.
- Análisis Rápido:
# Ver las 10 IPs más solicitadas en los últimos 5 minutos tail -n 10000 /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10 # Ver los User-Agents más comunes tail -n 10000 /var/log/nginx/access.log | awk -F'"' '{print $6}' | sort | uniq -c | sort -rn | head -10 # Ver las rutas más solicitadas tail -n 10000 /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10 - Acción Inmediata (sin downtime):
- Si el ataque es a una ruta específica (ej.
/xmlrpc.php): Añade unlocationconlimit_reqextremadamente restrictivo (ej.1r/m). - Si el ataque viene de un solo rango de IPs: Usa
geoomappara bloquear el rango.geo $block_ip { default 0; 192.168.1.0/24 1; } server { if ($block_ip) { return 444; # 444 es un código especial de Nginx que cierra la conexión sin respuesta. } } - Si el ataque usa un User-Agent específico: Bloquéalo.
if ($http_user_agent ~* (bot-malicioso|scanner)) { return 444; }
- Si el ataque es a una ruta específica (ej.
- Mitigación a Largo Plazo:
- Implementa las configuraciones de
limit_req_zoneygeoip2descritas anteriormente. - Configura
limit_connpara evitar que una sola IP acapare todas las conexiones.limit_conn_zone $binary_remote_addr zone=addr:10m; server { location / { limit_conn addr 10; # Máximo 10 conexiones concurrentes por IP proxy_pass http://backend; } } - Habilita
Syn Flood Protectiona nivel de sistema operativo (/etc/sysctl.conf):net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_syn_retries = 2 net.ipv4.tcp_synack_retries = 2 net.ipv4.tcp_max_syn_backlog = 4096
- Implementa las configuraciones de
Tabla Comparativa de Estrategias
| Estrategia | Ventajas | Desventajas | Ideal para |
|---|---|---|---|
| Rate Limiting (IP) | Simple, efectivo contra bots con pocas IPs. | Inútil contra botnets grandes (miles de IPs). | Ataques a login, formularios. |
| Rate Limiting (UA+IP) | Más robusto, difícil de evadir. | Puede bloquear usuarios legítimos con UA extraños. | APIs públicas, sitios de comercio electrónico. |
| GeoIP (Bloqueo duro) | Reduce drásticamente el tráfico basura. | Bloquea usuarios legítimos de regiones enteras. | Sitios con audiencia local muy definida (ej. solo España). |
| GeoIP (Rate limit diferencial) | Equilibrio perfecto entre seguridad y accesibilidad. | Configuración más compleja. | La mayoría de los casos de uso empresarial. |
| Bloqueo por rango IP (CIDR) | Muy efectivo si |
