
Node.js developers frequently encounter the dreaded ETIMEDOUT Connection timed out error, which manifests as:
Error: ETIMEDOUT
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1134:16)
This “Connection timed out” exception occurs when a network socket fails to establish a connection within the allotted timeframe. You might see it in:
- HTTP clients (e.g.,
http.request
,fetch
, Axios) - Database drivers (MongoDB, MySQL)
- SMTP/email modules (Nodemailer)
- Package managers (
npm install
,yarn add
)
Why it matters:
- Failed API calls break user journeys (e.g., data fetch halts UI updates).
- Delayed or missing emails hamper communication workflows.
- Hanging npm installs delay CI/CD pipelines.
In production environments—even a handful of timed‑out requests can cascade into degraded performance, poor user experience, or worse: data inconsistency. Understanding the root causes and implementing best practices is critical for resilient, fault‑tolerant applications.
What Causes ETIMEDOUT in Node.js
Below are the major root causes of ETIMEDOUT in Node.js, drawn from community posts on Reddit, Stack Overflow, and official discussions:
- Broken Happy Eyeballs (Node v20+)
New network family auto‑selection can stall IPv6‑first attempts if IPv6 is unreachable before falling back to IPv4.- Reports on GitHub Issue #54359 and Reddit threads detail delays in
.connect()
called under the hood.
- Reports on GitHub Issue #54359 and Reddit threads detail delays in
- Missing Keep‑Alive / Default Agent Settings
By default, each HTTP/HTTPS request opens a new TCP socket. High overhead and slow handshake increases timeout risk. - Parallel Requests / Thread Pool Limitations
DNS resolution, file I/O, and crypto operations share a limited libuv thread pool (UV_THREADPOOL_SIZE
, default 4 threads), causing queuing. - Proxy Misconfiguration / DNS Issues
Corporate proxies, stale DNS caches, or IPv6 cut‑overs can delay lookups or block traffic entirely. - Low Timeout Settings
Unintentionally shorttimeout
,keepAliveTimeout
, orheadersTimeout
values can trigger timeouts before connection stabilization.
Step-by-Step Troubleshooting & Fixes
A. Happy Eyeballs in Node v20+
Problem: Node v20+ attempts IPv6 first, waits for default 10s before falling back to IPv4.
Fixes:
# Disable auto‑selection and force IPv4-first fallback
node --no-network-family-autoselection index.js
# Or tune timeouts for selection
node \
--network-family-autoselection-attempt-timeout=5000 \
--dns-result-order=ipv6first \
index.js
Why it works:
--no-network-family-autoselection
restores legacy fallback behavior.- Reducing
autoselection-attempt-timeout
shortens IPv6 wait.
B. Enable HTTP Keep‑Alive
Problem: New socket per request → handshake latency → ETIMEDOUT.
Solution:
import http from 'http';
import https from 'https';
// Custom agent
const keepAliveAgent = new http.Agent({
keepAlive: true,
keepAliveMsecs: 15000,
maxSockets: 100,
});
// Apply globally
http.globalAgent = keepAliveAgent;
https.globalAgent = new https.Agent({ keepAlive: true });
Benefit:
- Reuses TCP connections, reducing handshake cost.
- Improves throughput and lowers CPU usage.
C. Increase UV_THREADPOOL_SIZE
Problem: DNS resolutions and crypto calls queue behind limited threads.
Solution:
# Increase thread pool to 128
export UV_THREADPOOL_SIZE=128
node app.js
Effect:
- More concurrent background tasks (e.g.,
dns.lookup()
).- Reduces queuing delays that can exacerbate socket timeouts.
D. Adjust Timeout Settings
Problem: Client timeout < network latency.
Solution:
// Axios example
import axios from 'axios';
const client = axios.create({
timeout: 10000, // 10s
httpAgent: keepAliveAgent, // from previous step
});
// Server-side (Express)
const server = http.createServer(app);
server.keepAliveTimeout = 20000; // 20s
server.headersTimeout = 30000; // 30s
Tip: Align client timeouts slightly below server keep‑alive to avoid unexpected closures.
E. Configure Proxy & DNS
- Remove broken proxy: bashCopyEdit
npm config delete proxy npm config delete https-proxy
- Force IPv4 DNS resolution: jsCopyEdit
import dns from 'dns'; dns.setDefaultResultOrder('ipv4first');
Reason:
- Corporate npm proxies often block certain hosts.
- IPv6‑only DNS entries without reachable IPv6 can stall lookups.
F. Add Retry Logic with Exponential Backoff
Problem: Intermittent network blips cause single-request failures.
Solution:
import retry from 'async-retry';
import fetch from 'node-fetch';
async function fetchWithRetry(url) {
return retry(async bail => {
const res = await fetch(url, { timeout: 5000 });
if (!res.ok) {
// Do not retry on 4xx
if (res.status >= 400 && res.status < 500) bail(new Error(res.statusText));
throw new Error('Network error, retrying...');
}
return res.json();
}, {
retries: 5,
factor: 2,
minTimeout: 1000,
maxTimeout: 10000,
});
}
Why retry: In distributed networks, transient DNS or router flaps often self‑heal within seconds.
Real‑world Use Cases & Examples
1. AggregateError ETIMEDOUT in Node v20 Fetch
import fetch from 'node-fetch';
(async () => {
try {
await fetch('https://api.example.com/data', { timeout: 5000 });
} catch (err) {
console.error(err);
// AggregateError: The operation was aborted due to ETIMEDOUT
}
})();
- Cause: IPv6 fallback delay on dual‑stack networks.
- Solution: Apply
dns.setDefaultResultOrder('ipv4first')
or Happy Eyeballs flags.
2. High‑volume Parallel HTTP Requests
import axios from 'axios';
async function hammerAPI(endpoints) {
return Promise.all(endpoints.map(url =>
axios.get(url, { httpAgent: keepAliveAgent, timeout: 8000 })
));
}
// 1000 endpoints at once will queue sockets!
hammerAPI(largeList).catch(console.error);
- Symptom: ETIMEDOUT after ~4 queued DNS calls.
- Fix:
- Batch requests (
p-limit
orPromise.map
with concurrency). - Increase UV_THREADPOOL_SIZE for DNS.
- Tune
maxSockets
on agent to cap concurrency.
- Batch requests (
3. SMTP Timeout via Nodemailer in Heroku
mport nodemailer from 'nodemailer';
const transporter = nodemailer.createTransport({
host: 'smtp.example.com',
port: 587, // Ensure not blocked
secure: false,
connectionTimeout: 10000,
logger: true,
tls: { rejectUnauthorized: false },
});
- Common: Port 25 blocked → handshake never completes → ETIMEDOUT.
- Workaround: Use port 587 or 465, ensure
connectionTimeout
> 5s.
4. AWS Lambda + Axios ETIMEDOUT
- Scenario: Cold starts + no keep‑alive → new connections on each invocation.
- Optimizations:
- Reuse agent across invocations.
- Increase memory to speed startup.
- Retry Axios on ETIMEDOUT.
// Lambda handler
let client;
export const handler = async (event) => {
if (!client) client = axios.create({ httpAgent: keepAliveAgent, timeout: 7000 });
return client.get('https://example.com/data');
};
Best Practices & Performance Tips
- Use Persistent Agents:
keepAlive: true
yields up to 3× throughput improvement.- See Node.js HTTP agent guide.
- Align Timeouts:
- Client
<
Server (keepAliveTimeout + buffer).
- Client
- Limit Concurrency:
- Use
maxSockets
or libraries likep-limit
to cap parallelism.
- Use
- DNS & IPv6 Monitoring:
- Enable
dns.setDefaultResultOrder('ipv4first')
for dual‑stack networks.
- Enable
- Retry with Caps:
- Exponential backoff, max 3–5 retries.
- Comprehensive Logging:
- Log
err.code
,err.address
, and stack to diagnose root cause.
- Log
- Monitor in Production:
- Integrate with APM tools to spot spikes in ETIMEDOUT rates.
- Upgrade Node.js:
- Stay on LTS releases where Happy Eyeballs bugs are patched.
FAQs
1. What does ETIMEDOUT mean in Node.js?
ETIMEDOUT indicates a TCP socket failed to connect before the specified timeout. It’s a low‑level network error originating from the OS.
2. Why does Node v20 cause ETIMEDOUT with IPv4/IPv6?
Node v20 introduced network‑family‑autoselection, preferring IPv6 first. Unreachable IPv6 can delay fallback, triggering a timeout.
3. How do I configure Keep‑Alive to avoid timeouts?
Instantiate anhttp.Agent({ keepAlive: true })
, assign it tohttp.globalAgent
(andhttps.globalAgent
), and tunekeepAliveMsecs
.
4. What is UV_THREADPOOL_SIZE and how does it affect timeouts?
UV_THREADPOOL_SIZE
controls libuv’s thread pool for DNS, file I/O, and crypto. Raising it (e.g., to 128) prevents DNS lookups from queuing.
5. Why am I seeing ETIMEDOUT but the URL works in the browser?
Browsers use optimized DNS and parallel Happy Eyeballs logic. Node’s defaults may wait longer or use fewer threads, delaying the handshake.
6. How to fix npm install ETIMEDOUT errors?
- Delete broken proxy:
npm config delete proxy
. - Increase
fetch-retry
in npm:npm set fetch-retry-mintimeout 20000
. - Force IPv4:
npm config set prefer-offline false
anddns.setDefaultResultOrder('ipv4first')
.
7. Can SMTP or database connections cause ETIMEDOUT?
Yes. If mail servers block ports (25, 465, 587) or database endpoints are unreachable behind VPC rules, you’ll see ETIMEDOUT.
8. How should I implement retry logic in HTTP clients?
Use libraries like axios-retry or async-retry with exponential backoff and a max retry cap to gracefully handle transient network failures.
Conclusion
The Node.js ETIMEDOUT error—indicative of a “Connection timed out”—can strike across HTTP clients, database drivers, email transports, and even package managers. While network flakiness is inevitable, you can dramatically reduce ETIMEDOUT occurrences by:
- Tuning Happy Eyeballs (Node v20+ flags or legacy fallback)
- Enabling Keep‑Alive agents for socket reuse
- Scaling the libuv thread pool with
UV_THREADPOOL_SIZE
- Aligning client & server timeouts to realistic network latencies
- Configuring DNS & proxies for reliable resolution
- Implementing robust retry strategies with exponential backoff
By adopting these best practices, monitoring your production logs, and sharing insights with the community, you’ll build more resilient Node.js applications—where timeouts become an exception, not the norm. Happy coding!