Node.js Error

Error: read ECONNRESET

error code: ECONNRESET  ·  also: Error: socket hang up  ·  Error: write ECONNRESET

Complete reference — what it means, why it happens with keep-alive (including the Node.js 19 change), axios, database pools, and how to fix it with retries, timeouts, and server tuning.

Quick Answer: Error: read ECONNRESET means the remote server closed the TCP connection while your code was reading or writing. The most common cause is a stale keep-alive socket — catch the error and retry the request once with a fresh connection. For persistent issues, add retry logic with exponential backoff, tune server.keepAliveTimeout above your load balancer's idle timeout, and set a socket timeout with AbortSignal.timeout().

What is ECONNRESET?

ECONNRESET stands for Error CONNection RESET — a POSIX system error thrown when the remote end of a TCP connection sends a RST packet, abruptly terminating the socket. Unlike a graceful close (FIN), a reset discards any data in flight and gives the local side no chance to finish reading or writing.

Node.js surfaces this as an Error object with code: 'ECONNRESET' on HTTP requests, raw net.Socket streams, database connections, and any other TCP-based API. At the HTTP layer it also appears as Error: socket hang up — both errors share the same root cause.

Exact error strings you will see in the terminal:
Error: read ECONNRESET
Error: socket hang up
Error: write ECONNRESET
Error: connect ECONNRESET <ip>:<port>

Full Error Example

Error: read ECONNRESET
    at TLSWrap.onStreamRead (node:internal/stream_base_commons:217:20)
    at TLSWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  errno: -104,
  code: 'ECONNRESET',
  syscall: 'read'
}
Error: socket hang up
    at connResetException (node:internal/errors:711:14)
    at Socket.socketOnEnd (node:_http_client:518:23)
    at Socket.emit (node:events:529:35) {
  code: 'ECONNRESET'
}

The syscall field tells you which operation was interrupted: 'read' (receiving data), 'write' (sending data), or 'connect' (initial handshake). This narrows down whether the problem is during connection establishment or mid-transfer. The socket hang up variant does not expose a syscall but always carries code: 'ECONNRESET'.

Common Causes

CauseWhy it happens
Stale keep-alive socket The HTTP Agent reused a socket that the server had already closed. The RST arrives the moment Node.js tries to read the response. Race condition most visible with keepAlive: true.
Node.js 19+ default keep-alive (5 s timeout) Since Node.js 19, the HTTP server enables keep-alive by default with a 5-second keepAliveTimeout. Clients that reuse a socket after this window expires receive ECONNRESET. The fix is setting keepAliveTimeout higher than the load balancer idle timeout.
Server crash or restart The remote process died mid-request, causing the OS to send RST packets for all open connections.
Load balancer or proxy timeout A reverse proxy (nginx, AWS ALB) closed an idle or slow connection before Node.js finished sending the request body. AWS ALB default idle timeout is 60 seconds.
Firewall dropping idle connections Stateful firewalls silently drop TCP connections that exceed an idle timeout, causing the next read/write to fail.
Server-side rate limiting The server resets connections when a client exceeds request limits instead of returning a proper HTTP error code.
TLS/SSL handshake failure Certificate mismatch, unsupported cipher, or protocol version mismatch causes the TLS peer to reset the connection during the handshake.
Database pool idle socket A pooled database connection (MySQL, PostgreSQL, MongoDB) sat idle long enough for a firewall or the database server to expire it. The next query receives ECONNRESET from the dead socket.
server.maxConnections limit hit When server.maxConnections is set too low and the connection limit is reached, the server resets incoming connections instead of queuing them.
VPN or proxy misconfiguration A VPN that is required for the target endpoint is not active, or the VPN itself enforces connection time limits that reset active connections.

Fix 1 – Retry on ECONNRESET (keep-alive race condition)

The most common cause is a race between the server closing a keep-alive socket and Node.js reusing it from the pool. The fix is to catch ECONNRESET and retry the request once with a fresh socket.

async function fetchWithRetry(url, options = {}, retries = 1) {
  try {
    return await fetch(url, options);
  } catch (err) {
    if (err.code === 'ECONNRESET' && retries > 0) {
      console.warn('ECONNRESET – retrying request...');
      return fetchWithRetry(url, options, retries - 1);
    }
    throw err;
  }
}

// Usage
const res = await fetchWithRetry('https://api.example.com/data');
const data = await res.json();

