Los valores por defecto del kernel Linux funcionan bien para la mayoría de servidores, pero un servidor web con muchos clientes concurrentes puede toparse con colas de conexión que se desbordan, límites de descriptores de archivo o un control de congestión poco eficiente en enlaces con pérdidas. En este tutorial medirás primero el estado actual, aplicarás un conjunto pequeño y justificado de parámetros sysctl en Ubuntu 24.04, ajustarás Nginx para que aproveche los nuevos límites y comprobarás el resultado con contadores del kernel y una prueba de carga.

La idea no es copiar cientos de parámetros, sino cambiar solo los que resuelven un problema medible y dejar el resto en sus valores por defecto, que en el kernel 6.8 de Ubuntu 24.04 ya son razonables.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Nginx instalado y sirviendo un sitio (sudo apt install nginx). Los ajustes de kernel sirven igual para Apache, Caddy o HAProxy; solo cambia la sección de configuración de la aplicación.
  • Opcional, para el paso 5: una segunda máquina desde la que lanzar la prueba de carga. Lanzarla desde el propio servidor mide el servidor compitiendo consigo mismo.

Paso 1: Medir el estado actual

Antes de cambiar nada, anota los valores actuales y comprueba si hay síntomas reales. Consulta los parámetros que vas a tocar:

sysctl net.core.somaxconn net.ipv4.tcp_max_syn_backlog net.core.netdev_max_backlog net.ipv4.tcp_congestion_control net.ipv4.ip_local_port_range
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 1024
net.core.netdev_max_backlog = 1000
net.ipv4.tcp_congestion_control = cubic
net.ipv4.ip_local_port_range = 32768	60999

El valor de tcp_max_syn_backlog depende de la memoria del servidor, así que puede ser distinto en el tuyo.

Ahora revisa los dos contadores que indican que la cola de conexiones pendientes se ha llenado y el kernel ha descartado clientes. La herramienta nstat forma parte del paquete iproute2, instalado por defecto:

nstat -az TcpExtListenOverflows TcpExtListenDrops
#kernel
TcpExtListenOverflows           0                  0.0
TcpExtListenDrops               0                  0.0

Si estos contadores crecen durante los picos de tráfico, la cola de aceptación es demasiado pequeña. Mira también la cola de los sockets en escucha con ss. En un socket en estado LISTEN, Send-Q es el tamaño máximo de la cola (el backlog) y Recv-Q las conexiones que esperan a que la aplicación las acepte:

ss -ltn 'sport = :80'
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port
LISTEN  0       511           0.0.0.0:80         0.0.0.0:*

Ese 511 es el backlog que Nginx pide por defecto en Linux, aunque el kernel permita hasta 4096. Lo corregirás en el paso 4.

Por último, un resumen de los estados TCP te dirá si tienes miles de conexiones en TIME-WAIT:

ss -s

Paso 2: Crear el archivo de ajustes sysctl

Los cambios con sysctl -w se pierden al reiniciar. Guarda los ajustes en un archivo propio dentro de /etc/sysctl.d/, que se carga en cada arranque:

sudo nano /etc/sysctl.d/99-servidor-web.conf

Añade el siguiente contenido:

# Cola de conexiones
net.core.somaxconn = 8192
net.ipv4.tcp_max_syn_backlog = 8192
net.core.netdev_max_backlog = 16384

# Control de congestión BBR con la cola fq
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# Buffers TCP: tamaño máximo de 16 MB por socket
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 131072 16777216
net.ipv4.tcp_wmem = 4096 16384 16777216

# Conexiones keepalive y conexiones salientes
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30

Qué hace cada bloque:

  • Cola de conexiones: somaxconn es el máximo que el kernel acepta para la cola de conexiones establecidas de cada socket en escucha, y tcp_max_syn_backlog el de conexiones a medio abrir (SYN recibido). netdev_max_backlog es la cola de paquetes que llegan por la interfaz antes de que el kernel los procese. Subirlos solo sirve si la aplicación también pide un backlog mayor, como verás en el paso 4.
  • BBR y fq: BBR es un algoritmo de control de congestión que estima el ancho de banda y la latencia del camino en lugar de reaccionar solo a la pérdida de paquetes. Suele mejorar la velocidad de descarga para clientes lejanos o en redes móviles. fq es la disciplina de colas recomendada para acompañarlo.
  • Buffers TCP: los tres valores de tcp_rmem y tcp_wmem son mínimo, valor inicial y máximo. El kernel ajusta cada socket de forma automática dentro de ese rango, así que subir el máximo no reserva memoria por adelantado. Un máximo de 16 MB permite aprovechar enlaces rápidos con latencia alta.
  • Keepalive y salientes: tcp_slow_start_after_idle = 0 evita que una conexión keepalive inactiva vuelva a empezar con una ventana pequeña. ip_local_port_range amplía los puertos de origen disponibles para las conexiones que el servidor abre hacia backends (proxy inverso, PHP-FPM por TCP, bases de datos). Empieza en 10240 para no chocar con servicios que escuchan en puertos bajos. tcp_tw_reuse = 1 permite reutilizar sockets en TIME-WAIT para nuevas conexiones salientes, y tcp_fin_timeout reduce el tiempo en FIN-WAIT-2.

Antes de aplicar el archivo, confirma que el kernel tiene BBR disponible:

sudo modprobe tcp_bbr
sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic bbr

Para que el módulo se cargue también en cada arranque, añádelo a /etc/modules-load.d/:

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

Paso 3: Aplicar y verificar los cambios

