El kernel de Linux trae valores de red pensados para funcionar razonablemente en cualquier equipo, desde un portátil hasta un servidor. En un servidor con mucho tráfico o que transfiere datos a clientes lejanos, algunos de esos valores limitan el rendimiento: búferes TCP pequeños para enlaces con mucha latencia, un control de congestión que reacciona mal a la pérdida de paquetes o colas que se desbordan en picos de conexiones. En esta guía medirás el punto de partida, aplicarás un conjunto de ajustes justificados con sysctl en Ubuntu 24.04, activarás BBR y comprobarás la mejora con iperf3.

Requisitos previos

Necesitas:

  • Un servidor con Ubuntu 24.04 LTS (kernel 6.8 o posterior), por ejemplo un VPS de CubePath, con un usuario no root con privilegios sudo.
  • Un segundo equipo, idealmente en otra región o con una latencia de varias decenas de milisegundos, para medir el rendimiento.
  • Conocer el tipo de carga del servidor (servidor web, proxy, transferencia de archivos, base de datos). No hay un ajuste universal y no todos los cambios convienen a todas las cargas.

Paso 1: Guardar la configuración actual y medir

Guarda una copia de todos los valores actuales del kernel para poder compararlos o volver atrás:

sudo sysctl -a 2>/dev/null > ~/sysctl-antes-$(date +%F).txt

Revisa los valores que vas a tocar en esta guía:

sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc \
    net.core.rmem_max net.core.wmem_max net.ipv4.tcp_rmem net.ipv4.tcp_wmem \
    net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.ipv4.ip_local_port_range
net.ipv4.tcp_congestion_control = cubic
net.core.default_qdisc = fq_codel
net.core.rmem_max = 212992
net.core.wmem_max = 212992
net.ipv4.tcp_rmem = 4096	131072	6291456
net.ipv4.tcp_wmem = 4096	16384	4194304
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.ip_local_port_range = 32768	60999

Instala iperf3 en ambos equipos para medir el caudal:

sudo apt update
sudo apt install iperf3

En el equipo remoto, abre el puerto y arranca el servidor de pruebas:

sudo ufw allow 5201/tcp
iperf3 -s

Desde tu servidor, mide la latencia y después el caudal con un solo flujo TCP, que es lo que más depende de los ajustes del kernel. Sustituye remote_ip por la IP del equipo remoto:

ping -c 10 remote_ip | tail -n 1
iperf3 -c remote_ip -t 30
iperf3 -c remote_ip -t 30 -R

La opción -R mide en sentido inverso (el remoto envía y tu servidor recibe). Anota la latencia media y los dos resultados:

rtt min/avg/max/mdev = 98.112/98.420/99.031/0.254 ms
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-30.00  sec  1.62 GBytes   464 Mbits/sec  1204            sender

Paso 2: Activar BBR como control de congestión

El algoritmo de control de congestión decide a qué ritmo envía datos TCP. CUBIC, el predeterminado, reduce el ritmo drásticamente ante cualquier pérdida de paquetes. BBR, desarrollado por Google, estima el ancho de banda y la latencia reales del camino y suele conseguir bastante más caudal en enlaces largos o con algo de pérdida, sin llenar las colas de los routers. Solo afecta al tráfico que envía tu servidor.

Comprueba qué algoritmos están disponibles:

sysctl net.ipv4.tcp_available_congestion_control

Si bbr no aparece en la lista, carga el módulo y haz que se cargue en cada arranque:

sudo modprobe tcp_bbr
echo "tcp_bbr" | sudo tee /etc/modules-load.d/bbr.conf

Crea un archivo propio en /etc/sysctl.d/, que se aplica al arrancar y no se sobrescribe con las actualizaciones del sistema:

sudo nano /etc/sysctl.d/90-network-tuning.conf

Añade:

# Congestion control: BBR with the fq packet scheduler for pacing
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

La disciplina de colas fq realiza el espaciado de paquetes (pacing) en el que se apoya BBR. Aplica los cambios:

sudo sysctl --system

Comprueba el algoritmo activo:

sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = bbr

El cambio de default_qdisc solo se aplica a las interfaces que se inicializan después, así que reinicia el servidor o ejecuta sudo tc qdisc replace dev eth0 root fq (cambiando eth0 por tu interfaz) para aplicarlo ya. Mientras corre una prueba de iperf3, puedes confirmar que la conexión usa BBR:

