...

Configurar de forma óptima el «Keepalive» de NGINX Upstream para obtener el máximo rendimiento como proxy inverso

Configuraré el «Upstream Keepalive» de NGINX para que el proxy inverso establezca menos conexiones, ofrezca menores latencias y absorba de forma fiable los picos de carga. Para ello, ajustaré Tamaño de la piscina, los límites de tiempo y los encabezados de forma específica, para que se reutilicen las conexiones y la ruta de datos se mantenga optimizada.

Puntos centrales

  • HTTP/1.1 Forzar y depurar los encabezados de conexión
  • keepalive dimensionar correctamente por trabajador
  • Tiempos muertos adaptar a los valores del backend
  • Solicitudes/Conexión reducir y reciclar
  • Monitoreo en cuanto a velocidad de conexión y latencia

Por qué Upstream Keepalive reduce drásticamente el esfuerzo de establecimiento de conexiones

Sin reutilización, NGINX abre una nueva conexión con el backend por cada solicitud, lo que conlleva más intercambios de datos, un mayor consumo de ciclos de CPU y un uso adicional de recursos del núcleo; es precisamente aquí donde entra en juego Keepalive . Hago que NGINX almacene en caché los sockets ya establecidos que se encuentran inactivos en ese momento y los utilice para las solicitudes posteriores, lo que reduce de forma apreciable los tiempos de conexión. Esto disminuye la tasa de conexiones por segundo, reduce los picos de backlog y frena los cambios de contexto en el sistema operativo. Especialmente con TLS hacia el backend, ahorro tiempo de forma notable gracias a la reutilización de sesiones. De este modo, la cadena de respuestas se mantiene estable incluso con un alto rendimiento fiable y responde con fluidez.

Principio básico y la directiva «keepalive» en el upstream

La directiva keepalive En el bloque „upstream“ se limita el número de conexiones de backend inactivas almacenadas temporalmente por cada trabajador. Este límite no es global, sino que se aplica estrictamente por cada proceso de trabajador, por lo que siempre estoy pendiente del número de trabajadores. Cuando el grupo está lleno, NGINX cierra primero la conexión que lleva más tiempo sin utilizarse, para que haya espacio para nuevos sockets. Para la reutilización, el lado del proxy necesita HTTP/1.1 y un encabezado «Connection» neutralizado. Sin estos requisitos, el grupo permanece vacío, aunque configure «keepalive» en el upstream, algo que muchos administradores al principio sorprendido.

upstream backend_pool {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    keepalive 32; # conexiones inactivas por trabajador
    keepalive_requests 1000;   # reciclaje tras N solicitudes
    keepalive_timeout 60s;     # tiempo de vida en inactividad
}

servidor {
    escuchar 80;
    ubicación / {
 proxy_pass http://backend_pool;
 proxy_http_version 1.1;
 proxy_set_header Connection "";
    }
}

Directivas obligatorias en el bloque «Location»: HTTP/1.1 y control de encabezados

En la ruta del proxy, obligo a NGINX a utilizar HTTP/1.1, ya que Keepalive no funciona correctamente con HTTP/1.0 y las conexiones se cierran innecesariamente; la directiva proxy_http_version Por lo tanto, 1.1 es obligatorio. Además, elimino el encabezado „Connection“ de las solicitudes normales, para que el backend no reciba una instrucción „close“. Para actualizaciones como WebSockets, establezco de forma específica «Connection: Upgrade» mediante map, sin afectar a la reutilización habitual. De este modo, la política de conexión se mantiene coherente y desacoplada de los encabezados del cliente. Es precisamente este pequeño cambio el que evita muchos problemas difíciles de detectar Imágenes de errores.

location / {
    proxy_pass http://backend_pool;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    "" "";
}

Ajuste preciso: seleccionar correctamente los valores de «keepalive_requests» y «keepalive_timeout»

