Debian 13: nf_conntrack tabla llena — por qué fallan las conexiones y cómo ajustar de forma segura

¿Te fue útil?

Si tu host Debian 13 de repente empieza a “perder internet” mientras la CPU está bien y los discos inactivos, revisa el registro del kernel. Cuando ves nf_conntrack: table full, dropping packet, no estás ante una advertencia vaga. Estás ante un límite de capacidad duro en el subsistema de seguimiento de conexiones del kernel, y los nuevos flujos están pagando el precio.

Este es uno de esos modos de fallo que hace que la gente inteligente diga cosas cuestionables como “pero TCP reintentará”. Claro. Lo hace. Mientras tu balanceador de carga se vuelve loco, tus usuarios refrescan, tus sondas se ponen en rojo y todos aprenden que la pila de red es un recurso finito.

Qué es conntrack (y por qué importa en Debian 13)

nf_conntrack es el sistema de seguimiento de conexiones de Linux. Mantiene estado sobre los flujos de red para que Netfilter pueda realizar firewalling con estado y NAT. Ese “estado” se almacena en una tabla del kernel: entradas para flujos como TCP, UDP y algunas conversaciones ICMP. No es opcional si haces cosas comunes como:

  • Reglas de firewall con estado (ct state established,related / -m conntrack --ctstate)
  • NAT / masquerade (típico en contenedores, nodos de Kubernetes, routers domésticos y “hacks” corporativos temporales que se vuelven permanentes)
  • Balanceadores de carga o proxies inversos en un host que también hace NAT para otra cosa
  • Cualquier cosa que dependa de rastrear respuestas (helpers de FTP, cierto comportamiento SIP y otras alegrías del pasado)

Cuando la tabla se llena, el kernel no puede asignar una nueva entrada para un nuevo flujo. Así que descarta paquetes que crearían nuevo estado. Los flujos existentes pueden seguir funcionando a trompicones, pero el comportamiento visible por el usuario son fallos “aleatorios”: algunas conexiones funcionan, otras se quedan colgadas, retransmiten o agotan el tiempo.

Datos interesantes y contexto (porque este lío tiene historia)

  1. Conntrack surgió en la era Netfilter a principios de los 2000 para permitir firewalling con estado y NAT en Linux sin aparatos externos.
  2. Históricamente, muchos incidentes de “mi firewall está bien” eran en realidad agotamiento de conntrack—porque las reglas de filtrado eran correctas, pero la tabla de estado no era lo bastante grande.
  3. En pilas modernas de contenedores, conntrack se convirtió en un recurso compartido: un solo nodo de Kubernetes puede rastrear tráfico para muchos pods y servicios, por lo que las ráfagas golpean más fuerte.
  4. El dimensionamiento por defecto de conntrack es intencionalmente conservador para evitar grandes reservas de memoria en máquinas pequeñas. Bueno para portátiles. Malo para gateways ocupados.
  5. NAT consume entradas de conntrack agresivamente, porque cada traducción debe ser rastreada; añade conexiones de corta duración y la rotación se dispara.
  6. Las “conexiones” UDP son ficticias pero aún se rastrean, y sus timeouts pueden mantener entradas mucho tiempo después de que la aplicación dejara de importarle.
  7. nf_conntrack_buckets (buckets de la tabla hash) afecta al rendimiento de búsqueda; una tabla grande con demasiado pocos buckets es como una guía telefónica con una sola pestaña de letras.
  8. Las pruebas de carga a menudo no detectan la presión de conntrack porque se centran en el rendimiento de la aplicación, no en la rotación de flujos y los timeouts.
  9. Algunos incidentes atribuidos a “DDoS” fueron en realidad tráfico normal más un cambio de timeout que mantuvo las entradas vivas más tiempo.

Una regla operativa: los problemas de conntrack rara vez aparecen en períodos tranquilos. Aparecen durante despliegues, failovers, crowds repentinos o cuando alguien cambia timeouts sin avisar. Por eso debes tratar conntrack como planificación de capacidad, no como una perilla de emergencia.

Por qué fallan las conexiones cuando la tabla está llena

Cuando llega un paquete que requiere estado de conntrack (nuevo SYN TCP, nueva “sesión” UDP, una respuesta que necesita mapeo NAT), el kernel intenta asignar una entrada conntrack. Si no puede, obtienes un mensaje en el log y un paquete descartado. Las nuevas conexiones fallan de formas que parecen:

  • SYNs TCP que se retransmiten hasta que el cliente se rinde (parece pérdida de paquetes)
  • Time outs de DNS (consultas UDP descartadas, se reintenta, latencia aumentada)
  • Llamadas HTTP salientes colgándose (especialmente desde nodos NAT muy ocupados)
  • Fallos intermitentes en descubrimiento de servicios en Kubernetes