Carga todos los archivos de /etc/sysctl.d/, igual que ocurrirá en el próximo arranque:

sudo sysctl --system

La salida lista cada archivo leído y cada valor aplicado. Si algún parámetro no existe o tiene un formato incorrecto, verás una línea con sysctl: cannot stat o Invalid argument: corrígela antes de seguir.

Comprueba los valores clave:

sysctl net.core.somaxconn net.ipv4.tcp_congestion_control net.core.default_qdisc
net.core.somaxconn = 8192
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq

Las conexiones nuevas usarán BBR de inmediato. Puedes verlo en cualquier conexión activa, por ejemplo tu propia sesión SSH después de reconectar:

ss -ti state established '( sport = :22 )' | grep -o 'bbr'
bbr

La cola fq se aplica a las interfaces al crearse, así que la interfaz principal seguirá con la cola anterior hasta el próximo reinicio. Si quieres activarla ya sin reiniciar, sustituye eth0 por el nombre de tu interfaz (ip -br link lo muestra):

sudo tc qdisc replace dev eth0 root fq
tc qdisc show dev eth0
qdisc fq 8001: root refcnt 2 limit 10000p flow_limit 100p ...

Paso 4: Ajustar Nginx a los nuevos límites

El kernel limita el backlog, pero es la aplicación quien lo solicita al abrir el socket. Nginx pide 511 por defecto, así que hay que indicarle el nuevo valor en la directiva listen. Abre el bloque server de tu sitio:

sudo nano /etc/nginx/sites-available/default

Añade el parámetro backlog a las directivas listen:

server {
    listen 80 default_server backlog=8192;
    listen [::]:80 default_server backlog=8192;
    # ... resto de la configuración
}

El segundo límite habitual son los descriptores de archivo: cada conexión de cliente consume uno, y si Nginx actúa como proxy consume dos. El servicio de systemd arranca con un límite blando de 1024. Sube el límite de los workers y el número de conexiones en /etc/nginx/nginx.conf:

sudo nano /etc/nginx/nginx.conf

Ajusta estas directivas (la primera va en el contexto principal, fuera de cualquier bloque; worker_connections ya existe dentro de events):

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 16384;
}

worker_rlimit_nofile eleva el límite de cada worker sin tocar la unidad de systemd, y worker_connections debe quedar por debajo de él. Valida la configuración y recarga Nginx:

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

Comprueba que el socket usa el nuevo backlog:

ss -ltn 'sport = :80'
State   Recv-Q  Send-Q  Local Address:Port  Peer Address:Port
LISTEN  0       8192          0.0.0.0:80         0.0.0.0:*

Y que los workers tienen el límite de descriptores ampliado:

grep 'Max open files' /proc/$(pgrep -f 'nginx: worker' | head -n 1)/limits
Max open files            65535                65535                files

Paso 5: Probar bajo carga

La herramienta wrk genera muchas conexiones HTTP concurrentes con poco consumo de recursos. Instálala en la máquina cliente:

sudo apt install wrk

Lanza una prueba de 30 segundos con 4 hilos y 2000 conexiones abiertas contra your_server_ip:

wrk -t4 -c2000 -d30s --latency http://your_server_ip/
Running 30s test @ http://your_server_ip/
  4 threads and 2000 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    38.12ms   21.44ms 412.30ms   81.02%
    Req/Sec    13.40k     1.52k   17.90k    72.11%
  Latency Distribution
     50%   34.80ms
     99%  118.66ms
  1599208 requests in 30.04s, 1.28GB read
Requests/sec:  53236.18

Si el cliente se queja de Too many open files, sube su propio límite antes de lanzar la prueba con ulimit -n 65535.

Mientras dura la prueba, vuelve a mirar en el servidor los contadores del paso 1:

nstat -az TcpExtListenOverflows TcpExtListenDrops

Si siguen en 0 (o crecen mucho menos que antes de los cambios) y la columna Socket errors de wrk no muestra connect ni timeout, la cola de conexiones ya no es el cuello de botella. A partir de aquí, las mejoras vendrán de la aplicación: caché, compresión, número de workers de PHP-FPM o del backend.

Solución de problemas

sysctl: cannot stat /proc/sys/net/...: No such file or directory: el parámetro no existe en este kernel (típico de tcp_tw_recycle) o tiene una errata. Elimínalo del archivo y vuelve a ejecutar sudo sysctl --system.

tcp_congestion_control vuelve a cubic tras reiniciar: el módulo tcp_bbr no se cargó antes de aplicar sysctl. Comprueba que existe /etc/modules-load.d/bbr.conf y que lsmod | grep tcp_bbr muestra el módulo.

Nginx no arranca tras añadir backlog: nginx -t mostrará duplicate listen options for 0.0.0.0:80. El parámetro está repetido en varios bloques server del mismo puerto; déjalo solo en uno.

Recv-Q crece en el socket en escucha aunque el backlog sea grande: la aplicación no acepta conexiones al ritmo al que llegan. El problema está en los workers (CPU saturada, backend lento), no en el kernel.

Conclusión

Has medido la cola de conexiones del servidor, aplicado un conjunto persistente de ajustes sysctl con BBR, buffers TCP más amplios y un backlog mayor, y has configurado Nginx para aprovecharlo. La clave es repetir la medición del paso 1 tras cada cambio: si un contador no se mueve, el ajuste no hacía falta.

Como siguientes pasos, puedes revisar la gestión de memoria y swap del servidor, habilitar HTTP/2 y la caché de Nginx, o ajustar el programador de E/S si el servidor también sirve mucho contenido desde disco.