Fix 2 – Retry with exponential backoff

For production code making many outbound requests, use a full retry strategy that handles transient network errors and backs off to avoid overwhelming the server.

const RETRYABLE_CODES = new Set(['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND']);

async function fetchWithBackoff(url, options = {}, { retries = 3, baseDelayMs = 200 } = {}) {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      const res = await fetch(url, options);
      if (res.ok) return res;
      // Retry on 5xx server errors
      if (res.status >= 500 && attempt < retries) {
        await sleep(baseDelayMs * 2 ** attempt);
        continue;
      }
      return res; // Return non-retryable HTTP errors as-is
    } catch (err) {
      if (RETRYABLE_CODES.has(err.code) && attempt < retries) {
        await sleep(baseDelayMs * 2 ** attempt);
        continue;
      }
      throw err;
    }
  }
}

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

Fix 3 – Set a socket timeout

Without a timeout, a connection reset mid-response can leave your code waiting indefinitely. Set AbortSignal.timeout() (Node.js 17.3+) or a manual timeout on the socket to fail fast.

// Using AbortSignal.timeout (Node.js 17.3+, recommended)
const res = await fetch('https://api.example.com/data', {
  signal: AbortSignal.timeout(5000), // 5 second total timeout
});

// Using http/https request timeout (older Node.js)
const http = require('http');

const req = http.get('http://api.example.com/data', (res) => {
  res.resume(); // consume the response
});

req.setTimeout(5000, () => {
  req.destroy(new Error('Request timed out'));
});

req.on('error', (err) => {
  if (err.code === 'ECONNRESET' || err.message === 'Request timed out') {
    console.error('Request failed:', err.message);
  }
});

Fix 4 – Handle ECONNRESET on keep-alive agents

When using a custom http.Agent with keepAlive: true, attach an error listener to the socket to prevent unhandled errors from crashing the process when idle sockets are reset.

const http = require('http');

const agent = new http.Agent({ keepAlive: true });

function request(options) {
  return new Promise((resolve, reject) => {
    const req = http.request({ ...options, agent }, resolve);

    req.on('socket', (socket) => {
      socket.on('error', (err) => {
        if (err.code === 'ECONNRESET') {
          // Socket was reset; retry without keep-alive
          req.destroy();
          http.request({ ...options, agent: false }, resolve).end();
        }
      });
    });

    req.on('error', (err) => {
      if (err.code === 'ECONNRESET') {
        // Retry once with a new connection
        http.request({ ...options, agent: false }, resolve)
          .on('error', reject)
          .end();
      } else {
        reject(err);
      }
    });

    req.end();
  });
}

Fix 5 – Tune keep-alive timeouts (Node.js 19+ / AWS ALB)

If you control the server, set its keep-alive timeout to be longer than your load balancer's idle timeout. This prevents the server from closing sockets that the load balancer still considers open. This is especially important in Node.js 19+ where keepAliveTimeout defaults to only 5 seconds.

const http = require('http');
const app = require('./app'); // your Express/Fastify app

const server = http.createServer(app);

// Set server keep-alive timeout longer than your load balancer's idle timeout.
// AWS ALB default idle timeout is 60 s — set this to 65 s or higher.
server.keepAliveTimeout = 65_000;
server.headersTimeout = 66_000; // must be > keepAliveTimeout

server.listen(3000);
AWS ALB / nginx tip: The default ALB idle timeout is 60 seconds. If your Node.js keepAliveTimeout is lower than that (Node.js 19+ default is 5 s, earlier versions close immediately), the server closes the socket before the load balancer does, causing ECONNRESET on the next request routed through that socket. Always set keepAliveTimeout > load balancer idle timeout and set headersTimeout slightly higher than keepAliveTimeout.

Fix 6 – Axios retry interceptor and agent tuning

When using axios, configure the HTTP agent's freeSocketTimeout to be shorter than the server's keep-alive timeout so stale sockets are discarded before reuse. Add a request interceptor to retry on ECONNRESET.

const axios = require('axios');
const http = require('http');
const https = require('https');

// Agents with freeSocketTimeout shorter than server keepAliveTimeout
const httpAgent  = new http.Agent({ keepAlive: true, freeSocketTimeout: 4000 });
const httpsAgent = new https.Agent({ keepAlive: true, freeSocketTimeout: 4000 });

const client = axios.create({ httpAgent, httpsAgent });