Los flujos TCP establecidos existentes a menudo continúan porque sus entradas conntrack ya existen. Esto crea el síntoma clásico de “la mitad del mundo funciona”: tu sesión SSH permanece, pero los nuevos inicios de sesión fallan. El chat del incidente se llena de superstición.

Broma #1: La agotación de conntrack es la única vez en que la red “stateful” se vuelve “stateless” a propósito, y a nadie le agrada esa pureza.

El verdadero cuello de botella: entradas, no ancho de banda

La capacidad de conntrack se mide en número de flujos rastreados, no en bits por segundo. Un host puede tener mucho ancho de banda y aun así fallar porque se quedó sin entradas debido a:

  • Muchas conexiones de corta duración (microservicios con clientes HTTP muy chatos)
  • Alto fan-out (un servicio llamando a docenas de otros por petición)
  • Cargas UDP (DNS, QUIC, telemetría) con timeouts demasiado largos
  • Gateways NAT para muchos clientes (las conexiones de cada cliente se acumulan)
  • Tráfico de ataque, escaneos o ruido de bots (incluso cuando hay limitación de tasa en otros lugares)

Una cita, porque la operación también tiene filósofos

idea parafraseada — “La confiabilidad viene de diseñar para el fallo, no de pretender que no sucederá.” Atribuido a: John Allspaw (reliability/operations).

Guion de diagnóstico rápido

Este es el orden que encuentra el cuello de botella rápido, sin perderse en 40 contadores y un agujero de conejo de “quizá sea DNS”.

  1. Primero: confirma que es agotamiento de conntrack, no solo pérdida de paquetes.

    • Revisa los registros del kernel por “table full”.
    • Comprueba el uso actual frente al máximo.
  2. Segundo: identifica qué está creando entradas.

    • Cuenta las entradas de conntrack por protocolo/estado.
    • Busca grandes volúmenes UDP, o montones de SYN_SENT/SYN_RECV.
    • Verifica si el host está haciendo NAT (contenedores, Kubernetes, VPN, masquerade clásico).
  3. Tercero: decide si escalar el máximo, acortar timeouts o dejar de rastrear.

    • Si estás rutinariamente cerca del máximo: aumenta capacidad y buckets, luego valida el impacto en memoria.
    • Si solo tienes picos: reduce timeouts para el culpable y/o arregla fan-out o tormentas de reintentos.
    • Si rastreas tráfico que no necesitas: deshabilita el seguimiento para esos flujos (con cuidado).

Todo lo demás—perfilado de CPU, dashboards sofisticados, culpar al firewall—es para después de confirmar la presión en la tabla. Conntrack está lleno o no lo está.

Tareas prácticas: comandos, salidas, decisiones

Estas son tareas reales que puedes ejecutar en Debian 13. Cada una incluye qué significa la salida y la decisión que debes tomar. Ejecútalas como root o con los privilegios adecuados.

Task 1: Confirmar que el kernel está descartando por conntrack

cr0x@server:~$ sudo journalctl -k -g "nf_conntrack" -n 20 --no-pager
Dec 29 10:41:12 server kernel: nf_conntrack: table full, dropping packet
Dec 29 10:41:12 server kernel: nf_conntrack: table full, dropping packet
Dec 29 10:41:13 server kernel: nf_conntrack: table full, dropping packet

Significado: Esta es la prueba definitiva. El kernel está rechazando nuevas asignaciones conntrack y descartando paquetes.

Decisión: Deja de adivinar. Pasa a medir el uso e identificar qué está llenando la tabla.

Task 2: Ver uso actual frente al máximo configurado

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_count = 262144
net.netfilter.nf_conntrack_max = 262144

Significado: Estás en el techo. Count iguala al max, y los nuevos flujos serán descartados.

Decisión: Necesitas alivio: o bien aumentar el máximo (tras revisar memoria), reducir timeouts o reducir flujos rastreados.

Task 3: Ver consumo de memoria de conntrack (slab)

cr0x@server:~$ grep -E 'nf_conntrack|conntrack' /proc/slabinfo | head -n 3
nf_conntrack         262144 262144   320  256    8 : tunables    0    0    0 : slabdata   1024   1024      0
nf_conntrack_expect       0      0    96   42    1 : tunables    0    0    0 : slabdata      0      0      0

Significado: El slab nf_conntrack muestra cuántos objetos están asignados y su tamaño. Aquí, las entradas están completamente asignadas.

Decisión: Estima el impacto en memoria antes de aumentar el máximo. Si tienes poca memoria, reducir timeouts o reducir flujos puede ser más seguro.

