This error occurs when a TCP connection is forcibly closed by the remote server or network device before your Node.js application finishes reading data. The connection is abruptly terminated by sending a TCP RST packet instead of a graceful close.
The ECONNRESET error is a network-level error that occurs at the TCP layer when one side of a connection sends a TCP RST (reset) packet, indicating an immediate closure of the connection. In Node.js, this typically happens during HTTP requests, database connections, or any network operation when the remote server or an intermediate network device terminates the connection unexpectedly. This error is particularly common with HTTP keep-alive connections, where persistent connections are reused for multiple requests. When the server closes an idle connection due to timeout while the client attempts to send a request on that socket, you'll see ECONNRESET. The "peer" in "connection reset by peer" refers to the remote end of the connection - the server, service, or network device that initiated the connection closure. Unlike a graceful connection close (FIN packet), a reset is an abrupt termination that doesn't allow the other side to finish sending or receiving data.
Wrap your network operations in try-catch blocks to gracefully handle ECONNRESET errors:
const https = require('https');
async function makeRequest(url) {
try {
const response = await fetch(url);
return await response.json();
} catch (error) {
if (error.code === 'ECONNRESET') {
console.error('Connection reset by server:', error.message);
// Implement retry logic here
}
throw error;
}
}For http.request, listen for the 'error' event:
const req = https.request(options, (res) => {
// Handle response
});
req.on('error', (error) => {
if (error.code === 'ECONNRESET') {
console.error('Connection was reset');
}
});
req.end();Adjust keep-alive timeouts to ensure client closes connections before the server does. The client timeout should be shorter than the server timeout:
const http = require('http');
// For Node.js server
const server = http.createServer((req, res) => {
res.end('Hello');
});
// Set keep-alive timeout (default is 5 seconds)
server.keepAliveTimeout = 61000; // 61 seconds
server.headersTimeout = 62000; // 62 seconds (should be > keepAliveTimeout)
server.listen(3000);For HTTP clients using the default agent:
const http = require('http');
const agent = new http.Agent({
keepAlive: true,
keepAliveMsecs: 30000, // Send keep-alive probes every 30s
maxSockets: 50,
maxFreeSockets: 10,
timeout: 60000,
scheduling: 'lifo' // Use most recent socket first (reduces stale connections)
});
const options = {
hostname: 'api.example.com',
port: 443,
path: '/data',
method: 'GET',
agent: agent
};Add automatic retry for transient connection errors:
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, options);
return response;
} catch (error) {
lastError = error;
// Retry on ECONNRESET and other network errors
if (error.code === 'ECONNRESET' ||
error.code === 'ETIMEDOUT' ||
error.code === 'ENOTFOUND') {
if (attempt < maxRetries) {
// Exponential backoff: 1s, 2s, 4s
const delay = Math.pow(2, attempt) * 1000;
console.log(`Retry attempt ${attempt + 1} after ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw error;
}
}
throw lastError;
}
// Usage
const data = await fetchWithRetry('https://api.example.com/data');For axios:
const axios = require('axios');
const axiosInstance = axios.create({
timeout: 30000,
httpsAgent: new https.Agent({ keepAlive: true })
});
// Add retry interceptor
axiosInstance.interceptors.response.use(null, async (error) => {
const config = error.config;
if (!config || !config.retry) {
config.retry = 0;
}
if (config.retry >= 3) {
return Promise.reject(error);
}
if (error.code === 'ECONNRESET') {
config.retry += 1;
const delay = Math.pow(2, config.retry) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
return axiosInstance(config);
}
return Promise.reject(error);
});Configure connection pools to prevent socket exhaustion and stale connections:
const https = require('https');
const agent = new https.Agent({
keepAlive: true,
maxSockets: 50, // Max concurrent sockets
maxFreeSockets: 10, // Max idle sockets to keep
timeout: 60000, // Socket timeout
freeSocketTimeout: 30000, // Close idle sockets after 30s
scheduling: 'lifo' // Use most recent sockets first
});
// Use the agent for all requests
const options = {
hostname: 'api.example.com',
agent: agent
};For database connections (using a connection pool library):
const { Pool } = require('pg');
const pool = new Pool({
host: 'localhost',
port: 5432,
database: 'mydb',
max: 20, // Max pool size
idleTimeoutMillis: 30000, // Close idle connections after 30s
connectionTimeoutMillis: 2000,
keepAlive: true,
keepAliveInitialDelayMillis: 10000
});
// Handle pool errors
pool.on('error', (err, client) => {
console.error('Unexpected error on idle client', err);
});Investigate infrastructure problems that might cause connection resets:
1. Check load balancer timeout settings: Ensure load balancer idle timeout is greater than your application's keep-alive timeout.
2. Review firewall rules: Some firewalls aggressively close idle connections. Check iptables or cloud security groups.
3. Monitor server resource usage: High CPU or memory usage can cause servers to drop connections:
# Check server resources
top
free -m
netstat -an | grep ESTABLISHED | wc -l4. Test network stability:
# Test connection to the problematic endpoint
ping api.example.com
traceroute api.example.com
# Check for packet loss
mtr api.example.com5. Review server logs: Check nginx/apache logs for connection closures:
# Nginx error log
tail -f /var/log/nginx/error.log | grep "connection"
# Check for timeout configurations
grep -r "keepalive_timeout" /etc/nginx/Increase timeout values if legitimate requests are timing out:
// For fetch API (Node.js 18+)
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 60000); // 60s timeout
try {
const response = await fetch('https://api.example.com/data', {
signal: controller.signal
});
clearTimeout(timeoutId);
} catch (error) {
if (error.name === 'AbortError') {
console.error('Request timed out');
}
}For axios:
const axios = require('axios');
const instance = axios.create({
timeout: 60000, // 60 seconds
httpAgent: new http.Agent({
keepAlive: true,
timeout: 60000
}),
httpsAgent: new https.Agent({
keepAlive: true,
timeout: 60000
})
});For socket connections:
const net = require('net');
const socket = net.connect({
host: 'example.com',
port: 80
});
socket.setTimeout(60000); // 60 second timeout
socket.on('timeout', () => {
console.log('Socket timeout');
socket.destroy();
});Understanding Keep-Alive Race Conditions
A common cause of ECONNRESET is the keep-alive race condition: when a client attempts to reuse a socket at the exact moment the server decides to close it due to timeout. This is particularly problematic when client and server timeouts are similar.
Best practice: Set client-side timeouts 5-10 seconds shorter than server timeouts. For example, if your server keep-alive is 60 seconds, set client to 50-55 seconds.
LIFO vs FIFO Socket Scheduling
The 'scheduling' option in http.Agent determines which socket is selected from the pool:
- FIFO (first-in-first-out): Round-robin usage, but increases risk of picking stale sockets
- LIFO (last-in-first-out): Uses most recently active sockets, reducing stale socket issues
For low-traffic applications, LIFO significantly reduces ECONNRESET errors.
Dealing with Cloud Load Balancers
AWS ALB has a 60-second idle timeout by default. GCP Load Balancer defaults to 30 seconds. Azure defaults to 4 minutes. Always ensure your application's keep-alive timeout is shorter than your load balancer's timeout.
Production Monitoring
Set up monitoring for ECONNRESET errors to detect patterns:
const errorCounts = new Map();
process.on('uncaughtException', (error) => {
if (error.code === 'ECONNRESET') {
const count = errorCounts.get('ECONNRESET') || 0;
errorCounts.set('ECONNRESET', count + 1);
// Alert if error rate exceeds threshold
if (count > 100) {
// Send alert to monitoring service
}
}
});SSL/TLS Considerations
ECONNRESET can occur during TLS handshake failures. Enable TLS debugging:
NODE_DEBUG=tls node app.jsCheck certificate validity and ensure proper CA bundle configuration for self-signed certificates.
Error: Listener already called (once event already fired)
EventEmitter listener already called with once()
Error: EACCES: permission denied, open '/root/file.txt'
EACCES: permission denied
Error: Invalid encoding specified (stream encoding not supported)
How to fix Invalid encoding error in Node.js readable streams
Error: EINVAL: invalid argument, open
EINVAL: invalid argument, open
TypeError: readableLength must be a positive integer (stream config)
TypeError: readableLength must be a positive integer in Node.js streams