Keep-Alive y Límites de Conexión
Ajusta los tiempos de espera de keep-alive y conexión HTTP para que los servidores Node se comporten correctamente detrás de nginx, ALB y otros proxies inversos.
Receta
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain", Connection: "keep-alive" });
res.end("ok\n");
});
// Alinea con tu proxy inverso (el keepalive_timeout predeterminado de nginx es 75s)
server.keepAliveTimeout = 65_000; // ligeramente por debajo del tiempo de espera del proxy
server.headersTimeout = 66_000; // debe exceder keepAliveTimeout
server.requestTimeout = 30_000;
server.maxConnections = 1000; // límite de sockets concurrentes
server.listen(3000);Cuándo usarlo: Errores 502 intermitentes durante las implementaciones, registros de restablecimiento de conexión o EMFILE (demasiados archivos abiertos) bajo carga.
Ejemplo de Trabajo
import { createServer } from "node:http";
import { Agent, request as httpRequest } from "node:http";
// --- Ajuste del servidor ---
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
});
server.keepAliveTimeout = 65_000;
server.headersTimeout = 66_000;
server.requestTimeout = 30_000;
server.listen(3000);
// --- Cliente saliente con reutilización de conexión ---
const agent = new Agent({
keepAlive: true,
maxSockets: 50, // conexiones concurrentes por host
maxFreeSockets: 10, // sockets inactivos mantenidos en el pool
timeout: 30_000,
});
export function callDownstream(path: string): Promise<string> {
return new Promise((resolve, reject) => {
const req = httpRequest(
{ hostname: "api.internal", port: 8080, path, agent, timeout: 10_000 },
(res) => {
const chunks: Buffer[] = [];
res.on("data", (c) => chunks.push(c));
res.on("end", () => resolve(Buffer.concat(chunks).toString()));
}
);
req.on("error", reject);
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
req.end();
});
}Lo que esto demuestra:
- Tiempo de espera de keep-alive del lado del servidor alineado por debajo del proxy
headersTimeoutmayor quekeepAliveTimeout(requisito de Node)Agentsaliente conkeepAlive: truepara la agrupación de conexiones- Tiempo de espera por solicitud en llamadas salientes
Análisis Profundo
Cómo Funciona
- HTTP/1.1 por defecto utiliza conexiones persistentes (keep-alive)
- Después de una respuesta, el socket TCP permanece abierto para su reutilización
- Si el servidor y el proxy no están de acuerdo sobre cuándo cerrar, una de las partes envía datos en un socket cerrado (RST)
keepAliveTimeoutde Node controla cuánto tiempo un socket de servidor inactivo espera la siguiente solicitud
Tabla de Alineación de Proxy
| Capa | Configuración | Valor típico | Regla |
|---|---|---|---|
| nginx | keepalive_timeout | 75s | El proxy cierra el upstream inactivo |
| Servidor Node | keepAliveTimeout | 65s | Debe ser menor que el proxy |
| Servidor Node | headersTimeout | 66s | Debe ser mayor que keepAliveTimeout |
| ALB | tiempo de espera de inactividad | 60s por defecto | Establece Node keepAlive por debajo del tiempo de inactividad de ALB |
| Agente saliente | maxSockets | 50-100 | Evita que un host monopolice los descriptores de archivo |
Notas de TypeScript
// Registra cuando las conexiones se acercan a los límites
server.on("connection", (socket) => {
socket.setTimeout(30_000);
socket.on("timeout", () => socket.destroy());
});Errores Comunes
- 502 durante implementaciones continuas - el proxy envía la solicitud a un socket que Node ya cerró. Solución: establece
keepAliveTimeout5-10s por debajo del tiempo de espera del proxy. headersTimeoutdemasiado bajo - Node 18+ destruye las conexiones a mitad de los encabezados. Solución:headersTimeout > keepAliveTimeout.- Sin
Agentsaliente - cada llamada HTTP abre una nueva conexión TCP. Solución: reutiliza unAgenta nivel de módulo conkeepAlive: true. maxSocketspredeterminado (Infinity) - un downstream lento puede agotar los descriptores de archivo. Solución: limitamaxSocketspor host.- Ignorar
server.maxConnections- bajo ataque o tráfico viral, la cola de aceptación crece sin límites. Solución: establecemaxConnectionsy manejaserver.on("drop"). - ULIMIT demasiado bajo - 1024 descriptores de archivo por defecto no son suficientes para APIs ocupadas. Solución: aumenta
ulimit -nen la configuración del contenedor/systemd.
Alternativas
| Alternativa | Cuándo Usar | Cuándo No Usar |
|---|---|---|
| HTTP/2 en el proxy | La multiplexación reduce el número de conexiones | Necesitas que Node hable HTTP/2 directamente |
| Librería de pooling de conexiones (undici) | Fetch moderno con pooling integrado | Ya estandarizado en got o axios con agentes |
| Sockets de dominio Unix | nginx en el mismo host que Node | Despliegue entre hosts |
| Malla de servicios (Envoy) | mTLS + reintentos + disyuntor | Despliegue simple de un solo servicio |
Preguntas Frecuentes
¿Cuál es el valor predeterminado de keepalive_timeout de nginx?
75 segundos. Establece keepAliveTimeout de Node en 65s o menos para que Node cierre primero, evitando condiciones de carrera con el proxy.
¿Debo deshabilitar keep-alive?
Casi nunca para APIs. Deshabilitarlo aumenta la latencia y el uso de CPU debido a los handshakes TCP. Solo considéralo para depurar problemas de conexión específicos.
¿Cómo monitoreo las conexiones abiertas?
server.getConnections((err, count) => ...) para el recuento activo. Usa ss -s o Prometheus node_netstat para una vista a nivel de sistema.
¿Fastify maneja esto automáticamente?
Fastify usa el servidor Node subyacente. Aún debes establecer keepAliveTimeout en fastify.server después de listen().
¿Qué pasa con el keep-alive de WebSocket?
Los WebSockets usan tramas ping/pong, no keep-alive HTTP. Consulta Librería ws.
¿Cuántas maxConnections debo establecer?
Comienza con (RPS esperado * tiempo de respuesta promedio) * 2. Una API de 1000 RPS con 50ms de latencia necesita aproximadamente 50-100 conexiones concurrentes, pero la capacidad de ráfaga necesita margen.
¿Afecta keep-alive a las comprobaciones de estado del balanceador de carga?
Las comprobaciones de estado usan conexiones separadas de corta duración por defecto. Un keep-alive mal configurado afecta principalmente el tráfico de usuarios durante las implementaciones.
¿Cuál es la diferencia entre keepAliveTimeout y requestTimeout?
keepAliveTimeout se aplica a los sockets inactivos entre solicitudes. requestTimeout limita cuánto tiempo puede ejecutarse una sola solicitud activa.
Relacionado
- Conciencia del Proxy Inverso - Encabezados X-Forwarded y proxy de confianza
- Consideraciones de HTTP/2 y HTTP/3 - terminación de protocolo
- Conceptos Básicos de HTTP en Node - fundamentos del servidor
- Rendimiento en Express - ajuste a nivel de framework
- Mejores Prácticas de Fundamentos HTTP - lista de verificación de la sección
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS Activo), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.