Task 4: Si conntrack-tools está instalado, obtener un resumen rápido

cr0x@server:~$ sudo conntrack -S
cpu=0 found=1483921 invalid=121 insert=927314 insert_failed=8421 drop=8421 early_drop=0 error=0 search_restart=0

Significado: insert_failed y drop en aumento indican fallos de asignación—usualmente tabla llena, a veces presión en el hash.

Decisión: Si insert_failed está subiendo, estás descartando activamente nuevos flujos. Mitiga ahora; afina después.

Task 5: Contar entradas por protocolo (mapa de calor rápido)

cr0x@server:~$ sudo conntrack -L | awk '{print $1}' | sort | uniq -c | sort -nr | head
210944 tcp
50877 udp
323 icmp

Significado: Aquí domina TCP. Si UDP domina, debes mirar DNS/QUIC/telemetría y timeouts UDP.

Decisión: Elige tu siguiente investigación: distribución de estados TCP o rotación por timeouts UDP.

Task 6: Para TCP, encontrar qué estados están creciendo

cr0x@server:~$ sudo conntrack -L -p tcp | awk '{for (i=1;i<=NF;i++) if ($i ~ /^state=/) {split($i,a,"="); print a[2]}}' | sort | uniq -c | sort -nr | head
80421 ESTABLISHED
61211 TIME_WAIT
45433 SYN_SENT
12912 CLOSE_WAIT

Significado: Muchos SYN_SENT pueden indicar tormentas de conexiones salientes o que el upstream no responde. Muchos TIME_WAIT pueden reflejar conexiones de corta duración y rotación.

Decisión: Si SYN_SENT es alto, busca reintentos y conectividad upstream; si TIME_WAIT es alto, céntrate en reutilización de conexiones y keepalive.

Task 7: Identificar los principales destinos (detectar dependencias descontroladas)

cr0x@server:~$ sudo conntrack -L -p tcp | awk '{for(i=1;i<=NF;i++){if($i ~ /^dst=/){split($i,a,"="); print a[2]}}}' | sort | uniq -c | sort -nr | head
64221 10.10.8.21
33110 10.10.8.22
12044 192.0.2.50

Significado: Uno o dos destinos consumen una gran porción de la tabla. Suele ser una dependencia caída, una tormenta de reintentos o un punto de agregación NAT.

Decisión: Revisa logs/métricas de los clientes que hablan con esas IPs. Considera limitación de tasa o circuit breaking antes de tocar sysctls.

Task 8: Comprobar si hay NAT en uso (nftables)

cr0x@server:~$ sudo nft list ruleset | sed -n '/table ip nat/,/}/p'
table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    oifname "eth0" masquerade
  }
}

Significado: El host está haciendo masquerade NAT. Cada flujo traducido necesita seguimiento, y clientes con ráfagas pueden llenar la tabla rápidamente.

Decisión: Trata este host como un gateway. Dimensiona conntrack para el comportamiento agregado de clientes, no solo para un servicio.

Task 9: Comprobar el conteo de buckets y el máximo (dimensionado del hash)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_buckets net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_buckets = 65536
net.netfilter.nf_conntrack_max = 262144

Significado: Una guía habitual es nf_conntrack_max alrededor de 4× buckets (no es una ley, pero es un buen punto de partida). Aquí la proporción es exactamente 4.

Decisión: Si aumentas considerablemente el máximo, revisa también los buckets. De lo contrario pagarás CPU por cadenas largas en el hash bajo carga.

Task 10: Validar timeouts actualmente configurados (la palanca de capacidad oculta)

cr0x@server:~$ sudo sysctl net.netfilter.nf_conntrack_tcp_timeout_established net.netfilter.nf_conntrack_udp_timeout net.netfilter.nf_conntrack_udp_timeout_stream
net.netfilter.nf_conntrack_tcp_timeout_established = 432000
net.netfilter.nf_conntrack_udp_timeout = 30
net.netfilter.nf_conntrack_udp_timeout_stream = 180

Significado: Las conexiones TCP establecidas se mantienen 5 días aquí. No es “incorrecto”, pero en gateways NAT ocupados puede ser desastroso si los clientes churnean y no cierran limpiamente.

Decisión: Si ves muchas entradas establecidas que están obsoletas (cliente ausente), considera reducir el timeout de established—con cuidado y teniendo en cuenta conexiones de larga duración legítimas.

Task 11: Inspeccionar comportamiento por namespace (contenedores/Kubernetes)

cr0x@server:~$ sudo lsns -t net | head
        NS TYPE NPROCS   PID USER   NETNSID NSFS COMMAND