// Retry interceptor
client.interceptors.response.use(undefined, async (err) => {
  const config = err.config;
  if (!config) throw err;

  config._retryCount = (config._retryCount || 0) + 1;

  if (err.code === 'ECONNRESET' && config._retryCount <= 3) {
    const delay = 200 * 2 ** (config._retryCount - 1); // exponential backoff
    await new Promise((r) => setTimeout(r, delay));
    return client(config);
  }

  throw err;
});

// Usage
const { data } = await client.get('https://api.example.com/data');

Fix 7 – Database connection pool ECONNRESET (MySQL, PostgreSQL, MongoDB)

Database drivers reuse pooled connections. If a firewall or the database server closes an idle pooled socket, the next query receives ECONNRESET. Enable TCP keep-alive on pool connections and configure reconnection.

MySQL2

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  database: 'mydb',
  waitForConnections: true,
  connectionLimit: 10,
  enableKeepAlive: true,          // send TCP keep-alive packets
  keepAliveInitialDelay: 10000,   // start keep-alives after 10 s of idle
});

// Handle pool-level errors (prevents unhandled ECONNRESET crashes)
pool.on('error', (err) => {
  console.error('MySQL pool error:', err.code, err.message);
});

PostgreSQL (pg)

const { Pool } = require('pg');

const pool = new Pool({
  host: 'localhost',
  database: 'mydb',
  keepAlives: true,               // enable TCP keep-alives
  keepAlivesInitialDelay: 10,     // seconds before first keep-alive probe
  connectionTimeoutMillis: 5000,
  idleTimeoutMillis: 30000,       // release idle connections before firewall kills them
});

pool.on('error', (err) => {
  console.error('PostgreSQL pool error:', err.code);
});

MongoDB (Mongoose)

const mongoose = require('mongoose');

await mongoose.connect('mongodb://localhost:27017/mydb', {
  socketTimeoutMS: 45000,         // give up queries after 45 s
  heartbeatFrequencyMS: 10000,    // monitor server health every 10 s
  serverSelectionTimeoutMS: 5000, // fail fast if no server reachable
});

Fix 8 – Handle ECONNRESET on streams

When piping or reading from an HTTP response stream, a connection reset causes an 'error' event on the stream. Always attach an error handler.

const https = require('https');
const fs = require('fs');

https.get('https://example.com/large-file.zip', (res) => {
  const out = fs.createWriteStream('file.zip');

  res.on('error', (err) => {
    if (err.code === 'ECONNRESET') {
      console.error('Download interrupted, retry logic here');
      out.close();
    }
  });

  res.pipe(out);
}).on('error', (err) => {
  console.error('Request error:', err.code, err.message);
});

Fix 9 – Disable keep-alive (Node.js 19+ test environments)

In test environments where connections are expected to close immediately, the Node.js 19+ default keep-alive causes unexpected ECONNRESET errors. Disable it explicitly:

const http = require('http');

// Option A: disable keep-alive on server creation
const server = http.createServer({ keepAlive: false }, app);

// Option B: send Connection: close header on each response
app.use((req, res, next) => {
  res.setHeader('Connection', 'close');
  next();
});

Fix 10 – Circuit breaker for repeated ECONNRESET

When a remote service is persistently unreachable or resetting, a circuit breaker prevents cascading failures by stopping requests after a failure threshold is reached.

class CircuitBreaker {
  constructor(threshold = 5, resetAfterMs = 30_000) {
    this.failures  = 0;
    this.threshold = threshold;
    this.state     = 'closed'; // closed = normal, open = blocking requests
    this.resetAfterMs = resetAfterMs;
  }

  async call(fn) {
    if (this.state === 'open') {
      throw new Error('Circuit breaker is open — service unavailable');
    }
    try {
      const result = await fn();
      this.failures = 0; // reset on success
      return result;
    } catch (err) {
      if (err.code === 'ECONNRESET' || err.code === 'ECONNREFUSED') {
        this.failures++;
        if (this.failures >= this.threshold) {
          this.state = 'open';
          setTimeout(() => {
            this.state  = 'closed';
            this.failures = 0;
          }, this.resetAfterMs);
        }
      }
      throw err;
    }
  }
}

const breaker = new CircuitBreaker();
const data = await breaker.call(() => fetch('https://api.example.com/data'));

