Node.js Error

Error: connect ETIMEDOUT

error code: ETIMEDOUT

Complete reference — what it means, why it happens (including why it works in browser but not Node.js), and how to fix it with explicit timeouts, proxy config, IPv4 forcing, Axios, database pool settings, retry logic, and circuit breakers.

Quick Answer: Error: connect ETIMEDOUT means a TCP connection or read operation did not complete within the timeout window — the remote host went silent rather than actively refusing. Set an explicit timeout with AbortSignal.timeout() (Node.js 17.3+), the Axios timeout option, or req.setTimeout(). If the URL works in the browser but not Node.js, check proxy settings, add a User-Agent header, and force IPv4 resolution. Check err.syscall to tell a connect timeout from a read timeout, then add retry logic with exponential backoff.

What is ETIMEDOUT?

ETIMEDOUT stands for Error TIMed OUT — a POSIX system error thrown when a network operation (TCP connect, read, or write) does not complete within the configured timeout window. The remote host never sends a response; the local timer simply fires and Node.js surfaces the error.

Unlike ECONNRESET, where the peer actively sends a RST packet, ETIMEDOUT is characterised by silence — SYN packets go unanswered, or the server stops sending data mid-response. This makes it common in firewall-drop scenarios, proxy misconfigurations, and with overloaded or unreachable servers.

Exact error messages you will see:
Error: connect ETIMEDOUT 93.184.216.34:443
Error: ETIMEDOUT
Error: read ETIMEDOUT
FetchError: network timeout at: https://...
ESOCKETTIMEDOUT (thrown by some third-party HTTP libraries)

Full Error Example

Error: connect ETIMEDOUT 93.184.216.34:443
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16)
    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  errno: -110,
  code: 'ETIMEDOUT',
  syscall: 'connect',
  address: '93.184.216.34',
  port: 443
}

The syscall field is the most important diagnostic property:

Common Causes

CauseWhy it happens
Remote server is slow or overloaded The server accepts the TCP connection but takes too long to compute and send the response, exceeding the read timeout window.
Firewall silently dropping packets A stateful firewall or security group drops SYN packets without sending a RST, so the connect attempt hangs until the OS timer fires. This produces syscall: 'connect'.
No internet connectivity / wrong DNS If the resolved IP is unreachable (e.g. wrong DNS record, VPN misconfiguration, or no default route), every connect attempt will time out silently.
Missing proxy configuration Browsers automatically use system-wide proxy settings; Node.js does not. Behind a corporate network or VPN, every outbound request from Node.js may silently time out unless a proxy agent is configured explicitly.
IPv4 vs IPv6 mismatch Node.js may resolve a hostname to an IPv6 address while the target server only listens on IPv4. The IPv6 connect attempt silently times out. Force IPv4 resolution to fix this.
Missing User-Agent header Some servers and CDNs reject or silently drop requests that lack a User-Agent header, treating them as bots. Browsers send this header automatically; Node.js does not.
Default timeout too short for slow API Some http/https agents or libraries set a very short default socket timeout. A legitimate but slow third-party API will regularly trigger ETIMEDOUT if the timeout is shorter than the API's 95th-percentile response time.
Database connection pool exhaustion When all connections in a MySQL, PostgreSQL, or MongoDB pool are busy, new queries queue up. If the queue wait exceeds the configured timeout, ETIMEDOUT is thrown for the connection attempt.
Large request body with slow upload Uploading a large file over a slow connection can exceed a read timeout on the server side, which then closes the connection — or exceed a write timeout on the client side.
OS file descriptor limit exceeded At high concurrency, Node.js may exhaust the OS file descriptor limit (default ~1,024 on Linux). New socket opens fail silently, producing ETIMEDOUT on connection attempts.
DNS resolution timeout (EAI_AGAIN) When the DNS resolver itself is unreachable or slow, the lookup times out before a TCP connection is attempted. Often surfaces as ENOTFOUND or EAI_AGAIN rather than ETIMEDOUT, but both share the same root cause: the hostname cannot be resolved in time.

Fix 1 – Check proxy configuration (browser works, Node.js does not)

