Keep-Alive & Connection Limits
Tune HTTP keep-alive and connection timeouts so Node servers behave correctly behind nginx, ALB, and other reverse proxies.
Search across all documentation pages
Tune HTTP keep-alive and connection timeouts so Node servers behave correctly behind nginx, ALB, and other reverse proxies.
Quick-reference recipe card - copy-paste ready.
import { createServer } from "node:http";
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain", Connection: "keep-alive" });
res.end("ok\n");
});
// Align with your reverse proxy (nginx default keepalive_timeout is 75s)
server.keepAliveTimeout = 65_000; // slightly below proxy timeout
server.headersTimeout = 66_000; // must exceed keepAliveTimeout
server.requestTimeout = 30_000;
server.maxConnections = 1000; // cap concurrent sockets
server.listen(3000);When to reach for this: Intermittent 502 errors during deploys, connection reset logs, or EMFILE (too many open files) under load.
import { createServer } from "node:http";
import { Agent, request as httpRequest } from "node:http";
// --- Server tuning ---
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);
// --- Outbound client with connection reuse ---
const agent = new Agent({
keepAlive: true,
maxSockets: 50, // per-host concurrent connections
maxFreeSockets: 10, // idle sockets kept in 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();
});
}What this demonstrates:
headersTimeout greater than keepAliveTimeout (Node requirement)Agent with keepAlive: true for connection poolingkeepAliveTimeout controls how long an idle server socket waits for the next request| Layer | Setting | Typical value | Rule |
|---|---|---|---|
| nginx | keepalive_timeout | 75s | Proxy closes idle upstream |
| Node server | keepAliveTimeout | 65s | Must be less than proxy |
| Node server | headersTimeout | 66s | Must be greater than keepAliveTimeout |
| ALB | idle timeout | 60s default | Set Node keepAlive below ALB idle |
| Outbound Agent | maxSockets | 50-100 | Prevent one host from monopolizing FDs |
// Log when connections approach limits
server.on("connection", (socket) => {
socket.setTimeout(30_000);
socket.on("timeout", () => socket.destroy());
});keepAliveTimeout 5-10s below proxy timeout.headersTimeout too low - Node 18+ destroys connections mid-headers. Fix: headersTimeout > keepAliveTimeout.Agent with keepAlive: true.maxSockets default (Infinity) - one slow downstream can exhaust file descriptors. Fix: cap maxSockets per host.server.maxConnections - under attack or viral traffic, accept queue grows unbounded. Fix: set maxConnections and handle server.on("drop").ulimit -n in container/systemd config.| Alternative | Use When | Don't Use When |
|---|---|---|
| HTTP/2 at proxy | Multiplexing reduces connection count | You need Node to speak HTTP/2 directly |
| Connection pooling library (undici) | Modern fetch with built-in pooling | Already standardized on got or axios with agents |
| Unix domain sockets | nginx on same host as Node | Cross-host deployment |
| Service mesh (Envoy) | mTLS + retries + circuit breaking | Simple single-service deploy |
75 seconds. Set Node keepAliveTimeout to 65s or lower so Node closes first, avoiding race conditions with the proxy.
Almost never for APIs. Disabling increases latency and CPU from TCP handshakes. Only consider it for debugging specific connection issues.
server.getConnections((err, count) => ...) for active count. Use ss -s or Prometheus node_netstat for system-wide view.
Fastify uses the underlying Node server. You still set keepAliveTimeout on fastify.server after listen().
WebSockets use ping/pong frames, not HTTP keep-alive. See ws Library.
Start with (expected RPS * avg response time) * 2. A 1000 RPS API with 50ms latency needs roughly 50-100 concurrent connections, but burst capacity needs headroom.
Health checks use separate short-lived connections by default. Misconfigured keep-alive mainly affects user traffic during deploys.
keepAliveTimeout applies to idle sockets between requests. requestTimeout limits how long a single active request can run.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 18, 2026