DNS timeout errors occur when Node.js cannot resolve a hostname within the expected timeframe, often due to network issues, slow DNS servers, or threadpool exhaustion. This guide shows how to configure timeouts and implement caching strategies.
The "DNS timeout" error in Node.js indicates that a DNS lookup operation failed to complete within the allotted time. This happens when trying to resolve a hostname to an IP address, and the DNS server either doesn't respond or responds too slowly. Node.js uses two different methods for DNS resolution: `dns.lookup()` which uses the system's getaddrinfo(3) function and runs on libuv's threadpool, and `dns.resolve*()` which uses c-ares library and operates asynchronously. The timeout behavior differs between these methods. A critical aspect of this error is that Node.js's default thread pool has only 4 threads. When multiple slow DNS queries block these threads, it can cause a cascading failure affecting not just DNS lookups but also file I/O and crypto operations that share the same threadpool.
Test if your DNS servers are reachable from your environment:
# Test DNS resolution manually
nslookup example.com
dig example.com
# Check your DNS configuration
cat /etc/resolv.confIf these commands timeout, the issue is at the network/DNS level, not Node.js.
Use the dns.promises API with caching to avoid repeated lookups:
const dns = require('dns').promises;
const cache = new Map();
async function cachedLookup(hostname) {
const now = Date.now();
const cached = cache.get(hostname);
// Use cached result if still valid (60 second TTL)
if (cached && now - cached.timestamp < 60000) {
return cached.address;
}
const { address } = await dns.lookup(hostname);
cache.set(hostname, { address, timestamp: now });
return address;
}
// Usage
try {
const ip = await cachedLookup('example.com');
console.log('Resolved IP:', ip);
} catch (err) {
console.error('DNS lookup failed:', err);
}If using the c-ares based resolver, configure timeout settings:
const dns = require('dns');
const { Resolver } = dns;
const resolver = new Resolver();
// Set custom DNS servers
resolver.setServers(['8.8.8.8', '1.1.1.1']);
// Resolve with timeout handling
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('DNS timeout')), 5000)
);
const resolvePromise = new Promise((resolve, reject) => {
resolver.resolve4('example.com', (err, addresses) => {
if (err) reject(err);
else resolve(addresses);
});
});
try {
const addresses = await Promise.race([resolvePromise, timeoutPromise]);
console.log('Resolved addresses:', addresses);
} catch (err) {
console.error('DNS resolution failed:', err.message);
}If you're using dns.lookup() and experiencing threadpool exhaustion, increase the pool size:
# Set before starting Node.js
export UV_THREADPOOL_SIZE=128
node app.jsOr programmatically (must be set before any async operations):
process.env.UV_THREADPOOL_SIZE = 128;
// Now start your application
const http = require('http');
// ... rest of your codeNote: This only affects dns.lookup(), not dns.resolve*().
Avoid threadpool blocking by using the c-ares based resolver:
const dns = require('dns').promises;
// Instead of dns.lookup() which blocks the threadpool
async function resolveHostname(hostname) {
try {
// Use resolve4/resolve6 which don't block threadpool
const addresses = await dns.resolve4(hostname);
return addresses[0]; // Return first IP
} catch (err) {
console.error('DNS resolution failed:', err.message);
throw err;
}
}
// Usage
resolveHostname('example.com')
.then(ip => console.log('Resolved IP:', ip))
.catch(err => console.error('Error:', err));Configure DNS resolver behavior via environment variables:
# Set retry and timeout parameters
export RES_OPTIONS='retrans:1000 retry:3 rotate'
node app.jsParameters:
- retrans: Timeout in milliseconds (default: 5000)
- retry: Number of retries (default: 4)
- rotate: Rotate through available DNS servers
For production environments, use a local DNS cache like dnsmasq:
# Install dnsmasq (Ubuntu/Debian)
sudo apt-get install dnsmasq
# Configure it to cache DNS queries
sudo systemctl start dnsmasq
sudo systemctl enable dnsmasq
# Point Node.js to use localhost DNS
# Edit /etc/resolv.conf
nameserver 127.0.0.1Or in Kubernetes, use NodeLocal DNSCache to cache DNS on each node.
Thread Pool Considerations: Before Node.js v10.12.0, slow DNS queries could block ALL threadpool operations including file I/O and compression. Modern versions isolate DNS lookups better, but the 4-thread limit still applies.
HTTP Keep-Alive: Configure HTTP clients to reuse connections, reducing the number of DNS lookups needed:
const http = require('http');
const agent = new http.Agent({ keepAlive: true });
http.get({ hostname: 'example.com', agent }, (res) => {
// Connection will be reused
});DNS Prefetching: For known hostnames, pre-populate your DNS cache during application startup to avoid on-demand lookups that might timeout.
Kubernetes/Container Environments: DNS issues are particularly common in containerized environments. The NodeLocal DNSCache add-on can dramatically improve reliability by running a DNS cache on each worker node.
IPv6 Fallback: Some networks have broken IPv6 configurations. You can disable IPv6 DNS lookups by using dns.resolve4() explicitly instead of dns.resolve() which tries both IPv4 and IPv6.
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