If a URL loads fine in your browser but Error: connect ETIMEDOUT appears in Node.js, the most common cause is a missing proxy configuration. Browsers automatically inherit system-level proxy settings; Node.js does not. Install proxy-agent and pass it as the agent option to route requests through the system proxy.

// npm install proxy-agent
const { ProxyAgent } = require('proxy-agent');

const agent = new ProxyAgent(); // reads HTTP_PROXY / HTTPS_PROXY env vars automatically

// With fetch (Node.js 18+)
const res = await fetch('https://api.example.com/data', { agent });

// With https module
const https = require('https');
https.get({ hostname: 'api.example.com', path: '/data', agent }, (res) => {
  // ...
});
Also add a User-Agent header: Some CDNs and API gateways silently drop requests that lack a User-Agent header. Add headers: { 'User-Agent': 'MyApp/1.0' } to your request options to mimic a real client.

Fix 2 – Force IPv4 resolution to prevent IPv6 timeouts

Node.js may resolve a hostname to an AAAA (IPv6) record while the target server only listens on IPv4. The connect attempt to the IPv6 address times out silently. Force IPv4 by passing family: 4 to dns.lookup() or by setting the family option on the HTTP agent.

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

// Option A: force IPv4 at the dns.lookup level
dns.setDefaultResultOrder('ipv4first'); // Node.js 17+

// Option B: set family on the https agent
const agent = new https.Agent({ family: 4 });

const res = await fetch('https://api.example.com/data', { agent });

// Option C: resolve manually and connect to the IP directly
const { promisify } = require('util');
const lookup = promisify(dns.lookup);
const { address } = await lookup('api.example.com', { family: 4 });
console.log('Resolved to IPv4:', address);

Fix 3 – Set explicit timeouts with AbortSignal.timeout() (Node.js 17.3+)

The cleanest way to enforce a timeout on fetch() is AbortSignal.timeout(ms), available since Node.js 17.3. It throws a TimeoutError (a subclass of DOMException) that is distinct from other network errors, so you can handle it separately.

// Node.js 17.3+ — recommended approach
async function fetchWithTimeout(url, timeoutMs = 5000) {
  try {
    const res = await fetch(url, {
      signal: AbortSignal.timeout(timeoutMs),
      headers: { 'User-Agent': 'MyApp/1.0' }, // prevent bot-blocking
    });
    return await res.json();
  } catch (err) {
    if (err.name === 'TimeoutError') {
      // AbortSignal.timeout() fired — request took too long
      console.error(`Request to ${url} timed out after ${timeoutMs}ms`);
    } else if (err.code === 'ETIMEDOUT') {
      // Underlying TCP timeout surfaced through older path
      console.error('TCP timeout:', err.syscall, err.address);
    } else {
      throw err;
    }
  }
}

await fetchWithTimeout('https://api.example.com/data', 8000);
Node.js version note: AbortSignal.timeout() requires Node.js 17.3+. For older versions, use req.setTimeout() on the underlying http/https request (see Fix 4 below), or the AbortController + setTimeout pattern.

Fix 4 – Axios timeout configuration

Axios is one of the most widely used HTTP clients in Node.js. Set the timeout property (in milliseconds) in the Axios config to enforce a connect + read deadline. Catch the error and check error.code === 'ECONNABORTED' or axios.isAxiosError(error) to handle timeouts separately.

const axios = require('axios');

// Per-request timeout
async function axiosFetch(url) {
  try {
    const response = await axios.get(url, {
      timeout: 8000, // 8 seconds — covers both connect and read
      headers: { 'User-Agent': 'MyApp/1.0' },
    });
    return response.data;
  } catch (error) {
    if (error.code === 'ECONNABORTED') {
      console.error('Axios request timed out:', error.message);
    } else if (error.code === 'ETIMEDOUT') {
      console.error('TCP connect timeout:', error.message);
    } else {
      throw error;
    }
  }
}

// Global Axios defaults
axios.defaults.timeout = 10000;

// Per-instance defaults with axios-retry
const axiosRetry = require('axios-retry');
const client = axios.create({ timeout: 8000 });

axiosRetry(client, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) =>
    axiosRetry.isNetworkError(error) || error.code === 'ETIMEDOUT',
});

Fix 5 – Set a socket timeout on http/https requests