ss -ti dst remote_ip | grep -o 'bbr'

Paso 3: Dimensionar los búferes TCP

TCP solo puede tener en vuelo, sin confirmar, tantos datos como le permita el búfer. La cantidad necesaria para llenar un enlace es el producto ancho de banda por retardo (BDP): ancho de banda en bytes por segundo multiplicado por la latencia de ida y vuelta.

EnlaceLatencia (RTT)BDP
1 Gbit/s10 ms1,25 MB
1 Gbit/s100 ms12,5 MB
10 Gbit/s100 ms125 MB

El máximo por defecto de Ubuntu para recepción es 6 MB, que limita un único flujo a unos 480 Mbit/s con 100 ms de latencia, justo lo que muestra la medición del paso 1. Añade estas líneas a /etc/sysctl.d/90-network-tuning.conf:

# TCP buffers: min, default, max (bytes). The kernel autotunes between them.
net.ipv4.tcp_rmem = 4096 131072 33554432
net.ipv4.tcp_wmem = 4096 16384 33554432

# Upper limit for buffers requested explicitly by applications (SO_RCVBUF/SO_SNDBUF)
net.core.rmem_max = 33554432
net.core.wmem_max = 33554432

Con un máximo de 32 MB, un flujo puede llegar a unos 2,5 Gbit/s con 100 ms de latencia. Solo se sube el valor máximo: el kernel sigue asignando búferes pequeños a cada conexión y los hace crecer bajo demanda (autotuning), así que un servidor con miles de conexiones lentas no consume más memoria. Para enlaces de 10 Gbit/s o más con clientes lejanos, sube el máximo a 64 o 128 MB.

Aplica y comprueba:

sudo sysctl --system
sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096	131072	33554432

Paso 4: Ajustar colas y conexiones

Estos parámetros importan en servidores que aceptan o abren muchas conexiones por segundo, como un proxy inverso, un balanceador o una API muy concurrida. Añádelos al mismo archivo:

# Pending connections waiting for accept() per listening socket
net.core.somaxconn = 8192
# Half-open connections (SYN received) waiting for the handshake to finish
net.ipv4.tcp_max_syn_backlog = 8192

# Packets queued per CPU when the NIC delivers faster than the kernel processes
net.core.netdev_max_backlog = 16384

# Local ports for outbound connections (proxies to backends, clients to databases)
net.ipv4.ip_local_port_range = 10240 65535
# Reuse TIME_WAIT sockets for new outbound connections when safe
net.ipv4.tcp_tw_reuse = 1

# Do not shrink the congestion window after a connection has been idle
net.ipv4.tcp_slow_start_after_idle = 0
# Probe the path MTU when packets are silently dropped (broken PMTUD)
net.ipv4.tcp_mtu_probing = 1

Por qué cada uno:

  • somaxconn es el límite superior de la cola de conexiones pendientes. Una aplicación solo lo aprovecha si pide una cola grande en listen(): en Nginx, por ejemplo, con listen 443 ssl backlog=8192;.
  • ip_local_port_range y tcp_tw_reuse solo afectan a conexiones salientes. Un proxy que abre miles de conexiones por segundo hacia sus backends puede quedarse sin puertos libres por culpa de los sockets en TIME_WAIT. El valor por defecto de tcp_tw_reuse en kernels recientes es 2 (solo para loopback); 1 lo extiende a todo el tráfico, y es seguro porque se apoya en las marcas de tiempo TCP.
  • tcp_slow_start_after_idle = 0 evita que las conexiones keep-alive (HTTP/2, bases de datos) vuelvan a empezar despacio tras unos segundos de inactividad.

Aplica los cambios con sudo sysctl --system y comprueba cualquiera de ellos con sysctl nombre.del.parametro.

Paso 5: Ampliar la tabla de conntrack (solo si usas NAT o un cortafuegos con estado)

Si el servidor usa UFW, Docker o reglas de NAT, el kernel registra cada conexión en la tabla de seguimiento de conexiones (conntrack). Cuando se llena, descarta paquetes nuevos y en el registro del kernel aparece nf_conntrack: table full, dropping packet. Comprueba el uso actual y el máximo:

cat /proc/sys/net/netfilter/nf_conntrack_count /proc/sys/net/netfilter/nf_conntrack_max
1824
262144