4026531993 net     245     1 root unassigned       /lib/systemd/systemd
4026532458 net      12  3112 root unassigned       /usr/bin/containerd

Significado: Existen múltiples namespaces de red, pero conntrack sigue siendo principalmente un recurso global del kernel (con cierta contabilidad por netns según la configuración). Los contenedores pueden causar rotación de flujos inesperada.

Decisión: Si esto es un nodo, incluye el tráfico de pods/servicios en el dimensionamiento. No dimensionar solo en base a “la aplicación en el host”.

Task 12: Medir la rotación de conexiones a nivel de socket (correlacionar con conntrack)

cr0x@server:~$ ss -s
Total: 10648 (kernel 0)
TCP:   7342 (estab 1249, closed 5621, orphaned 0, synrecv 18, timewait 5621/0), ports 0

Transport Total     IP        IPv6
RAW       0         0         0
UDP       211       198       13

Significado: timewait es enorme también a nivel de sockets, coincidiendo con la rotación en conntrack. A menudo es un problema de comportamiento de la aplicación (sin keepalive, sin pooling) más que un problema del kernel.

Decisión: Si puedes arreglar la rotación en la app/proxy, hazlo. El ajuste del kernel no sustituye una reutilización sensata de conexiones.

Task 13: Mitigación rápida—aumentar el máximo inmediatamente (temporal)

cr0x@server:~$ sudo sysctl -w net.netfilter.nf_conntrack_max=524288
net.netfilter.nf_conntrack_max = 524288

Significado: El techo es ahora más alto. Los descartes deberían cesar si la única causa era el agotamiento.

Decisión: Trata esto como un torniquete. Haz seguimiento con dimensionado de buckets, revisión de memoria y análisis de causa raíz de por qué aumentaron las entradas.

Task 14: Hacer el cambio persistente (sysctl.d de Debian)

cr0x@server:~$ sudo sh -c 'cat > /etc/sysctl.d/99-conntrack-tuning.conf <
cr0x@server:~$ sudo sysctl --system | tail -n 5
* Applying /etc/sysctl.d/99-conntrack-tuning.conf ...
net.netfilter.nf_conntrack_max = 524288

Significado: La configuración sobrevivirá al reinicio y se aplica ahora.

Decisión: Persiste solo después de confirmar margen de memoria y de que no estás enmascarando una tormenta de tráfico.

Task 15: Ajustar buckets de hash de forma segura (opción del módulo en arranque)

cr0x@server:~$ sudo sh -c 'cat > /etc/modprobe.d/nf_conntrack.conf <
cr0x@server:~$ sudo update-initramfs -u
update-initramfs: Generating /boot/initrd.img-6.12.0-amd64

Significado: hashsize se establece en el momento de carga del módulo; en muchos sistemas es efectivamente una decisión en tiempo de arranque. Actualizar initramfs hace que se aplique temprano.

Decisión: Planifica una ventana de reinicio si necesitas cambiar buckets. No finjas que puedes “simplemente sysctl” esto en medio de un incidente.

Task 16: Detectar patrones obvios de escaneo/ataque (SYNs, invalids)

cr0x@server:~$ sudo conntrack -S | sed -n '1p'
cpu=0 found=1483921 invalid=121 insert=927314 insert_failed=8421 drop=8421 early_drop=0 error=0 search_restart=0

Significado: Si invalid empieza a dispararse junto con descartes, puedes estar rastreando basura: paquetes malformados, enrutamiento asimétrico o un DDoS parcial.

Decisión: Considera filtrado ascendente y limitación de tasa local; ampliar conntrack solo le da a los atacantes un juguete más grande.

Estrategia de dimensionamiento que no te perjudicará después

Hay tres perillas a las que la gente recurre:

  • nf_conntrack_max: número máximo de entradas rastreadas.
  • nf_conntrack_buckets (o la opción de módulo hashsize): buckets de la tabla hash para búsquedas.
  • Timeouts: cuánto tiempo permanecen las entradas, especialmente para UDP y estados TCP medio abiertos.

La mentalidad de ajuste seguro es: capacidad (max), eficiencia (buckets) y retención (timeouts). Puedes “solucionar” una tabla llena aumentando el max, pero podrías estar simplemente almacenando basura más tiempo, gastando más memoria y haciendo búsquedas más lentas si ignoras los buckets.

Paso 1: Decide qué tipo de host es

  • Servidor puro (sin NAT, estado de firewall mínimo): conntrack puede importar poco. Si está lleno, algo raro ocurre (como una regla NAT inesperada, un sidecar proxy o redes de contenedores locales).
  • Nodo Gateway/NAT: conntrack es central. Dimensiona según clientes agregados y su comportamiento. Aquí no conviene ser tacaño.
  • Nodo Kubernetes: es un gateway disfrazado. Espera picos durante rollouts, autoscaling y drenados de nodos.

