Un ataque de denegación de servicio distribuido (DDoS) intenta agotar un recurso de tu servidor: el ancho de banda del enlace, la tabla de conexiones del kernel o los procesos de la aplicación. Ningún ajuste en el servidor puede frenar un ataque que satura el enlace, pero sí puedes conseguir que los ataques más pequeños, las inundaciones de SYN y los abusos a nivel HTTP no tumben tus servicios. En este tutorial aplicarás esas defensas en capas en Ubuntu 24.04: ajustes del kernel, límites por IP con nftables y limitación de peticiones en Nginx, y aprenderás a reconocer un ataque en curso.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Nginx instalado si quieres aplicar la parte de la capa de aplicación.
- Acceso a la consola de emergencia (VNC o similar) de tu proveedor, por si una regla de cortafuegos te deja sin acceso SSH.
Qué puede mitigar el servidor y qué no
Antes de tocar nada, conviene saber en qué capa se frena cada tipo de ataque:
| Tipo de ataque | Qué agota | Dónde se mitiga |
|---|---|---|
| Volumétrico UDP, amplificación DNS/NTP/memcached | Ancho de banda del enlace | En la red del proveedor o un servicio de mitigación, antes de llegar al servidor |
| SYN flood | Cola de conexiones pendientes del kernel | Kernel (SYN cookies) y cortafuegos |
| Muchas conexiones desde pocas IP | Tabla de conexiones, workers | Cortafuegos (límites por IP) |
| HTTP flood, fuerza bruta en URLs costosas | CPU de la aplicación y la base de datos | Servidor web (rate limiting), caché, CDN o WAF |
| Slowloris y conexiones lentas | Conexiones abiertas del servidor web | Tiempos de espera del servidor web |
Si el tráfico de ataque supera la capacidad de tu enlace, los paquetes se pierden antes de llegar al kernel y nada de lo que configures en el servidor servirá. Para esos casos depende de la protección DDoS de la red de tu proveedor o de un CDN delante de tus servicios web. Lo que sigue en esta guía cubre las otras filas de la tabla.
Paso 1: Medir el estado normal
Para reconocer un ataque necesitas saber qué es lo normal en tu servidor. Consulta el resumen de sockets:
ss -s
Total: 312
TCP: 48 (estab 21, closed 9, orphaned 0, timewait 9)
Cuenta las conexiones en estado SYN-RECV, es decir, handshakes a medio completar:
ss -Htn state syn-recv | wc -l
0
Revisa los contadores del kernel relacionados con la cola de conexiones y las SYN cookies:
nstat -az TcpExtSyncookiesSent TcpExtListenOverflows TcpExtListenDrops
#kernel
TcpExtSyncookiesSent 0 0.0
TcpExtListenOverflows 0 0.0
TcpExtListenDrops 0 0.0
En un servidor sano, SYN-RECV se mantiene cerca de cero y estos contadores apenas crecen. Anota los valores habituales.
Paso 2: Ajustar el kernel frente a SYN floods
Las SYN cookies permiten al kernel responder a los SYN sin reservar memoria cuando la cola de conexiones pendientes se llena, lo que neutraliza la mayoría de las inundaciones de SYN. Ubuntu ya las activa por defecto, pero conviene fijarlas junto al resto de ajustes en un fichero propio:
sudo nano /etc/sysctl.d/60-ddos.conf
# SYN cookies cuando se llena la cola de conexiones pendientes
net.ipv4.tcp_syncookies = 1
# Cola de conexiones pendientes (SYN-RECV) más grande
net.ipv4.tcp_max_syn_backlog = 8192
# Menos reintentos de SYN-ACK: las conexiones falsas se liberan antes
net.ipv4.tcp_synack_retries = 2
# Cola de conexiones aceptadas por socket en escucha
net.core.somaxconn = 8192
# Paquetes en cola por CPU antes de que el kernel los procese
net.core.netdev_max_backlog = 16384
# Descartar paquetes con origen falsificado (filtro de ruta inversa)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# No aceptar redirecciones ICMP
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
Aplica todos los ficheros de /etc/sysctl.d:
sudo sysctl --system
Comprueba que los valores están activos:
sysctl net.ipv4.tcp_syncookies net.ipv4.tcp_max_syn_backlog net.core.somaxconn
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
Nota
rp_filter = 1(modo estricto) descarta paquetes que llegan por una interfaz distinta a la que se usaría para responder. Si tu servidor tiene varias interfaces con rutas asimétricas, usa el valor2(modo flexible).
somaxconn es solo el límite máximo: cada aplicación pide su propia cola al abrir el puerto. En Nginx, por ejemplo, se ajusta con el parámetro backlog de la directiva listen.
Paso 3: Limitar conexiones por IP con nftables
Las reglas siguientes van en una tabla nftables propia, inet ddos, que solo descarta tráfico abusivo y deja pasar el resto. Así conviven con UFW o con cualquier otro cortafuegos que ya tengas: un paquete aceptado aquí sigue pasando por las reglas de UFW. Instala nftables si no está presente:
sudo apt install -y nftables
Crea el fichero de reglas:
sudo nano /etc/nftables-ddos.nft
#!/usr/sbin/nft -f
# Crear y borrar la tabla permite recargar el fichero sin duplicar reglas
table inet ddos
delete table inet ddos
table inet ddos {
# IP que abren conexiones nuevas demasiado rápido
set syn_rate_v4 {
type ipv4_addr
flags dynamic, timeout
timeout 1m
}
set syn_rate_v6 {
type ipv6_addr
flags dynamic, timeout
timeout 1m
}
# Recuento de conexiones simultáneas por IP hacia la web
set web_conn_v4 {
type ipv4_addr
flags dynamic
}
set web_conn_v6 {
type ipv6_addr
flags dynamic
}
chain input {
type filter hook input priority filter - 10; policy accept;
# Paquetes que no pertenecen a ninguna conexión válida
ct state invalid counter drop
# Conexiones TCP nuevas que no empiezan con un SYN
ct state new tcp flags & (fin|syn|rst|ack) != syn counter drop
# Más de 30 conexiones nuevas por segundo desde una misma IP
ct state new tcp flags syn update @syn_rate_v4 { ip saddr limit rate over 30/second burst 60 packets } counter drop
ct state new tcp flags syn update @syn_rate_v6 { ip6 saddr limit rate over 30/second burst 60 packets } counter drop
# Más de 100 conexiones simultáneas a HTTP/HTTPS desde una misma IP
tcp dport { 80, 443 } ct state new add @web_conn_v4 { ip saddr ct count over 100 } counter drop
tcp dport { 80, 443 } ct state new add @web_conn_v6 { ip6 saddr ct count over 100 } counter drop
# Limitar el ping entrante
icmp type echo-request limit rate over 10/second burst 20 packets counter drop
icmpv6 type echo-request limit rate over 10/second burst 20 packets counter drop
}
}
Los umbrales son un punto de partida razonable para un servidor web. Si detrás de una misma IP hay muchos usuarios legítimos (una oficina con NAT, un proxy corporativo), súbelos.
Comprueba la sintaxis sin cargar nada:
sudo nft -c -f /etc/nftables-ddos.nft
Si no hay salida, la sintaxis es correcta. Carga las reglas:
sudo nft -f /etc/nftables-ddos.nft
Verifica que la tabla existe y que puedes seguir abriendo sesiones SSH nuevas desde otra terminal antes de cerrar la actual:
sudo nft list chain inet ddos input
table inet ddos {
chain input {
type filter hook input priority filter - 10; policy accept;
ct state invalid counter packets 12 bytes 480 drop
ct state new tcp flags & (fin | syn | rst | ack) != syn counter packets 0 bytes 0 drop
...
}
}
Cargar las reglas al arrancar
No uses /etc/nftables.conf para esto: su contenido por defecto empieza con flush ruleset, que borraría también las reglas de UFW. Crea una unidad de systemd que cargue solo este fichero:
sudo nano /etc/systemd/system/nftables-ddos.service
[Unit]
Description=Reglas nftables contra DDoS
Wants=network-pre.target
Before=network-pre.target
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/nft -f /etc/nftables-ddos.nft
ExecReload=/usr/sbin/nft -f /etc/nftables-ddos.nft
ExecStop=/usr/sbin/nft delete table inet ddos
[Install]
WantedBy=multi-user.target
Habilítala y comprueba su estado:
sudo systemctl daemon-reload
sudo systemctl enable --now nftables-ddos.service
systemctl status nftables-ddos.service --no-pager
● nftables-ddos.service - Reglas nftables contra DDoS
Loaded: loaded (/etc/systemd/system/nftables-ddos.service; enabled; preset: enabled)
Active: active (exited) since Thu 2026-09-25 13:02:11 UTC; 3s ago
Cuando cambies los umbrales, recarga con sudo systemctl reload nftables-ddos.
Paso 4: Limitar peticiones en Nginx
Los ataques HTTP usan conexiones TCP completas y peticiones válidas, así que el cortafuegos no los distingue del tráfico normal. Nginx puede limitar el número de peticiones por segundo y de conexiones simultáneas por IP, y cerrar las conexiones que envían datos demasiado despacio.
Crea un fichero en conf.d, que Ubuntu incluye dentro del bloque http:
sudo nano /etc/nginx/conf.d/ddos.conf
# Zonas compartidas: 10 MB guardan unas 160.000 IP
limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
limit_req_status 429;
limit_conn_status 429;
# Cortar clientes lentos (Slowloris)
client_header_timeout 10s;
client_body_timeout 10s;
send_timeout 10s;
keepalive_timeout 15s;
Después aplica los límites en el bloque server de tu sitio:
sudo nano /etc/nginx/sites-available/your_domain
server {
listen 80;
server_name your_domain;
# 10 peticiones/s por IP con ráfagas de hasta 20 sin retrasarlas
limit_req zone=req_per_ip burst=20 nodelay;
# Máximo 20 conexiones simultáneas por IP
limit_conn conn_per_ip 20;
# ... resto de la configuración del sitio
}
Puedes aplicar un límite más estricto solo en las rutas costosas, como el login o la búsqueda, poniendo limit_req dentro de su bloque location.
Comprueba la configuración y recarga:
sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Para verificar el límite, lanza desde otra máquina una ráfaga de peticiones con ab (paquete apache2-utils):
ab -n 300 -c 50 http://your_domain/
En el resultado, la línea Non-2xx responses indica cuántas peticiones han recibido un 429. En el servidor puedes contarlas en el log:
sudo awk '$9 == 429' /var/log/nginx/access.log | wc -l
262
Advertenciasi Nginx está detrás de un balanceador o un CDN,
$binary_remote_addres la IP del proxy y el límite se aplicaría a todos los visitantes a la vez. En ese caso configura antes el módulorealippara que Nginx use la IP real del cliente.
Paso 5: Identificar un ataque en curso
Cuando el servicio se degrada, estos comandos te dicen en pocos segundos qué está pasando. Compara los resultados con los que anotaste en el paso 1.
Conexiones a medio abrir y contadores de SYN cookies. Si SYN-RECV es de miles y TcpExtSyncookiesSent crece rápido, es un SYN flood:
ss -Htn state syn-recv | wc -l
nstat -z TcpExtSyncookiesSent TcpExtListenDrops
IP con más conexiones establecidas al puerto 443:
ss -Htn state established '( sport = :443 )' | awk '{sub(/:[0-9]+$/, "", $4); print $4}' | sort | uniq -c | sort -rn | head
412 198.51.100.23
38 192.0.2.77
3 203.0.113.8
IP que envían más paquetes SYN, capturando 2.000 paquetes. Sustituye eth0 por tu interfaz:
sudo tcpdump -nn -i eth0 -c 2000 'tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0' 2>/dev/null | awk '{print $3}' | cut -d. -f1-4 | sort | uniq -c | sort -rn | head
Paquetes descartados por cada regla de la tabla ddos y direcciones que están superando el límite ahora mismo:
sudo nft list chain inet ddos input | grep counter
sudo nft list set inet ddos syn_rate_v4
Las peticiones más frecuentes en Nginx durante los últimos minutos, útiles para detectar un HTTP flood contra una URL concreta:
sudo tail -n 20000 /var/log/nginx/access.log | awk '{print $1, $7}' | sort | uniq -c | sort -rn | head
Si identificas unas pocas IP responsables, bloquéalas de forma temporal con UFW (sudo ufw deny from 198.51.100.23) o con Fail2Ban. Si el tráfico procede de miles de direcciones o el enlace está saturado, el bloqueo tiene que hacerse aguas arriba: contacta con el soporte de tu proveedor con las IP de destino afectadas, la hora de inicio y el tipo de tráfico que ves.
Solución de problemas
- Usuarios legítimos reciben errores 429: los umbrales de
limit_reqson demasiado bajos para tu tráfico. Suberateoburst, o aplica el límite solo en las rutas costosas. - Las conexiones desde una oficina o un proxy fallan: muchos usuarios comparten IP y superan el límite
ct countde nftables o ellimit_connde Nginx. Aumenta los umbrales o añade una reglaip saddr 192.0.2.0/24 acceptal principio de la cadenainputde la tabladdospara esa red. nft -cdaError: Could not process rule: suele indicar que falta un módulo del kernel o una errata en la regla. Comprueba el número de línea del error y que el kernel es el de Ubuntu 24.04 (uname -r).- Te has quedado sin acceso SSH: entra por la consola de emergencia del proveedor y ejecuta
sudo systemctl stop nftables-ddos, que borra la tabla.
Conclusión
Tu servidor responde ahora mejor ante SYN floods gracias a las SYN cookies y a una cola de conexiones mayor, descarta en nftables a las IP que abren demasiadas conexiones y limita en Nginx las peticiones por cliente. También sabes cómo medir el estado normal y reconocer un ataque en curso. Como siguientes pasos, puedes usar Fail2Ban para bloquear de forma automática las IP que acumulan respuestas 429, poner un CDN delante de tus sitios web para absorber ataques de capa 7 y monitorizar los contadores del paso 5 con tu sistema de métricas para recibir alertas.
