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.
Fixes Error: DNS timeout
# Test DNS resolution manually
nslookup example.com
dig example.com
# Check your DNS configuration
cat /etc/resolv.conf# Test DNS resolution manuallynslookup example.comdig example.com# Check your DNS configurationcat /etc/resolv.confError: DNS timeout
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.