Con dos tornillos de ajuste controlo la duración y la renovación de las conexiones, para que la piscina se mantenga limpia y no haya sockets abandonados que molesten; estos son keepalive_requests y keepalive_timeout. Tras N solicitudes, NGINX cierra la conexión de forma selectiva y la restablece si es necesario, lo que mitiga los efectos del envejecimiento de la red. Suelo fijar el tiempo de espera de inactividad (Idle-Timeout) en un valor bastante ajustado, normalmente entre 30 y 120 segundos, para que los servidores backend no se desconecten antes de tiempo. Es importante el equilibrio: el valor de NGINX nunca debe superar el tiempo de espera de los servidores de aplicaciones, ya que, de lo contrario, se acumularían los reinicios de conexión. Quien desee profundizar en los aspectos técnicos, encontrará consejos prácticos en el artículo Tiempo de espera de keepalive, que explica los valores típicos y las interacciones.

Para facilitar la orientación, muestro los valores iniciales habituales y su finalidad respectiva en una tabla clara Cuadro. Los valores de referencia sirven como punto de partida y, tras el seguimiento, suelen acabar siendo algo más altos o más bajos. Un periodo demasiado corto genera nuevas conexiones innecesarias, mientras que uno demasiado largo mantiene conexiones antiguas. El número de solicitudes por conexión me protege de los valores atípicos, sin vaciar el grupo. Con estos datos clave, consigo soluciones que funcionan muy rápidamente. Por defecto.

Parámetros Propósito valor indicativo Nota sobre el tuning
keepalive Tamaño del grupo de procesos inactivos por trabajador 32-64 Ajustar según la carga simultánea por trabajador
keepalive_requests Solicitudes máximas por conexión 500–1000 En el caso de transmisiones largas, fíjalo un poco más alto
tiempo de espera de keepalive Tiempo máximo de inactividad por conexión los años 60 Tiempo de espera de inactividad del backend más corto o igual

Determinar el tamaño del grupo en función de las conexiones simultáneas

No elijo el tamaño del pool en función de las solicitudes por segundo, sino en función de Concurrencia por trabajador. En primer lugar, calculo el número medio y máximo de solicitudes paralelas al backend. A continuación, divido estas cifras entre el número de trabajadores de NGINX y redondeo al alza. Para 200 solicitudes simultáneas con cuatro workers, obtengo un resultado de unas 50 por worker, por lo que un valor inicial de keepalive de 64 resulta adecuado. De esta forma, mantengo los sockets disponibles sin abrir un número innecesario de Conexiones para atar.

Aprovechar deliberadamente las características especiales de las versiones más recientes de NGINX

Las versiones actuales suelen permitir la reutilización de forma predeterminada, aunque establecen límites bastante conservadores; aun así, voy a introducir los valores explícito . Esto garantiza la reproducibilidad, facilita el ajuste y evita sorpresas tras una actualización. Mediante el parámetro „local“, puedo limitar opcionalmente la reutilización a una ubicación concreta si los perfiles de seguridad o las políticas de encabezados difieren. De este modo, la separación se mantiene clara, sin perder las ventajas de la reutilización a nivel global. Con valores claros, documento mis intenciones y me ahorro trabajo más adelante Tiempo de análisis.

Supervisión y métricas: ¿funciona realmente la configuración?

En primer lugar, compruebo el número de nuevas conexiones al backend por segundo; una disminución significativa indica que las medidas están surtiendo efecto Reutilice. A continuación, observo el «upstream_connect_time», que se sitúa cerca de cero cuando hay coincidencias en el pool. Los errores en los registros, especialmente los restablecimientos de conexión, apuntan a límites de tiempo que están por detrás de los valores del backend. Además, correlaciono la CPU del backend y las latencias con el porcentaje de conexiones reutilizadas. Para comprender mejor la Reutilización de conexiones Son de ayuda los ejemplos que muestran los efectos en distintos patrones de carga.

Eliminar rápidamente las fuentes típicas de errores

Si no hay HTTP/1.1 hacia el backend, las conexiones duran muy poco, por mucho que yo keepalive establezco. Si el cliente envía „Connection: close“ y yo paso el encabezado sin filtrar, el backend libera cada conexión inmediatamente después de la respuesta. Si los tiempos de espera de inactividad no coinciden, el lado de la aplicación cierra la conexión primero y NGINX recibe un reinicio en la siguiente solicitud. Un pool sobredimensionado mantiene abiertos demasiados sockets y desperdicia memoria y puertos. Compruebo estos cuatro puntos en cada análisis como Primero, porque explican el 90 % de todos los problemas.

