TL;DR
The Error: socket hang up means the server closed the TCP connection before finishing the response. The fastest fixes:
- Set a longer
timeouton your request - Disable or reconfigure HTTP keep-alive if the server doesn't support it
- Retry on ECONNRESET/socket hang up errors
- Check that the server isn't behind a load balancer that's closing idle connections
What Actually Causes It
Node.js throws this when the TCP socket gets destroyed mid-request β either the server sent a TCP RST (reset), closed the connection after a timeout, or the network dropped it entirely. The full error usually looks like:
Error: socket hang up
at createHangUpError (node:_http_client:333:15)
at Socket.socketOnEnd (node:_http_client:437:23)
at Socket.emit (node:events:525:35)
at endReadableNT (node:internal/streams/readable:1358:12)
The most common culprits:
- Server-side timeout: The upstream server or proxy has a shorter timeout than your request takes to complete
- Keep-alive mismatch: You're reusing a connection the server already closed
- Load balancer idle timeout: AWS ALB, nginx, or HAProxy closes idle connections (often after 60s) while your agent holds them open
- Server crash or restart: The remote process died mid-response
- SSL/TLS handshake failure: Less common, but HTTPS requests can hang up during negotiation
Fix 1: Add a Request Timeout
When the server cuts off a slow request, having an explicit timeout on your end gives you a cleaner error and a chance to retry. Without it, Node just sits there waiting.
Using native https module
const https = require('https');
const req = https.request({
hostname: 'api.example.com',
path: '/data',
method: 'GET',
timeout: 10000, // 10 seconds
}, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => console.log(body));
});
req.on('timeout', () => {
req.destroy(); // triggers 'error' event
});
req.on('error', (err) => {
console.error('Request failed:', err.message);
});
req.end();
Using axios
const axios = require('axios');
const response = await axios.get('https://api.example.com/data', {
timeout: 10000, // 10s β applies to both connection + response
});
Fix 2: Disable Keep-Alive or Tune the Agent
This is the sneakiest cause of the bunch. Your HTTP agent has keep-alive enabled, the server quietly closed the connection on its side, and Node tries to reuse that dead socket on the next request. Hang up.
Disable keep-alive entirely (quick fix)
const https = require('https');
const agent = new https.Agent({ keepAlive: false });
const req = https.request({
hostname: 'api.example.com',
path: '/data',
agent,
}, (res) => { /* ... */ });
Tune keep-alive to match the server's timeout (better fix)
AWS ALB has a default idle timeout of 60 seconds. Set your agent's keepAliveMsecs lower than that β 30 seconds is a safe target:
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
keepAliveMsecs: 30000, // send TCP keep-alive every 30s
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
});
// Pass this agent to axios globally
const axios = require('axios');
const client = axios.create({
httpsAgent: agent,
timeout: 15000,
});
Fix 3: Retry on Socket Errors
Transient network blips happen. A simple retry wrapper catches hang ups before they bubble up and crash your app:
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let attempt = 1; attempt err.message.includes(code) || err.code === code);
if (isSocketError && attempt setTimeout(r, delay));
continue;
}
throw err;
}
}
}
Fix 4: Check Your Proxy or Load Balancer Settings
Apps running behind nginx or AWS ALB have an extra layer that can silently kill connections. Here's what to check:
nginx upstream keepalive
upstream backend {
server 127.0.0.1:3000;
keepalive 32; # keep up to 32 idle connections
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection ""; # required for keepalive upstream
proxy_read_timeout 90s; # match your app's response time
}
}
AWS ALB idle timeout
The default ALB idle timeout is 60 seconds. Requests that take longer than that will get cut off. Increase it in the AWS Console under Load Balancers β Attributes β Idle timeout. Alternatively, keep your HTTP agent's keepalive interval below 60s as shown in Fix 2.
Fix 5: HTTPS Certificate Issues
Certificate errors on internal services or staging environments can cause the server to drop the connection immediately β and that shows up as a socket hang up, not a cert error. Confusing.
// Temporary debug only β NEVER use in production
const agent = new https.Agent({ rejectUnauthorized: false });
// Better: point to your internal CA cert
const fs = require('fs');
const agent = new https.Agent({
ca: fs.readFileSync('/path/to/internal-ca.crt'),
});
Disabling rejectUnauthorized makes the error go away? You've got a certificate trust problem, not a network one.
Verify the Fix
Time to confirm the fix actually held. Run these checks:
# Test the endpoint directly with curl β if curl works but Node doesn't, it's the agent config
curl -v --max-time 15 https://api.example.com/data
# Check if keep-alive is actually being used
curl -v --keepalive-time 30 https://api.example.com/data 2>&1 | grep -i 'keep-alive\|connection'
# In Node, log the socket reuse
agent.on('free', (socket, options) => {
console.log('Socket returned to pool:', options.hostname);
});
Run your request 5β10 times in a loop. Stale keep-alive failures tend to hit on the first request after a 60-second idle period. Simulate that by waiting a minute, then retrying β if it only fails after the wait, Fix 2 is your solution.
Tips
Debugging network connectivity between Node.js and upstream APIs β especially in containerized environments β often comes down to routing and subnet configuration. The Subnet Calculator on ToolCraft is handy for checking CIDR ranges and confirming that source and destination are actually routable to each other. Saves a lot of time before you start poking at firewall rules.
One more pattern worth watching: if the error only shows up intermittently under production load, add structured logging around your HTTP calls. Log err.code, the target hostname, and request duration. Failures that cluster between 58β62 seconds? That's a load balancer idle timeout, almost every time.