Paso 2: Estima el número de entradas requerido (cálculo práctico, no fantasía)

No sobre-modeles. Usa picos observados y añade margen:

  • Mide nf_conntrack_count durante periodos ocupados normales.
  • Mídelo durante despliegues/failovers (la línea base del “mal día”).
  • Configura nf_conntrack_max al menos 2× del percentil 99 de pico para gateways de producción, a veces 3× si tienes tormentas de reintentos.

Si no puedes medir picos (entorno nuevo), empieza conservadoramente alto para gateways y luego valida memoria. El costo de ponerlo muy bajo son outages. El costo de ponerlo muy alto es uso de memoria y posible sobrecarga de búsquedas (que los buckets pueden mitigar).

Paso 3: Valida margen de memoria

Las entradas conntrack viven en memoria del kernel (slab). Aumentar nf_conntrack_max incrementa el uso potencial de memoria del kernel. El tamaño por entrada varía según kernel y características, así que trátalo como “cientos de bytes” y luego confirma con slabinfo.

En una máquina que ya coquetea con OOM, subir conntrack es como comprar un almacén más grande mudándote a tu cocina.

Timeouts: el multiplicador silencioso

Los timeouts determinan cuánto tiempo permanecen las entradas. Esto importa porque muchos workloads crean flujos más rápido de lo que crees, y un pequeño cambio en el tiempo de retención puede duplicar o triplicar el recuento de entradas en estado estacionario.

Qué timeouts suelen causar problemas

  • Timeout UDP demasiado alto: común cuando se ajusta “por si acaso”. Mantiene flujos UDP muertos y devora la tabla.
  • Timeout TCP established muy alto en gateways NAT: puede retener estado de clientes que desaparecieron (clientes móviles, Wi‑Fi inestable, apps que se bloquean).
  • Timeouts de TCP medio abierto: si estás bajo escaneo o patrones de SYN flood, las entradas medio abiertas pueden dominar.

Broma #2: Aumentar timeouts “para estar seguros” es como guardar todas las invitaciones de reunión para siempre porque quizás algún día asistas retroactivamente.

Un enfoque sensato para los timeouts

No pongas timeouts al azar desde posts de blog. En su lugar:

  1. Identifica el protocolo/estado que domina conntrack.
  2. Confirma que se correlaciona con valor real para el usuario (sesiones TCP de larga duración) o con rotación/ruido (picos DNS, reintentos, escaneos).
  3. Ajusta el conjunto más pequeño de timeouts que reduzca la retención de basura sin romper flujos legítimos de larga duración.

Para muchos entornos, la mejor solución no es ajustar timeouts sino detener la rotación de conexiones: keepalive HTTP, pooling de conexiones y presupuestos de reintentos sensatos. Conntrack debe rastrear flujos reales, no el subproducto de una manada estruendosa.

Buckets de hash y rendimiento: nf_conntrack_buckets

Incluso si nunca alcanzas el máximo, una tabla hash mal dimensionada puede perjudicar. Las búsquedas conntrack ocurren en el camino caliente del procesamiento de paquetes; cadenas largas en el hash significan CPU extra por paquete, lo que se traduce en latencia y tiempo en softirq.

Lo que puedes controlar

  • nf_conntrack_buckets es a menudo de solo lectura en tiempo de ejecución y se establece en la init del módulo.
  • La opción del módulo hashsize= es la forma común de fijarlo de manera persistente.

Guía que funciona más veces de las que falla

Una proporción operativa común es:

  • nf_conntrack_max ≈ 4 × nf_conntrack_buckets

Esto mantiene las longitudes medias de las cadenas razonables. No es magia. Es un punto de partida. Si tu tráfico tiene distribuciones raras de tuplas o estás bajo ataque, aún necesitas observación.

Específicos de Debian 13: sysctl, systemd y persistencia

Debian 13 se comporta como Debian moderno: parámetros del kernel vía sysctl, configuraciones persistentes bajo /etc/sysctl.d/, opciones de módulo bajo /etc/modprobe.d/ y arranque temprano gestionado por initramfs.

Qué hacer en Debian 13

  • Pon los sysctls de conntrack en un fichero dedicado como /etc/sysctl.d/99-conntrack-tuning.conf.
  • Para hashsize, añade options nf_conntrack hashsize=... en /etc/modprobe.d/nf_conntrack.conf y ejecuta update-initramfs -u.
  • Reinicia durante una ventana para aplicar cambios de buckets de forma fiable.