Ejemplo práctico: configuración de referencia para un alto rendimiento

Con unas pocas instrucciones, consigo que un proxy muy sobrecargado funcione de forma rápida y fiable y garantizo un reenvío correcto de los encabezados; el siguiente modelo ha demostrado su eficacia y es fácil de personalizar. Establezco el keepalive en 64, limito las solicitudes por conexión a 1000 y mantengo un tiempo de inactividad de 60 segundos. Además, transmito correctamente la información del host y de reenvío para que los backends puedan aplicar la lógica y la limitación de tasa. Esta combinación reduce la carga de la CPU, acorta los tiempos de respuesta y permite gestionar los picos de carga con mayor tranquilidad. Así es precisamente como consigo una Actuación.

upstream app_backend {
    server 10.0.1.10:3000 max_fails=2 fail_timeout=30s;
    server 10.0.1.11:3000 max_fails=2 fail_timeout=30s;

    keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    listen 80;
    server_name example.com;

    location / {
 proxy_pass http://app_backend;
 proxy_http_version 1.1;
 proxy_set_header Connection "";
 proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Entornos de alojamiento y aspectos operativos que realmente importan

A menudo utilizo NGINX delante de PHP-FPM, Node.js o servicios Java, y me aseguro de que las latencias de red sean mínimas y de que los tiempos de espera del backend sean constantes; esto proporciona Planificabilidad. Una configuración de red del kernel sólida, con límites de sockets adecuados, evita que se produzcan colisiones entre numerosas conexiones abiertas. Una asignación uniforme de la CPU y rutas de almacenamiento rápidas ayudan a los backends a mantener tiempos de respuesta cortos. Además, me encargo de que las configuraciones estén versionadas, para que los cambios sean trazables. Con esta disciplina, el sistema se mantiene estable incluso en picos de tráfico reaccionable.

Buenas prácticas para las operaciones en curso

Empiezo con un keepalive de 32-64, 500-1000 solicitudes por conexión y un tiempo de inactividad de 60 segundos; después realizo mediciones sistemáticas y ajusto los valores; esto permite una rápida éxitos. Acompaño cada cambio con métricas sobre la tasa de conexión, la latencia y los patrones de error, hasta que las curvas se estabilicen. Ajusto el tamaño del pool en función de las solicitudes simultáneas, no del rendimiento bruto por segundo. Los tiempos de espera nunca deben ser más largos que los de sus homólogos en la pila de backend; de lo contrario, se corre el riesgo de que se produzcan reinicios esporádicos. Quien desee ajustar la velocidad con mayor precisión encontrará indicaciones sobre el ajuste fino en Optimizar las solicitudes Keepalive, lo que hace que el reciclaje sea fácil de gestionar.

Sincronización de los tiempos de espera del proxy y del «keepalive» de TCP

Además de los parámetros puros de Keepalive, ajusto con precisión los límites de tiempo de transporte. La tríada formada por proxy_connect_timeout, proxy_send_timeout y proxy_read_timeout Determina el nivel de paciencia de NGINX a la hora de establecer conexiones, enviar y recibir datos. Nunca establezco estos valores por encima de los correspondientes en el backend, sino ligeramente por debajo, para que los errores se detecten pronto y no se agraven en el lado de la aplicación. Además, activo proxy_socket_keepalive, para que el sistema operativo envíe señales de vida a intervalos regulares a través de los sockets inactivos y detecte las conexiones semiabiertas. Esto evita que las conexiones inactivas permanezcan en el grupo y provoquen picos de latencia en la siguiente solicitud.

server {
    listen 80;

    location / {
 proxy_pass http://backend_pool;

 proxy_connect_timeout 3s;   #: fallar rápidamente si no es posible establecer la conexión
 proxy_send_timeout    30s;  # Escritura en el backend
 proxy_read_timeout    30s;  # Respuestas del backend
 proxy_socket_keepalive on;  # Activar el keepalive TCP del sistema operativo
    }
}

En el caso de flujos de datos de larga duración (por ejemplo, SSE o WebSockets), solo aumento el tiempo de espera de lectura, mientras que el de conexión se mantiene igual. De esta forma, reacciono rápidamente ante destinos defectuosos, pero dejo que las respuestas legítimas y largas se procesen sin interrupciones.

Planificación de recursos: worker_connections, FD y puertos efímeros

Un grupo de keepalive limpio no sirve de nada si se agotan los límites de descriptores de archivo o los rangos de puertos. Por eso tengo pensado conexiones_trabajadores y worker_rlimit_nofile con margen. A grandes rasgos, calculo lo siguiente: FD abiertos ≈ (conexiones simultáneas de clientes + conexiones simultáneas de backend + sockets inactivos agrupados en un pool) por trabajador. Si utilizo varios upstreams con pools, la demanda se multiplica. Asimismo, presto atención al rango de puertos efímeros del sistema, ya que NGINX actúa como cliente TCP hacia el backend y acumula estados TIME_WAIT.

worker_processes auto;
worker_rlimit_nofile 131072;

events {
    worker_connections 8192;
}
Ejemplos de Linux para # (sysctl):
net.core.somaxconn = 4096
net.ipv4.ip_local_port_range = 10240 65535
net.ipv4.tcp_fin_timeout = 15

Adopto un enfoque conservador: no elimino el TIME_WAIT de forma agresiva, sino que reduzco la frecuencia de conexión mediante Keepalive. De este modo, los parámetros del núcleo no se ven afectados de forma crítica y el comportamiento sigue siendo predecible.

Zonas de origen, estrategia de equilibrio de carga y rotación de DNS

Si hay varios trabajadores, comparto el estado del equilibrador a través de una zona, para que las caídas y las cargas se mantengan constantes. Aunque las conexiones Keepalive siguen asignándose por trabajador, la distribución es más uniforme. En el caso de backends dinámicos que cambian de ubicación a través del DNS, configuro „resolver“ en las líneas del servidor y define un resolver. Importante: cuando las direcciones IP rotan, el grupo no recicla inmediatamente todos los sockets antiguos; por lo tanto, considero que keepalive_requests y plazos realistas, para que la renovación surta efecto rápidamente.

upstream backend_pool {
    zone backend_zone 128k;  # comparte el estado del equilibrador
    least_conn; # distribución equitativa en solicitudes largas

 server app-1.internal:8080 resolve;
    servidor app-2.internal:8080 resolver;

 keepalive 64;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

resolver 10.0.0.2 valid=30s;
resolver_timeout 5s;

proxy_next_upstream error timeout http_502 http_504;
proxy_next_upstream_tries 2;  #: pocas repeticiones selectivas

Para las sesiones vinculadas a un nodo de backend concreto (por ejemplo, «sticky-state»), combino la reutilización con ip_hash o un mecanismo de sesión externo. Esto evita que el agrupamiento de conexiones afecte a la coherencia de la sesión.

TLS hacia el backend: SNI, reutilización de sesiones y algoritmos de cifrado

Cuanto más se utilice TLS en la ruta de backend, más valioso resulta Keepalive. Activo SNI, defino el nombre esperado y me aseguro de que se reutilice la sesión TLS. Esto reduce los costes del handshake y suaviza los picos de latencia. Selecciono las suites de cifrado y los protocolos de forma restrictiva, sin excluir los backends más antiguos. En la verificación de certificados (opcional), la cadena de confianza debe estar completa; de lo contrario, las conexiones se interrumpirán esporádicamente.

upstream https_backend {
    server backend.example.local:443;
    keepalive 32;
}

server {
    listen 443 ssl;

 location / {
 proxy_pass https://https_backend;
 proxy_http_version 1.1;
 proxy_set_header Connection "";

        proxy_ssl_server_name on;
 proxy_ssl_name backend.example.local;
 proxy_ssl_session_reuse on;
 proxy_ssl_protocols TLSv1.2 TLSv1.3;
        proxy_ssl_ciphers HIGH:!aNULL:!MD5;
 # opcional: proxy_ssl_verify on;
 # opcional: proxy_ssl_trusted_certificate /etc/nginx/ca.pem;
    }
}

Si controlo yo mismo el backend, activo allí los tickets de sesión o las cachés y compruebo, mediante métricas, si aumentan las tasas de reanudación. En combinación con Keepalive, consigo así tiempos de conexión y de handshake bajos de forma permanente.

Casos especiales: gRPC, WebSockets y autenticación vinculada a la conexión

En gRPC NGINX funciona en la capa superior a través de HTTP/2. En este caso, unas pocas conexiones de larga duración con muchos flujos suelen ofrecer los mejores resultados; el grupo de conexiones se mantiene pequeño, pero estable. Para WebSockets Establezco tiempos de espera de lectura largos y mantengo la lógica de los encabezados de la solución «map», para que las conexiones de actualización no se cierren por error. NTLM o bien otras autenticaciones vinculadas a la conexión requieren «connection pinning»; separo esas rutas en ubicaciones independientes y reduzco allí el uso de pools o la reutilización, para que los handshakes de seguridad no se mezclen entre clientes.

Ejemplo de gRPC con #
location /grpc.Service/ {
    grpc_pass grpc://backend_pool;
    grpc_read_timeout 300s;  Permitir flujos largos con #
}

Es fundamental establecer una política de conexión coherente para cada ruta y utilizar Keepalive de forma generalizada únicamente en aquellos casos en los que no sea crítico desde el punto de vista semántico.

La medibilidad en la práctica: registros de acceso con tiempos de transmisión ascendente

Amplío el registro de Access con métricas de upstream. Así puedo ver de un vistazo si una respuesta procedía de un socket agrupado (tiempo de conexión muy corto) y con qué frecuencia se producen errores en el backend. Además, registro el número de conexión y el número de solicitudes realizadas a través de la conexión actual del cliente para detectar correlaciones.

log_format upstream_timing '$remote_addr - $host "$request" '
 'up=$upstream_addr '
 'sc=$status usc=$upstream_status '
                           'cc=$connection cr=$connection_requests '
 'tc=$upstream_connect_time '
 'th=$upstream_header_time '
 'tr=$upstream_response_time';

access_log /var/log/nginx/access_upstream.log upstream_timing;

Además, utilizo los puntos finales de estado y las estadísticas de sockets del sistema operativo. Un estado óptimo se caracteriza por: una tasa de conexión al backend en descenso, un tiempo de conexión ascendente (upstream_connect_time) más corto, tiempos de respuesta estables y apenas reinicios de conexión. Las desviaciones suelen indicar casi siempre que los límites de tiempo no están bien ajustados o que los grupos de conexiones son demasiado pequeños o demasiado grandes.

Estrategia de implantación y ajuste con bajo riesgo

Sigo un proceso iterativo: pequeños pasos, medir y ajustar. Primero activo el keepalive de forma moderada y, a continuación, ajusto los tiempos de espera y el número de solicitudes por conexión. Aplico los cambios actualizando la página, sin desconectar las conexiones activas. De esta forma, el riesgo es mínimo y los efectos se pueden atribuir con claridad.

#: validar los cambios y cargarlos sin tiempo de inactividad
nginx -t && nginx -s reload

Cuando gestiono varios flujos ascendentes, los ajusto uno tras otro, empezando por la ruta más crítica. A cada etapa le asigno un periodo de observación para que se puedan identificar claramente los patrones en las métricas. Solo entonces aumento o reduzco los valores.

Resumen conciso sobre tu proxy inverso

Apuesto por HTTP/1.1, dejo vacío el encabezado «Connection» y elijo el tamaño del grupo en función de las solicitudes simultáneas, no de las RPS; esto contribuye a la Actuación. Con «keepalive_requests» y «keepalive_timeout» mantengo las conexiones activas y evito sorpresas debidas a sockets obsoletos. La supervisión muestra si «upstream_connect_time» tiende a cero y si la tasa de conexión con el backend disminuye. En caso de errores, compruebo primero la versión del protocolo, la transmisión de encabezados, los tiempos de espera y el tamaño del grupo. Así se mantiene tu proxy NGINX bajo una carga elevada receptivo y previsible.

Artículos de actualidad

Servidores modernos con un rendimiento optimizado en la caducidad de claves de Redis
Bases de datos

Analizar y optimizar el rendimiento de la caducidad de claves en Redis

Aprende a optimizar el rendimiento de la caducidad de claves de Redis mediante estrategias de TTL adecuadas, políticas de expulsión y una supervisión específica, y a mantener la estabilidad de tu caché. Tema central: la caducidad de claves de Redis.