Si /proc/sys/net/netfilter/ no existe, el módulo no está cargado y no necesitas este paso. Si el uso se acerca al máximo en horas punta, amplíalo. Cada entrada ocupa unos 300 bytes, así que un millón de entradas son unos 300 MB de RAM. Como el parámetro solo existe cuando el módulo nf_conntrack está cargado, crea un archivo aparte y asegúrate de que el módulo se carga al arrancar:

echo "nf_conntrack" | sudo tee /etc/modules-load.d/conntrack.conf
sudo nano /etc/sysctl.d/91-conntrack.conf
net.netfilter.nf_conntrack_max = 1048576
# Established connections idle for longer than 1 day are forgotten (default is 5 days)
net.netfilter.nf_conntrack_tcp_timeout_established = 86400

Aplica y comprueba:

sudo sysctl --system
sysctl net.netfilter.nf_conntrack_max

Paso 6: Medir de nuevo y comparar

Con todos los cambios aplicados, repite exactamente las mismas pruebas del paso 1 contra el mismo equipo remoto:

iperf3 -c remote_ip -t 30
iperf3 -c remote_ip -t 30 -R
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-30.00  sec  6.21 GBytes  1.78 Gbits/sec  312             sender

En enlaces con latencia alta, la mejora de un solo flujo suele ser grande; en una red local con 1 ms de latencia, apenas habrá diferencia porque los valores por defecto ya bastaban. Revisa también las retransmisiones globales del sistema:

nstat -az TcpRetransSegs TcpOutSegs

Una proporción de retransmisiones por encima del 1-2 % de los segmentos enviados indica un problema en la red, no en la configuración del kernel.

Por último, reinicia el servidor y comprueba que todos los valores se mantienen:

sudo reboot
sysctl net.ipv4.tcp_congestion_control net.core.default_qdisc net.ipv4.tcp_rmem

Revisar la tarjeta de red (opcional)

En servidores físicos con mucho tráfico, las colas de la propia tarjeta de red (ring buffers) también pueden desbordarse. Instala ethtool y consulta el tamaño actual y el máximo admitido:

sudo apt install ethtool
sudo ethtool -g eth0

Si los contadores de descartes crecen (ip -s link show eth0, columna dropped) y el tamaño actual está por debajo del máximo, amplíalo, por ejemplo con sudo ethtool -G eth0 rx 4096. Este cambio no es persistente; para mantenerlo tras reiniciar, configúralo en Netplan o en una unidad de systemd. En una VM con virtio-net muchos de estos parámetros no se pueden modificar, y no suele hacer falta.

Solución de problemas

sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack_max. El módulo nf_conntrack no está cargado. Cárgalo con sudo modprobe nf_conntrack o elimina ese archivo si no usas NAT ni un cortafuegos con estado.

El algoritmo vuelve a cubic tras reiniciar. El módulo tcp_bbr no se cargó antes de aplicar los sysctl. Comprueba que existe /etc/modules-load.d/bbr.conf y que otro archivo de /etc/sysctl.d/ con un número mayor no sobrescribe el valor: grep -r congestion /etc/sysctl.d/ /etc/sysctl.conf.

El caudal no mejora. El límite puede estar en otra parte: el plan de red del servidor, el equipo remoto (que también necesita búferes grandes para recibir), la CPU (un solo núcleo al 100 % durante la prueba) o el cifrado de la aplicación. Prueba con varios flujos (iperf3 -c remote_ip -P 4) para ver si el enlace da más de sí.

Quieres deshacer todos los cambios. Elimina los archivos que has creado y reinicia:

sudo rm /etc/sysctl.d/90-network-tuning.conf /etc/sysctl.d/91-conntrack.conf /etc/modules-load.d/bbr.conf /etc/modules-load.d/conntrack.conf
sudo reboot

Conclusión

Has activado BBR, dimensionado los búferes TCP según el BDP de tus enlaces, ampliado las colas de conexión y conntrack, y comprobado el efecto con mediciones antes y después. Mantén estos ajustes en un único archivo de /etc/sysctl.d/ versionado junto al resto de tu configuración. Como siguientes pasos puedes ajustar la directiva backlog de Nginx o HAProxy para aprovechar el nuevo somaxconn, vigilar las retransmisiones y el uso de conntrack desde tu sistema de monitorización, o aplicar los mismos ajustes a todos tus servidores con Ansible.