Además: si tu host usa contenedores, comprueba que tu “firewall simple” no esté secretamente haciendo NAT para redes bridge. Muchos lo hacen. A conntrack no le importa que fuera “solo para desarrollo”.

Tres mini-historias corporativas reales

Mini-historia 1: El incidente causado por una suposición equivocada

La compañía tenía una capa edge basada en Debian: unos pocos hosts ejecutando un reverse proxy, más algunas reglas de firewall por comodidad. Sin NAT, sin VPN, nada sofisticado. Estaban convencidos de que conntrack era irrelevante porque “no somos un router”.

Llegó entonces una nueva política de seguridad: una política por defecto stateful-deny con amplias excepciones “established/related”. Era correcta desde el punto de vista de las reglas y también bloqueó basura. Pero también convirtió conntrack en algo obligatorio para prácticamente todos los flujos entrantes.

Unas semanas después, una campaña de marketing golpeó. La rotación de conexiones aumentó—muchos handshakes TLS cortos debido a clientes agresivos y un keepalive mal configurado. En el pico, el log del kernel empezó a gritar sobre conntrack table full. El proxy parecía “saludable” en CPU, y el equipo de red culpó a perdida de paquetes upstream. Mientras tanto, los nuevos usuarios no podían conectar, pero las sesiones existentes se mantenían, lo que hacía que todo pareciera un outage parcial.

La suposición equivocada no era “conntrack es malo”. La suposición equivocada era “conntrack es solo para NAT”. En un firewall stateful, conntrack es el producto.

Solución: aumentaron nf_conntrack_max con un factor de margen sensato, ajustaron el hashsize para que coincidiera y—más importante—arreglaron keepalive y la reutilización de conexiones en el proxy. Después de eso, el recuento de conntrack se estabilizó en una fracción del máximo y los fallos “aleatorios” desaparecieron.

Mini-historia 2: La optimización que salió mal

Un equipo de plataforma quiso menor latencia para telemetría basada en UDP y decidió “evitar descartes” aumentando los timeouts de conntrack UDP. El cambio se desplegó en un conjunto de nodos Debian 13 que actuaban como gateways NAT para sucursales y cargas de contenedores. Nadie hizo una revisión de capacidad porque los nodos tenían mucha RAM y “es solo un timeout”.

Al principio pareció bien. Luego un despliegue rutinario causó un pequeño brownout en un colector downstream. Los clientes reintentaron. Los flujos UDP churnearon. El timeout más largo mantuvo las entradas más tiempo, así que la tabla se llenó con conversaciones muertas que nunca recibirían respuesta. Los flujos nuevos empezaron a fallar, lo que provocó más reintentos y más flujos. Bucle de realimentación positivo clásico. El gráfico del incidente parecía una curva exponencial hecha por alguien que te odia.

Intentaron aumentar nf_conntrack_max durante el incidente, lo que ayudó brevemente pero incrementó CPU debido a un mal dimensionamiento de buckets y cadenas de búsqueda más largas. El resultado neto fue “está menos roto pero más lento”, que no es una victoria para anunciar.

El fallo no fue la idea de ajustar. Fue ajustar sin medir la rotación y sin entender que el tiempo de retención fija la ocupación en estado estacionario. La solución correcta fue: revertir el aumento del timeout UDP, reducir reintentos en el cliente y añadir control de presión en la canalización. Después subieron el max moderadamente—con buckets a juego—basándose en picos medidos, no en corazonadas.

Mini-historia 3: La práctica aburrida pero correcta que salvó el día

Otra organización tenía una política: cada host con rol de gateway tenía un “dashboard de capacidad del kernel” con tres líneas aburridas: nf_conntrack_count, nf_conntrack_max y uso de slab para nf_conntrack. Sin matemáticas SLO sofisticadas. Solo lo básico y una alerta al 70% sostenido.

Durante una caída de proveedor, sus servicios empezaron a reintentar llamadas HTTPS salientes más agresivamente. Es un problema de comportamiento del cliente, pero se manifiesta como rotación de flujos en el nodo NAT. Su alerta de conntrack saltó antes de que los clientes notaran nada. El on-call vio el conteo subir, confirmó que dominaba SYN_SENT y limitó la clase de trabajos que reintentaban. También aumentaron temporalmente el max porque tenían margen de memoria pre-calculado y un rango de “aumento seguro” documentado.

Sin heroísmos, sin sala de crisis. El ticket del incidente fue casi embarazoso: “Evitada la agotación de conntrack limitando reintentos; ajustado el max dentro del rango planeado.” Ese es el tipo de aburrimiento que quieres en producción.