For the built-in http and https modules, call req.setTimeout(ms) to configure an inactivity timeout on the socket. The callback fires when no data is received for the specified duration; you must call req.destroy() yourself to actually abort the request.

const https = require('https');

function httpsGet(url, timeoutMs = 5000) {
  return new Promise((resolve, reject) => {
    const req = https.get(url, (res) => {
      const chunks = [];
      res.on('data', (chunk) => chunks.push(chunk));
      res.on('end', () => resolve(Buffer.concat(chunks).toString()));
      res.on('error', reject);
    });

    // Fire when no data received for timeoutMs
    req.setTimeout(timeoutMs, () => {
      req.destroy(new Error(`ETIMEDOUT: request timed out after ${timeoutMs}ms`));
    });

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

const body = await httpsGet('https://api.example.com/data', 8000);

Fix 6 – Database connection timeouts (MySQL, PostgreSQL, MongoDB, Redis)

ETIMEDOUT is common when connecting to remote databases. Use connection pooling and set explicit timeout values so your application fails fast and can retry rather than hanging indefinitely.

MySQL (mysql2)

const mysql = require('mysql2');

const pool = mysql.createPool({
  host: 'db.example.com',
  user: 'app_user',
  password: process.env.DB_PASS,
  database: 'mydb',
  connectionLimit: 10,
  connectTimeout: 10000,       // TCP connect timeout (ms)
  waitForConnections: true,
  queueLimit: 0,
  enableKeepAlive: true,
  keepAliveInitialDelay: 30000, // send TCP keepalive after 30s idle
});

// Always use the pool, not single connections
const promisePool = pool.promise();

try {
  const [rows] = await promisePool.query('SELECT 1');
} catch (err) {
  if (err.code === 'ETIMEDOUT') {
    console.error('MySQL connect timed out — check host, firewall, and max_connections');
  }
}

PostgreSQL (pg)

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

const pool = new Pool({
  host: 'db.example.com',
  port: 5432,
  database: 'mydb',
  user: 'app_user',
  password: process.env.DB_PASS,
  connectionTimeoutMillis: 10000, // fail fast if no connection available
  idleTimeoutMillis: 30000,       // release idle connections after 30s
  query_timeout: 20000,           // cancel queries running longer than 20s
  max: 10,
});

pool.on('error', (err) => {
  if (err.code === 'ETIMEDOUT') {
    console.error('PostgreSQL pool error — idle connection timed out');
  }
});

MongoDB / Mongoose

const mongoose = require('mongoose');

await mongoose.connect('mongodb://db.example.com:27017/mydb', {
  serverSelectionTimeoutMS: 5000,  // how long to wait for a server to be found
  connectTimeoutMS: 10000,         // TCP connect timeout
  socketTimeoutMS: 45000,          // socket inactivity timeout
  maxPoolSize: 10,
  minPoolSize: 2,
});

mongoose.connection.on('error', (err) => {
  if (err.name === 'MongoNetworkTimeoutError' || err.code === 'ETIMEDOUT') {
    console.error('MongoDB connection timed out');
  }
});

Redis (ioredis)

const Redis = require('ioredis');

const redis = new Redis({
  host: 'redis.example.com',
  port: 6379,
  connectTimeout: 10000,  // TCP connect timeout
  commandTimeout: 5000,   // per-command timeout
  retryStrategy(times) {
    if (times > 5) return null; // stop retrying
    return Math.min(times * 200, 2000); // exponential backoff up to 2s
  },
});

redis.on('error', (err) => {
  if (err.code === 'ETIMEDOUT') {
    console.error('Redis connection timed out');
  }
});

Fix 7 – Retry with exponential backoff on ETIMEDOUT

ETIMEDOUT is almost always transient. A robust production retry strategy should catch it (along with other transient codes) and back off before retrying to avoid overwhelming a struggling server.

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

async function fetchWithBackoff(url, options = {}, { retries = 3, baseDelayMs = 300 } = {}) {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      const res = await fetch(url, {
        ...options,
        signal: AbortSignal.timeout(10_000), // 10 s per attempt
        headers: { 'User-Agent': 'MyApp/1.0', ...options.headers },
      });
      if (res.ok) return res;
      // Retry on 5xx server errors
      if (res.status >= 500 && attempt < retries) {
        await sleep(baseDelayMs * 2 ** attempt);
        continue;
      }
      return res;
    } catch (err) {
      const isTimeout = err.name === 'TimeoutError' || RETRYABLE_CODES.has(err.code);
      if (isTimeout && attempt < retries) {
        const delay = baseDelayMs * 2 ** attempt;
        console.warn(`Attempt ${attempt + 1} failed (${err.code ?? err.name}), retrying in ${delay}ms…`);
        await sleep(delay);
        continue;
      }
      throw err;
    }
  }
}

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

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

Fix 8 – Connection pooling with Keep-Alive (high concurrency)

At high concurrency, creating a new TCP connection per request rapidly exhausts OS resources and produces ETIMEDOUT errors. Use HTTP Keep-Alive to reuse sockets, and cap the number of concurrent requests to prevent server-side rate limiting.

// npm install agentkeepalive
const { HttpsAgent } = require('agentkeepalive');

const agent = new HttpsAgent({
  keepAlive: true,
  maxSockets: 50,          // max simultaneous connections per origin
  maxFreeSockets: 10,      // idle sockets to keep alive
  timeout: 60000,          // active socket timeout
  freeSocketTimeout: 30000,// idle socket timeout before closing
  dnsCache: true,          // cache DNS results for 5 minutes
});

// Use with fetch (Node.js 18+)
const res = await fetch('https://api.example.com/data', { agent });

// Combine with p-limit to cap concurrency
// npm install p-limit
const pLimit = require('p-limit');
const limit = pLimit(20); // max 20 concurrent requests

const urls = ['https://api.example.com/1', 'https://api.example.com/2' /* ... */];
const results = await Promise.all(
  urls.map((url) => limit(() => fetch(url, { agent })))
);
OS file descriptor limit: On Linux the default soft limit is 1,024 file descriptors. Under high load this is exhausted quickly, causing new socket opens to fail with ETIMEDOUT. Raise the limit with ulimit -n 65536 for the current session, or add * soft nofile 65536 to /etc/security/limits.conf for a permanent change.

Fix 9 – Circuit breaker pattern to prevent cascading timeouts

When a dependency is degraded, continuing to send requests that all time out wastes resources and amplifies the outage. A circuit breaker opens after a threshold of failures and fast-fails subsequent calls until the dependency recovers.

class CircuitBreaker {
  constructor({ threshold = 5, resetTimeoutMs = 30000 } = {}) {
    this.threshold = threshold;
    this.resetTimeoutMs = resetTimeoutMs;
    this.failures = 0;
    this.state = 'CLOSED'; // CLOSED | OPEN | HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker OPEN — fast-failing request');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (err) {
      this.onFailure(err);
      throw err;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure(err) {
    if (err.code === 'ETIMEDOUT' || err.name === 'TimeoutError') {
      this.failures++;
      if (this.failures >= this.threshold) {
        this.state = 'OPEN';
        this.nextAttempt = Date.now() + this.resetTimeoutMs;
        console.error(`Circuit breaker opened after ${this.failures} timeouts`);
      }
    }
  }
}

const breaker = new CircuitBreaker({ threshold: 5, resetTimeoutMs: 30_000 });

const data = await breaker.call(() =>
  fetch('https://api.example.com/data', { signal: AbortSignal.timeout(5000) })
    .then((r) => r.json())
);

Fix 10 – Check DNS resolution with dns.lookup()

If you suspect the timeout is happening before the TCP connection even starts, verify that the hostname resolves correctly. A slow or broken DNS resolver will cause every outbound request to time out.

const dns = require('dns');
const { promisify } = require('util');

const lookup = promisify(dns.lookup);

async function checkDns(hostname) {
  try {
    const result = await lookup(hostname);
    console.log(`${hostname} resolves to ${result.address} (IPv${result.family})`);
    return result;
  } catch (err) {
    if (err.code === 'ENOTFOUND') {
      console.error(`DNS resolution failed: ${hostname} does not exist`);
    } else if (err.code === 'EAI_AGAIN') {
      console.error(`DNS resolution timed out for ${hostname} — check your DNS server`);
    } else {
      console.error('DNS error:', err.code, err.message);
    }
    throw err;
  }
}

// Diagnose before making the actual request
await checkDns('api.example.com');

// Force IPv4 to avoid IPv6 timeout issues
const { address: ipv4Address } = await lookup('api.example.com', { family: 4 });
console.log('IPv4 address:', ipv4Address);

// For finer control, use dns.resolve() to bypass the OS hosts file:
const resolve4 = promisify(dns.resolve4);
const addresses = await resolve4('api.example.com');
console.log('A records:', addresses);

Fix 11 – Distinguish connect timeout vs read timeout (err.syscall)

The err.syscall property tells you exactly where the timeout occurred, which points to different root causes and fixes.

async function fetchDiagnostic(url) {
  try {
    const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
    return res;
  } catch (err) {
    if (err.code === 'ETIMEDOUT') {
      switch (err.syscall) {
        case 'connect':
          // TCP handshake never completed.
          // Root causes: host unreachable, firewall dropping SYN packets,
          //              wrong IP from DNS, server not listening,
          //              missing proxy config, IPv6/IPv4 mismatch.
          console.error('Connect timeout — check firewall rules, routing, proxy, and DNS');
          console.error('  address:', err.address, 'port:', err.port);
          break;

        case 'read':
          // Connection succeeded but server stopped sending data.
          // Root causes: overloaded server, slow database query,
          //              server-side timeout shorter than client timeout.
          console.error('Read timeout — server connected but did not respond in time');
          break;

        default:
          console.error('Timeout during syscall:', err.syscall);
      }
    } else if (err.name === 'TimeoutError') {
      // AbortSignal.timeout() fired — total wall-clock limit reached
      console.error('AbortSignal timeout — increase the timeout or investigate server latency');
    }
    throw err;
  }
}

ETIMEDOUT vs ESOCKETTIMEDOUT

ETIMEDOUT is a system-level error thrown during the initial TCP handshake — the three-way SYN / SYN-ACK / ACK sequence never completed within the OS timeout. ESOCKETTIMEDOUT is a higher-level error thrown by some HTTP client libraries (e.g. the legacy request package) after a successful TCP connection is established, when the server fails to send a complete response within the inactivity timeout window.

ErrorWhen it occursCommon library
ETIMEDOUTTCP connect phase (SYN never acknowledged)Node.js core, axios, got
ESOCKETTIMEDOUTAfter connection — inactivity during readrequest, superagent
TimeoutErrorTotal wall-clock deadline (AbortSignal)fetch, got, ky

To handle all three in one place:

function isTimeoutError(err) {
  return (
    err.code === 'ETIMEDOUT' ||
    err.code === 'ESOCKETTIMEDOUT' ||
    err.code === 'ECONNABORTED' || // axios timeout
    err.name === 'TimeoutError'    // AbortSignal
  );
}

Debugging Checklist

  1. Check err.syscall: 'connect' = TCP handshake timed out (unreachable host / firewall), 'read' = server connected but stopped responding.
  2. Check err.address and err.port to confirm you are hitting the right endpoint — a stale DNS cache can resolve a hostname to a wrong or unreachable IP.
  3. Does the URL work in a browser but not Node.js? Check proxy configuration, add a User-Agent header, and force IPv4 resolution.
  4. Run dns.lookup(hostname) or nslookup hostname from the same machine to verify DNS resolves correctly. Note the address family (IPv4 vs IPv6).
  5. Try curl --connect-timeout 5 https://your-endpoint/ or nc -zv host port from the same host to test raw TCP reachability outside Node.js.
  6. Check whether a firewall, security group, or VPN is silently dropping packets — a connect timeout without a RST is the classic symptom.
  7. For database ETIMEDOUT, verify the pool is not exhausted: check active connection counts against max_connections (MySQL) or max_connections (PostgreSQL).
  8. If the timeout is intermittent, check server-side metrics (CPU, memory, connection pool saturation) around the time of failure.
  9. Enable NODE_DEBUG=net,http to trace socket lifecycle events and pinpoint when the timer fires relative to the connection state.
# Test TCP reachability directly (Unix/macOS/Linux)
nc -zv api.example.com 443

# Test with curl and explicit connect/max-time limits
curl --connect-timeout 5 --max-time 10 -v https://api.example.com/health

# Diagnose IPv4 vs IPv6 resolution
nslookup api.example.com
dig api.example.com AAAA  # check for IPv6 records

# Check OS file descriptor limit (Linux)
ulimit -n
# Raise it temporarily
ulimit -n 65536

# Enable Node.js network debug output
NODE_DEBUG=net,http node app.js 2>&1 | grep -E "(ETIMEDOUT|timeout|connect)"
Never set timeouts to zero or a very large value in production: A zero timeout means no timeout, which can cause your Node.js process to hang indefinitely on a hung connection — exhausting connection pool slots and eventually bringing down your service. Always set a realistic upper bound matched to your SLA, and let the retry layer handle transient failures.

Frequently Asked Questions

What is ETIMEDOUT in Node.js?

ETIMEDOUT stands for "Error TIMed OUT." Node.js throws it as Error: connect ETIMEDOUT, Error: read ETIMEDOUT, or FetchError: network timeout when a TCP connect, read, or write operation does not complete within the configured timeout window. Unlike ECONNRESET, the remote host never sends a RST packet — the connection simply goes silent until the local timer fires.

Why does connect ETIMEDOUT happen in Node.js but not in the browser?

There are four main reasons the same URL works in a browser but produces Error: connect ETIMEDOUT in Node.js:

  • Proxy: Browsers automatically use system proxies; Node.js does not — configure proxy-agent explicitly.
  • User-Agent header: Browsers send one automatically; Node.js sends none — servers may silently drop headless requests.
  • IPv4 vs IPv6: Node.js may resolve the hostname to an IPv6 address while the server only supports IPv4. Force IPv4 with dns.setDefaultResultOrder('ipv4first') (Node.js 17+) or family: 4 on the agent.
  • Firewall rules: Some firewalls whitelist browser processes but block background Node.js processes from making outbound connections.
What is the difference between ETIMEDOUT and ECONNRESET?

ETIMEDOUT means no response was received within the timeout window — the connection went silent. ECONNRESET means the connection was actively terminated by the remote peer, which sent a TCP RST packet. ETIMEDOUT is often caused by firewalls silently dropping packets or an overloaded server, while ECONNRESET is an active rejection or premature close.

What is the difference between ETIMEDOUT and ESOCKETTIMEDOUT?

ETIMEDOUT is a system-level error thrown during the initial TCP handshake — the three-way connection setup never completed. ESOCKETTIMEDOUT is a higher-level error thrown by some HTTP client libraries (like the legacy request package) after a successful TCP connection when the server fails to send a complete response within the inactivity timeout. Both indicate a timeout but at different stages of the connection lifecycle.

How do I fix ETIMEDOUT when connecting to MySQL, PostgreSQL, or MongoDB?

Always use connection pooling and set explicit timeout values:

  • MySQL (mysql2): set connectTimeout: 10000 and enableKeepAlive: true on the pool.
  • PostgreSQL (pg): set connectionTimeoutMillis, idleTimeoutMillis, and query_timeout on the Pool.
  • MongoDB/Mongoose: configure serverSelectionTimeoutMS, connectTimeoutMS, and socketTimeoutMS.
  • Redis (ioredis): set connectTimeout, commandTimeout, and implement a retryStrategy.

Also verify the database server is reachable from the Node.js host, that firewall rules allow the connection on the correct port, and that the maximum connection count has not been exceeded.

How do I tell if ETIMEDOUT is a connect timeout or a read timeout?

Check the err.syscall property on the error object. A value of 'connect' means the TCP handshake itself timed out — the host is unreachable or a firewall is silently dropping SYN packets. A value of 'read' means the connection was established but the server took too long to send data back. Connect timeouts point to network/firewall/DNS/proxy issues; read timeouts point to slow or overloaded servers.

How do I add retry logic for ETIMEDOUT in Node.js?

Wrap requests in a loop that catches ETIMEDOUT (and related codes: ECONNRESET, EAI_AGAIN, TimeoutError) and waits an exponentially increasing delay before each retry. For Axios, use the axios-retry package. Cap the total retries (typically 3–5) and use jitter to avoid thundering herd when many clients retry simultaneously. Always log each retry attempt for observability.

Related Errors