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.
Error: connect ETIMEDOUT 93.184.216.34:443Error: ETIMEDOUTError: read ETIMEDOUTFetchError: 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:
'connect'— the TCP three-way handshake never completed. The host is unreachable, the port is blocked by a firewall, or the server is too overloaded to accept new connections.'read'— the connection was established but the server stopped sending data before the response completed.'lookup'(or nosyscall) — DNS resolution itself timed out before a TCP connection was even attempted.
Common Causes
| Cause | Why 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) => {
// ...
});
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);
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 })))
);
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.
| Error | When it occurs | Common library |
|---|---|---|
ETIMEDOUT | TCP connect phase (SYN never acknowledged) | Node.js core, axios, got |
ESOCKETTIMEDOUT | After connection — inactivity during read | request, superagent |
TimeoutError | Total 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
- Check
err.syscall:'connect'= TCP handshake timed out (unreachable host / firewall),'read'= server connected but stopped responding. - Check
err.addressanderr.portto confirm you are hitting the right endpoint — a stale DNS cache can resolve a hostname to a wrong or unreachable IP. - Does the URL work in a browser but not Node.js? Check proxy configuration, add a
User-Agentheader, and force IPv4 resolution. - Run
dns.lookup(hostname)ornslookup hostnamefrom the same machine to verify DNS resolves correctly. Note the address family (IPv4 vs IPv6). - Try
curl --connect-timeout 5 https://your-endpoint/ornc -zv host portfrom the same host to test raw TCP reachability outside Node.js. - Check whether a firewall, security group, or VPN is silently dropping packets — a connect timeout without a RST is the classic symptom.
- For database ETIMEDOUT, verify the pool is not exhausted: check active connection counts against
max_connections(MySQL) ormax_connections(PostgreSQL). - If the timeout is intermittent, check server-side metrics (CPU, memory, connection pool saturation) around the time of failure.
- Enable
NODE_DEBUG=net,httpto 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)"
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-agentexplicitly. - 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+) orfamily: 4on 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: 10000andenableKeepAlive: trueon the pool. - PostgreSQL (pg): set
connectionTimeoutMillis,idleTimeoutMillis, andquery_timeouton thePool. - MongoDB/Mongoose: configure
serverSelectionTimeoutMS,connectTimeoutMS, andsocketTimeoutMS. - Redis (ioredis): set
connectTimeout,commandTimeout, and implement aretryStrategy.
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
ECONNRESET: connection reset by peer— the remote host actively sent a TCP RST packet to close the connection, as opposed to silently timing outECONNREFUSED: connection refused— the remote host actively rejected the connection; nothing is listening on that portENOTFOUND: getaddrinfo ENOTFOUND— DNS resolution failed entirely before a TCP connection could be attemptedEAI_AGAIN: getaddrinfo EAI_AGAIN— temporary DNS failure; the resolver is unreachable or returned a SERVFAILESOCKETTIMEDOUT— thrown by some third-party HTTP client libraries (e.g.request) as a higher-level wrapper around an underlying socket inactivity timeout