La práctica salvadora no era una bandera del kernel secreta. Era observabilidad de capacidad, límites de ajuste aprobados y una cultura que trata los reintentos como tráfico de producción, no como terapia gratuita para dependencias que fallan.

Errores comunes: síntomas → causa raíz → solución

1) “Algunas conexiones funcionan, las nuevas fallan” → conntrack lleno → aumentar max y detener la rotación

Síntomas: SSH existente se mantiene, pero nuevos SSH/HTTP cuelgan. Logs del kernel muestran descartes.

Causa raíz: Tabla en nf_conntrack_max. No se pueden rastrear nuevos flujos.

Solución: Inmediato: aumentar nf_conntrack_max (temporal). Luego encontrar la fuente de rotación y arreglar keepalive/reintentos; ajustar buckets si se mantiene el máximo más alto.

2) “Aumentamos el max pero sigue lento” → buckets demasiado pequeños → establecer hashsize y reiniciar

Síntomas: Los descartes paran, pero la latencia sube y la CPU en softirq aumenta.

Causa raíz: Aumentaste el max sin aumentar los buckets, provocando cadenas largas en el hash y búsquedas más lentas.

Solución: Establece la opción del módulo hashsize (o ajusta buckets si es soportado) para que el número de buckets escale con el max, luego reinicia en una ventana.

3) “Solo pasa durante despliegues” → tormentas de reintentos + flujos de corta duración → arreglar comportamiento del cliente

Síntomas: Recuento de conntrack picos durante rollouts o failovers.

Causa raíz: Rotación de conexiones por reintentos, sondas de salud o flapping en descubrimiento de servicios.

Solución: Reducir reintentos, añadir jitter, habilitar pooling de conexiones y suavizar sondas. Luego dimensionar conntrack para picos realistas.

4) “UDP se está comiendo todo” → timeouts UDP demasiado altos o tráfico ruidoso → ajustar UDP y filtrar ruido

Síntomas: Conntrack dominado por entradas UDP; fallos DNS frecuentes.

Causa raíz: Alta rotación UDP con timeouts que retienen entradas, además de posible escaneo o ruido de telemetría.

Solución: Mantén timeouts UDP sensatos, reduce fan-out de telemetría y filtra basura obvia en el borde cuando sea apropiado.

5) “Empezó después de habilitar Docker/Kubernetes” → NAT oculto y reglas iptables → tratar nodo como gateway

Síntomas: Un “servidor normal” de repente alcanza límites de conntrack tras instalar runtime de contenedores.

Causa raíz: La red de contenedores añade NAT/forwarding y reglas stateful, aumentando flujos rastreados.

Solución: Inventaría reglas NAT, luego dimensiona conntrack acorde. Considera gateways de egress dedicados para cargas pesadas.

6) “conntrack invalid es enorme” → enrutamiento asimétrico o comportamiento de offload extraño → arreglar rutas/caminos

Síntomas: Altos recuentos de invalid, descartes extraños, a veces solo una dirección falla.

Causa raíz: El tráfico vuelve por un camino distinto al que salió (enrutamiento asimétrico), o los paquetes evitan el seguimiento debido a topología/offload.

Solución: Arregla la simetría de enrutamiento o ajusta el diseño para que el seguimiento con estado esté en un camino consistente. Evita soluciones parche que solo amplíen conntrack.

Listas de comprobación / plan paso a paso

Checklist: durante un incidente activo (15–30 minutos)

  1. Confirma que los logs muestran nf_conntrack: table full y que count iguala al max.
  2. Obtén un desglose rápido por protocolo/estado (TCP vs UDP; SYN_SENT vs ESTABLISHED).
  3. Comprueba si este host hace NAT (cadenas NAT en nftables/iptables).
  4. Aplica un aumento temporal del max si tienes margen de memoria.
  5. Limita la mayor fuente de rotación (trabajos que reintentan, clientes abusivos, sondas malfuncionantes).
  6. Captura un paquete de evidencias breve para después: valores sysctl, resumen conntrack, destinos principales.

Checklist: después de estabilizar (mismo día)

  1. Decide el rol del host: gateway/NAT, nodo k8s o servidor puro.
  2. Fija persistente nf_conntrack_max basado en picos medidos más margen.
  3. Planifica ajuste de buckets/hashsize para coincidir con el nuevo max; programa reinicio.
  4. Revisa timeouts; revierte aumentos “por si acaso” que incrementen la retención.
  5. Arregla la rotación en la fuente: keepalive, pooling, presupuestos de reintentos, backoff/jitter.
  6. Añade alertas al 70–80% de uso sostenido y un panel con count/max/slab.

