Optimize Node.js Outbound Requests with Keep-Alive
Making frequent outbound API calls in Node.js can lead to network latency and system resource exhaustion if a new TCP connection is opened for every request. By leveraging HTTP keep-alive, Node.js allows applications to reuse existing TCP connections for multiple outbound HTTP/HTTPS requests, drastically reducing overhead. This article explains how Node.js manages connection reuse using its internal agent architecture, how to enable keep-alive, and the performance benefits it brings to high-throughput systems.
The Problem with Default Behavior
By default, the standard Node.js HTTP/HTTPS agents destroy TCP connections immediately after a response is completed. For every subsequent outbound API request, Node.js must perform a new TCP three-way handshake and, for secure endpoints, an expensive TLS handshake. This process adds significant latency to each round-trip time (RTT) and can lead to ephemeral port exhaustion on the host server under heavy traffic.
How Node.js Leverages Keep-Alive
Node.js manages outbound HTTP requests through the
http.Agent and https.Agent classes. When HTTP
keep-alive is enabled, the agent keeps the underlying TCP sockets open
in a pool after a request finishes, rather than closing them.
When a new request is initiated to the same destination (host and port), the agent retrieves an idle socket from the pool instead of creating a new one. This bypasses the TCP connection setup and TLS handshake entirely, allowing the payload to be sent immediately over the pre-existing socket.
Configuring Keep-Alive in Node.js
To enable connection reuse, you must instantiate a custom agent with
the keepAlive option set to true and pass it
to your outbound request configuration.
Here is how to configure it using the native https
module:
const https = require('https');
// Create a persistent agent
const keepAliveAgent = new https.Agent({
keepAlive: true,
maxSockets: 100, // Maximum active sockets per origin
maxFreeSockets: 10, // Maximum idle sockets to keep open in the pool
keepAliveMsecs: 1000 // Delay before sending keep-alive probes on idle socket
});
const options = {
hostname: 'api.example.com',
port: 443,
path: '/data',
method: 'GET',
agent: keepAliveAgent // Instructs Node.js to use the persistent agent
};
const req = https.request(options, (res) => {
res.on('data', () => {}); // Consume response to free up the socket
});
req.end();For popular third-party HTTP libraries like Axios, you can pass the custom agent directly into the instance configuration:
const axios = require('axios');
const https = require('https');
const axiosInstance = axios.create({
httpsAgent: new https.Agent({ keepAlive: true })
});Key Performance Benefits
- Reduced Latency: Eliminating the TCP and TLS handshakes for subsequent requests saves valuable milliseconds on every API call, offering much faster overall response times.
- Lower CPU Usage: Reusing secure connections avoids the heavy cryptographic calculations required to establish TLS sessions repeatedly.
- Prevention of Socket Exhaustion: Reusing sockets
prevents the operating system from accumulating thousands of sockets in
the
TIME_WAITstate, ensuring system stability and scalability under high loads.