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 ataqueQué agotaDónde se mitiga
Volumétrico UDP, amplificación DNS/NTP/memcachedAncho de banda del enlaceEn la red del proveedor o un servicio de mitigación, antes de llegar al servidor
SYN floodCola de conexiones pendientes del kernelKernel (SYN cookies) y cortafuegos
Muchas conexiones desde pocas IPTabla de conexiones, workersCortafuegos (límites por IP)
HTTP flood, fuerza bruta en URLs costosasCPU de la aplicación y la base de datosServidor web (rate limiting), caché, CDN o WAF
Slowloris y conexiones lentasConexiones abiertas del servidor webTiempos 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

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

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_req son demasiado bajos para tu tráfico. Sube rate o burst, 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 count de nftables o el limit_conn de Nginx. Aumenta los umbrales o añade una regla ip saddr 192.0.2.0/24 accept al principio de la cadena input de la tabla ddos para esa red.
  • nft -c da Error: 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.