Checklist: endurecimiento a largo plazo (este trimestre)

  1. Prueba de capacidad la rotación de conexiones, no solo el throughput. Incluye escenarios de despliegue/failover.
  2. Documenta un rango de emergencia seguro para nf_conntrack_max basado en margen de memoria.
  3. Si ejecutas Kubernetes, trata conntrack como una dependencia SLO a nivel de nodo; dimensiona por tipo de nodo.
  4. Considera mover NAT/egress pesado a gateways dedicados para aislar el radio de impacto.
  5. Audita reglas de firewall en busca de seguimiento innecesario (con cuidado; no rompas seguridad stateful).

Preguntas frecuentes

1) ¿“nf_conntrack table full” significa que mis reglas de firewall están mal?

No. Normalmente significa que tus reglas funcionan como se espera (stateful), pero la tabla de estado es demasiado pequeña para el patrón de tráfico o los timeouts.

2) ¿Puedo simplemente poner nf_conntrack_max a un valor enorme y olvidarme?

Puedes, pero pagarás memoria del kernel y posiblemente CPU (especialmente si los buckets no se escalan). Además, una tabla más grande puede permitir que escaneos/ataques consuman más estado antes de notarlo.

3) ¿Cómo sé si NAT es la razón?

Busca reglas de masquerade/SNAT en nftables o tablas NAT de iptables y confirma que el host está reenviando tráfico para otros clientes o contenedores. NAT casi siempre incrementa la presión de conntrack.

4) ¿Por qué los fallos de DNS aparecen primero?

DNS usa UDP, es de corta duración y sensible a la latencia. Cuando se descartan nuevos flujos UDP, los clientes reintentan y amplifican la rotación. A menudo es la primera grieta visible.

5) ¿Cuál es la diferencia entre nf_conntrack_buckets y nf_conntrack_max?

nf_conntrack_max es capacidad (cuántas entradas). Los buckets determinan el rendimiento de búsqueda. Un max grande con pocos buckets incrementa el coste de CPU por paquete.

6) ¿Las conexiones TCP establecidas se caen cuando la tabla está llena?

Normalmente no de forma inmediata, porque ya tienen entradas. El dolor es sobre todo para conexiones nuevas y tráfico que necesita nuevos mapeos NAT.

7) ¿Puedo deshabilitar conntrack para algún tráfico?

A veces sí (para flujos específicos donde el seguimiento no es necesario). Pero es fácil romper NAT, expectativas de firewall stateful y tráfico de respuesta. Hazlo solo cuando entiendas la ruta y tengas pruebas.

8) ¿Es esto un problema de iptables o de nftables?

Ninguno y ambos. iptables/nftables son frontends de reglas; conntrack es un subsistema del kernel del que dependen. Migrar a nftables no eliminará mágicamente la presión de conntrack.

9) ¿Qué pasa si conntrack_count no está cerca del máximo pero aún veo descartes?

Mira conntrack -S para insert failures, comprueba el dimensionamiento de buckets y considera si tienes ráfagas que causan agotamiento transitorio o presión de memoria. También verifica que los logs sean actuales y no históricos.

10) ¿Aumentar nf_conntrack_max requiere reinicio?

No, puedes cambiar nf_conntrack_max en tiempo de ejecución vía sysctl. Los cambios de buckets/hashsize típicamente requieren recarga del módulo o reinicio para aplicarse de forma segura.

Próximos pasos que realmente puedes hacer esta semana

Haz estas tareas en orden, y conseguirás convertir conntrack de una sorpresa de outage en una métrica de capacidad gestionada:

  1. Añadir visibilidad: grafica nf_conntrack_count, nf_conntrack_max y uso de slab de conntrack. Alerta al 70–80% sostenido.
  2. Clasifica tus nodos: todo lo que haga NAT o ejecute redes de contenedores es un gateway. Trátalo como tal.
  3. Elige un max sensato: basarlo en picos observados con margen; persístelo vía /etc/sysctl.d/.
  4. Hace coincidir buckets con el max: establece la opción del módulo hashsize, actualiza initramfs y programa un reinicio.
  5. Arregla la rotación: reduce reintentos, añade jitter, habilita keepalive/pooling y deja de convertir fallos transitorios en inundaciones de tráfico.
  6. Revisa timeouts: revierte aumentos “por si acaso”; ajusta solo con evidencia y considerando conexiones de larga duración.

Conntrack es una tabla compartida del kernel. Trátala como el espacio en disco de un servidor de base de datos: finita, medible y capaz de arruinarte la tarde si la ignoras.

← Anterior
Las mentiras de ZFS “No hay espacio”: solucionar el espacio sin borrados aleatorios
Siguiente →
Proxmox systemd “Dependency failed”: Detectar el servicio que rompe el arranque

Deja un comentario