Debugging Checklist

  1. Check err.syscall: 'read' = reset while receiving, 'write' = reset while sending, 'connect' = reset during handshake.
  2. Check server logs for crashes, OOM kills, or intentional connection limits around the time of the error.
  3. Verify your keepAliveTimeout is higher than your load balancer's idle timeout (AWS ALB: 60 s).
  4. Confirm server.maxConnections is not set to a value too low for your traffic.
  5. If using a proxy or VPN, check whether it enforces connection time limits.
  6. Enable NODE_DEBUG=net,http to trace socket lifecycle events and see exactly when the reset occurs.
  7. Use tcpdump or Wireshark to confirm a RST packet is actually arriving from the remote host.
  8. For database ECONNRESET: check the database server's wait_timeout (MySQL) or tcp_keepalives_idle (PostgreSQL) settings.
# Enable Node.js network debug output
NODE_DEBUG=net,http node app.js 2>&1 | grep -E "(ECONNRESET|RST|socket)"
Do not silently swallow ECONNRESET: Catching the error without retrying or logging it hides connectivity problems that may indicate a misconfigured server, failing infrastructure, or a security-relevant connection termination. Always log the error and consider whether a retry is safe for your use case (idempotent GET requests: safe to retry; POST/PUT requests that mutate state: only retry if the server guarantees idempotency or you have deduplication).

Frequently Asked Questions

What is ECONNRESET in Node.js?

ECONNRESET stands for Error CONNection RESET. Node.js throws it as Error: read ECONNRESET or Error: socket hang up when the remote server sends a TCP RST packet, forcibly terminating an in-progress connection. It can occur on any socket operation: HTTP/HTTPS requests, raw TCP clients, database connections, or streaming downloads.

What is the difference between "Error: read ECONNRESET" and "Error: socket hang up"?

Error: read ECONNRESET is the raw OS-level error emitted at the net or tls layer when the kernel receives a TCP RST during a read syscall. Error: socket hang up is a higher-level Node.js HTTP message emitted by the http/https module when the socket closes before a complete HTTP response was received. Both carry code: 'ECONNRESET' and share the same root cause — the remote peer forcibly closed the connection.

Why does ECONNRESET happen with HTTP keep-alive?

When using an HTTP Agent with keepAlive: true, the client reuses sockets from a pool. The server may close an idle socket at any time (after its keepAliveTimeout expires). If Node.js picks up that socket just as the server closes it, you get ECONNRESET. This race condition became more common in Node.js 19+ which enabled keep-alive by default on both server and client. Fix by catching ECONNRESET and retrying once, or by setting the agent's freeSocketTimeout lower than the server's keepAliveTimeout.

What changed in Node.js 19 that causes more ECONNRESET errors?

Starting in Node.js 19, the HTTP server enables Keep-Alive by default with a 5-second keepAliveTimeout. Before Node.js 19, connections closed immediately after the response. This change means clients reusing a socket after the 5-second window expires will receive ECONNRESET. The fix is to set server.keepAliveTimeout higher than your load balancer's idle timeout (e.g., 65000 ms for AWS ALB with its 60-second default), or set it to 0 to disable keep-alive entirely in environments where it causes problems.

How do I fix ECONNRESET with axios in Node.js?

Configure an axios instance with custom HTTP/HTTPS agents that have keepAlive: true and freeSocketTimeout set lower than the server's keep-alive timeout (e.g., 4000 ms). Add a response interceptor that catches errors where err.code === 'ECONNRESET' and retries up to 3 times with exponential backoff. Do not retry non-idempotent requests automatically without server-side deduplication.

Why does ECONNRESET happen on database connections (MySQL, PostgreSQL, MongoDB)?

Database drivers maintain connection pools. If a pooled connection sits idle long enough for a firewall or the database server to close it (e.g., MySQL's wait_timeout, typically 8 hours but often 60–600 seconds in cloud environments), the next query receives ECONNRESET from the dead socket. Fix by enabling TCP keep-alive on pool connections (enableKeepAlive: true for MySQL2, keepAlives: true for pg), setting a shorter idleTimeoutMillis than the server's timeout, and always attaching a pool.on('error', ...) handler to prevent unhandled ECONNRESET crashes.

What is the difference between ECONNRESET and ECONNREFUSED?

ECONNRESET means the connection was established but then forcibly closed mid-session by the remote peer (TCP RST packet received during an active connection). ECONNREFUSED means the connection attempt was rejected immediately — nothing is listening on that port (the OS returned a RST in response to the SYN, or the port is actively refused).

Related Errors