Error: getaddrinfo ENOTFOUND api.example.com means Node.js could not resolve the hostname to an IP address via DNS. The most common causes are: a typo in the hostname, a missing environment variable producing ENOTFOUND undefined, using localhost inside Docker, a corporate proxy not configured, or a missing /etc/hosts entry. Verify with nslookup or dns.lookup(), fix your environment variables, configure proxy agents for corporate networks, and add retry logic for the transient variant EAI_AGAIN.
What is ENOTFOUND?
ENOTFOUND is a
POSIX system error
thrown by Node.js when the DNS resolver cannot translate a hostname into an IP address.
The operating system's getaddrinfo() call — which Node.js uses internally for
all hostname lookups — returns EAI_NONAME (name not known), which Node.js
surfaces as ENOTFOUND.
Unlike ECONNRESET or
ECONNREFUSED,
no TCP connection is ever attempted — the error happens entirely in the
DNS lookup phase, before Node.js can open a socket.
Error: getaddrinfo ENOTFOUND api.example.comError: getaddrinfo ENOTFOUND undefinedError: getaddrinfo EAI_AGAIN api.example.comFetchError: request to https://api.example.com failed, reason: getaddrinfo ENOTFOUND api.example.comError: getaddrinfo ENOTFOUND localhost
Full Error Example
Error: getaddrinfo ENOTFOUND api.example.com
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:107:26) {
errno: -3008,
code: 'ENOTFOUND',
syscall: 'getaddrinfo',
hostname: 'api.example.com'
}
The error object always includes:
err.code—'ENOTFOUND'or'EAI_AGAIN'err.syscall— always'getaddrinfo'for DNS failureserr.hostname— the hostname that could not be resolved
err.hostname is especially useful in production logging — it tells you
exactly which hostname failed, even when the error bubbles up through multiple layers.
When you see ENOTFOUND undefined, err.hostname will literally be
the string "undefined", which is a strong signal that an environment variable
is missing.
ENOTFOUND vs EAI_AGAIN — Key Difference
| Code | Meaning | Retryable? | Common cause |
|---|---|---|---|
ENOTFOUND |
Permanent DNS failure — the hostname definitively does not resolve (DNS returned NXDOMAIN or the name is invalid) | No — fix the hostname or DNS configuration | Typo in hostname, wrong env var, hostname does not exist |
EAI_AGAIN |
Temporary DNS failure — the resolver could not reach a DNS server at this moment (network blip, DNS server overloaded, container startup race) | Yes — retry with backoff | Container starting before DNS is ready, flaky DNS server, network outage |
ENOTFOUND instead of
EAI_AGAIN. When in doubt, treat both codes as candidates for retry logic,
but cap retries at 3–5 attempts with exponential backoff.
Common Causes
| Cause | Why it happens |
|---|---|
| Typo in hostname | The URL string has a misspelled domain (e.g. api.examlpe.com instead of api.example.com). The DNS server correctly returns NXDOMAIN for a name that does not exist. |
ENOTFOUND undefined — missing env var |
process.env.API_URL is undefined. When interpolated into a template string (`${process.env.API_URL}/path`), the hostname becomes the literal string "undefined", producing getaddrinfo ENOTFOUND undefined. |
| Protocol included in host option | Passing { host: 'https://api.example.com' } to http.get() or https.get() includes the https:// scheme in the hostname. The DNS resolver sees https://api.example.com as the hostname and correctly cannot resolve it. Use only the bare hostname: { host: 'api.example.com' }. |
| No internet connectivity / offline environment | The machine or container has no network access. All external DNS lookups fail immediately. Common in restricted CI runners, air-gapped servers, or laptops with no active connection. |
| DNS server down or misconfigured | The system's configured DNS resolver (in /etc/resolv.conf) is unreachable or returns errors. Every hostname lookup fails regardless of whether the name is valid. |
| Wrong environment variable | API_URL, DATABASE_URL, or a similar config variable points to a hostname that does not exist in the current environment (e.g. a staging hostname used in production, or vice versa). |
Using localhost inside a Docker container |
Inside a container, localhost resolves to the container itself — not the host machine and not another container. Attempting to connect to a service on the host or a sibling container using localhost fails with ENOTFOUND or ECONNREFUSED. |
Missing /etc/hosts entry for localhost |
On some minimal Linux environments, localhost is not mapped in /etc/hosts, so DNS resolution fails. The fix is adding 127.0.0.1 localhost to the hosts file or using 127.0.0.1 directly. |
| Corporate proxy not configured | In enterprise environments, all outbound traffic must route through an HTTP/HTTPS proxy. Node.js built-in modules do not honor HTTP_PROXY / HTTPS_PROXY environment variables automatically, so DNS resolution fails when a proxy is required. |
| VPN / corporate DNS issues | Internal hostnames (e.g. api.internal.corp.com) only resolve via the corporate DNS server. Without VPN active, or when split-horizon DNS is misconfigured, the lookup fails. |
| IPv6 DNS resolution returning no usable address | Node.js may attempt to resolve a hostname over IPv6 first. If the DNS server returns an IPv6 address but the target is not reachable over IPv6, the connection fails. Forcing IPv4 with family: 4 resolves this. |
| Docker container can't resolve external hostnames | Docker's embedded DNS (127.0.0.11) forwards to the host's DNS. If the host's DNS is misconfigured, or if the Docker daemon was started without a reachable DNS server, all external lookups fail inside containers. |
Fix 1 – Verify the hostname with dns.lookup() or system tools
Before changing any code, confirm whether the hostname resolves at all from the environment where Node.js is running. This immediately tells you whether the problem is a bad hostname or an environment DNS problem.
# From the terminal (macOS / Linux / WSL)
nslookup api.example.com
dig api.example.com +short
# On Windows
nslookup api.example.com
# ping also triggers a DNS lookup
ping api.example.com
# Enable verbose DNS tracing in Node.js
NODE_DEBUG=dns node app.js
node --trace-dns app.js # Node.js 20+
You can also verify programmatically from inside your Node.js process or a quick script:
// dns-check.js — run with: node dns-check.js
const dns = require('dns');
dns.lookup('api.example.com', (err, address, family) => {
if (err) {
console.error('DNS lookup failed:', err.code, err.message);
// err.code will be 'ENOTFOUND' or 'EAI_AGAIN'
} else {
console.log('Resolved to:', address, '(IPv' + family + ')');
}
});
// Or using the promise API (Node.js 15+)
const { lookup } = require('dns/promises');
lookup('api.example.com')
.then(({ address, family }) => console.log('Resolved:', address))
.catch((err) => console.error('Failed:', err.code, err.hostname));
// One-liner quick test
node -e "require('dns').lookup('api.example.com', (e,a) => console.log(e || a))"
docker exec -it my-container node dns-check.js
Fix 2 – Check environment variables (fix "ENOTFOUND undefined")
Error: getaddrinfo ENOTFOUND undefined is one of the most common forms of this
error. It means process.env.API_URL (or a similar variable) was never set, so
JavaScript converted it to the literal string "undefined" when building the URL.
// ❌ Common mistake — env var is undefined → hostname becomes "undefined"
const apiUrl = process.env.API_URL; // undefined
const res = await fetch(`${apiUrl}/users`);
// → Error: getaddrinfo ENOTFOUND undefined
// ✅ Validate environment variables at startup (fail fast)
function requireEnv(name) {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}
const API_BASE = requireEnv('API_URL'); // throws at startup if missing
// ✅ Also validate that the value is a well-formed URL
function getValidatedUrl(envVar) {
const raw = requireEnv(envVar);
try {
return new URL(raw); // throws if not a valid URL
} catch {
throw new Error(`${envVar} is not a valid URL: "${raw}"`);
}
}
const apiUrl = getValidatedUrl('API_URL');
console.log('API host:', apiUrl.hostname); // Confirm this is what you expect
dotenv, call
require('dotenv').config() (or import 'dotenv/config') before
any other module that reads process.env. If the .env file is missing
from the deployment, all variables will be undefined and you will see a flood of
ENOTFOUND undefined errors.
Fix 3 – Fix the hosts file for localhost issues
If the failing hostname is localhost or a custom local hostname, the problem
may be a missing entry in your system's hosts file. This is especially common in Docker
base images, WSL environments, and minimal Linux distros.
# Check your current hosts file
cat /etc/hosts # Linux / macOS / Docker containers
# Windows: C:\Windows\System32\drivers\etc\hosts
# Required entries — add these if missing:
# 127.0.0.1 localhost
# ::1 localhost
# On macOS, flush the DNS cache after editing hosts:
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder
# On Linux (systemd-resolved):
sudo systemd-resolve --flush-caches
# On Windows:
ipconfig /flushdns
For MongoDB connections, a common fix is switching from localhost
to 127.0.0.1 in the connection string. This bypasses DNS entirely by using
the IP address directly:
// ❌ May fail with ENOTFOUND on systems where localhost is not in /etc/hosts
mongoose.connect('mongodb://localhost:27017/mydb');
// ✅ Use IP address to bypass DNS lookup entirely
mongoose.connect('mongodb://127.0.0.1:27017/mydb');
Fix 4 – Docker DNS fix
Docker containers frequently produce ENOTFOUND for two different reasons, each with a different fix.
Problem A: Using localhost to reach another container
# docker-compose.yml
services:
api:
image: my-api
# ✅ Other services reach this container as 'api' (the service name)
worker:
image: my-worker
environment:
# ❌ This resolves to the worker container itself, not the api service
API_URL: http://localhost:3000
# ✅ Use the docker-compose service name
API_URL: http://api:3000
Problem B: Container cannot resolve external hostnames
# /etc/docker/daemon.json — add reliable public DNS servers
{
"dns": ["8.8.8.8", "1.1.1.1"]
}
# Apply the change
sudo systemctl restart docker
# Alternatively, pass DNS per container at runtime
docker run --dns 8.8.8.8 --dns 1.1.1.1 my-image node app.js
# Verify DNS inside the container
docker exec -it my-container nslookup api.example.com
docker exec -it my-container cat /etc/resolv.conf
Fix 5 – Configure proxy for corporate networks
Node.js built-in http and https modules do not
automatically read HTTP_PROXY or HTTPS_PROXY environment variables.
In corporate networks where all traffic must route through a proxy, DNS resolution for
external hostnames fails with ENOTFOUND because requests never reach the proxy.
Option A: Use hpagent (recommended for Node.js built-in fetch / https)
// npm install hpagent
const { HttpsProxyAgent } = require('hpagent');
const agent = new HttpsProxyAgent({
proxy: process.env.HTTPS_PROXY || 'http://proxy.corp.example.com:8080',
});
// With Node.js built-in fetch (Node 18+)
const res = await fetch('https://api.example.com/data', { agent });
// With https.get()
const https = require('https');
https.get({ hostname: 'api.example.com', agent }, (res) => { /* ... */ });
Option B: Configure proxy in axios
// npm install axios
const axios = require('axios');
const instance = axios.create({
proxy: {
protocol: 'http',
host: 'proxy.corp.example.com',
port: 8080,
// auth: { username: 'user', password: 'pass' }, // if required
},
});
const res = await instance.get('https://api.example.com/data');
Option C: Remove npm proxy if incorrectly set
# Check if npm proxy is set (can affect npm-based tools)
npm config get proxy
npm config get https-proxy
# Remove proxy settings if no longer needed
npm config delete proxy
npm config delete https-proxy
# Or set NO_PROXY to bypass proxy for specific hosts
export NO_PROXY=localhost,127.0.0.1,api.example.com
Fix 6 – Force IPv4 to work around IPv6 DNS issues
On dual-stack networks, Node.js may attempt to resolve a hostname as AAAA (IPv6) first.
If the DNS server returns an IPv6 address but the destination is only reachable over IPv4,
the connection fails. Pass family: 4 to force IPv4-only resolution.
const dns = require('dns');
const https = require('https');
// Force IPv4 for a specific lookup
dns.lookup('api.example.com', { family: 4 }, (err, address, family) => {
console.log('IPv4 address:', address);
});
// Force IPv4 globally for all http/https lookups
const agent = new https.Agent({ family: 4 });
const res = await fetch('https://api.example.com/data', {
// Node 18+: dispatcher is used instead of agent
});
// With axios: set the family option on the httpAgent
const axios = require('axios');
const instance = axios.create({
httpsAgent: new https.Agent({ family: 4 }),
});
// Or set the global DNS order preference (Node.js 17+)
dns.setDefaultResultOrder('ipv4first');
Fix 7 – Retry on EAI_AGAIN (temporary DNS failures)
EAI_AGAIN is a transient error — the DNS server was momentarily unreachable.
This is common during container startup (the app starts before the network is fully ready)
and during brief DNS server outages. Add retry logic with exponential backoff.
const RETRYABLE_DNS_CODES = new Set(['EAI_AGAIN', 'ENOTFOUND', 'ECONNRESET', 'ETIMEDOUT']);
async function fetchWithRetry(url, options = {}, { retries = 3, baseDelayMs = 300 } = {}) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const res = await fetch(url, options);
if (res.ok) return res;
if (res.status >= 500 && attempt < retries) {
await sleep(baseDelayMs * 2 ** attempt);
continue;
}
return res;
} catch (err) {
const code = err.cause?.code ?? err.code;
const isRetryable = RETRYABLE_DNS_CODES.has(code);
if (isRetryable && attempt < retries) {
console.warn(`DNS/network error (${code}), retrying in ${baseDelayMs * 2 ** attempt}ms… (attempt ${attempt + 1}/${retries})`);
await sleep(baseDelayMs * 2 ** attempt);
continue;
}
throw err;
}
}
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Usage
const res = await fetchWithRetry('https://api.example.com/data');
const data = await res.json();
EAI_AGAIN.
Use a retry loop or a health-check / wait-for-it script in your Docker entrypoint rather
than ignoring the error.
Fix 8 – Handle ENOTFOUND gracefully in production code
DNS errors should be caught at the HTTP/fetch boundary and converted into structured
application errors. Never let an unhandled ENOTFOUND propagate to a user
response or crash your process.
class DnsError extends Error {
constructor(hostname, code) {
super(`DNS resolution failed for "${hostname}" (${code})`);
this.name = 'DnsError';
this.hostname = hostname;
this.code = code;
this.retryable = code === 'EAI_AGAIN';
}
}
async function apiRequest(path) {
try {
const res = await fetch(`https://api.example.com${path}`, {
signal: AbortSignal.timeout(8000),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
// fetch wraps network errors in a TypeError; the actual code is on err.cause
const code = err.cause?.code ?? err.code;
const hostname = err.cause?.hostname;
if (code === 'ENOTFOUND' || code === 'EAI_AGAIN') {
throw new DnsError(hostname ?? 'api.example.com', code);
}
throw err;
}
}
// In your request handler
app.get('/data', async (req, res) => {
try {
const data = await apiRequest('/data');
res.json(data);
} catch (err) {
if (err instanceof DnsError) {
const status = err.retryable ? 503 : 502;
res.status(status).json({
error: 'upstream_dns_failure',
message: err.message,
retryable: err.retryable,
});
} else {
res.status(500).json({ error: 'internal_error' });
}
}
});
Fix 9 – IP address fallback for critical connections
For connections where DNS availability cannot be guaranteed (e.g. a critical database
in a constrained environment), you can bypass DNS entirely by using a pre-resolved IP
address and providing the expected hostname in the Host header (for HTTP)
or in the TLS SNI field.
const https = require('https');
// Pre-resolved IP for api.example.com (update this if the IP changes)
const FALLBACK_IP = '203.0.113.42';
const options = {
host: FALLBACK_IP, // connect to IP directly — no DNS lookup
port: 443,
path: '/data',
headers: {
Host: 'api.example.com', // required for virtual hosting and SNI
},
servername: 'api.example.com', // TLS SNI — required for HTTPS
};
https.get(options, (res) => {
let body = '';
res.on('data', (chunk) => { body += chunk; });
res.on('end', () => console.log(JSON.parse(body)));
}).on('error', console.error);
Debugging Commands
Use these commands to diagnose ENOTFOUND from the same environment as your Node.js process.
# Check if the hostname resolves at all
nslookup api.example.com
dig api.example.com +short
# Check which DNS server is being used
cat /etc/resolv.conf # Linux / macOS / Docker containers
ipconfig /all | findstr DNS # Windows — shows DNS server addresses
# Verify network connectivity to the DNS server itself
ping 8.8.8.8
# Test hostname resolution from inside a Docker container
docker exec -it my-container nslookup api.example.com
docker exec -it my-container cat /etc/resolv.conf
# Enable Node.js DNS debug output
NODE_DEBUG=dns node app.js
node --trace-dns app.js # Node.js 20+ — verbose per-query tracing
# Quick one-liner to test DNS from Node.js
node -e "require('dns').lookup('api.example.com', (e,a) => console.log(e || a))"
# Flush DNS cache (after editing /etc/hosts or changing DNS server)
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder # macOS
sudo systemd-resolve --flush-caches # Linux (systemd)
ipconfig /flushdns # Windows
err.hostname and err.code
so you know exactly which hostname failed and under what conditions. In containerised
environments, a silent ENOTFOUND often means a misconfigured environment variable that
will surface again in a different, harder-to-debug form.
Frequently Asked Questions
What is ENOTFOUND in Node.js?
ENOTFOUND stands for "Error NOT FOUND" at the DNS level. Node.js throws it as Error: getaddrinfo ENOTFOUND hostname when the DNS resolver cannot map the given hostname to an IP address. This happens before any TCP connection is attempted. The hostname may not exist, may be misspelled, or your environment may have no DNS connectivity.
Why does Node.js show "Error: getaddrinfo ENOTFOUND undefined"?
The literal hostname undefined in the error means your code passed a JavaScript undefined value as the URL hostname. The most common cause is a missing or unset environment variable: process.env.API_URL is undefined, and when interpolated into a URL string it becomes the literal text "undefined". Fix: validate all required environment variables at application startup before any requests are made.
What is the difference between ENOTFOUND and EAI_AGAIN?
ENOTFOUND is a permanent DNS failure — the hostname definitively does not resolve, either because it does not exist or the DNS server authoritatively returned NXDOMAIN. EAI_AGAIN is a temporary DNS failure — the resolver could not reach a DNS server (e.g. network is down, DNS server is overloaded) and the lookup should be retried. EAI_AGAIN is transient and retryable; ENOTFOUND usually indicates a configuration problem.
Why does ENOTFOUND happen in Docker containers?
Docker containers use their own DNS resolver (127.0.0.11 by default). If the container cannot reach external DNS servers, or if you use localhost to refer to another container (which resolves to the container itself, not the host), DNS resolution fails with ENOTFOUND. Fix: use the docker-compose service name instead of localhost, and add "dns": ["8.8.8.8", "1.1.1.1"] to /etc/docker/daemon.json.
Can a corporate proxy cause getaddrinfo ENOTFOUND?
Yes. In corporate or restricted network environments, all outbound traffic must route through an HTTP/HTTPS proxy. Node.js built-in http/https modules do not read proxy environment variables automatically. If HTTP_PROXY or HTTPS_PROXY is set but your code does not configure a proxy agent, requests bypass the proxy and DNS resolution fails. Use the hpagent or proxy-agent npm package, or configure the proxy option in axios.
How do I fix ENOTFOUND localhost in Node.js?
On some systems, localhost is not mapped in /etc/hosts (Linux/macOS) or C:\Windows\System32\drivers\etc\hosts (Windows), causing DNS resolution to fail. Add 127.0.0.1 localhost and ::1 localhost to your hosts file. Alternatively, use 127.0.0.1 directly instead of localhost. In MongoDB connection strings, switching from localhost to 127.0.0.1 is a common fix.
How do I fix ENOTFOUND in axios?
In axios, ENOTFOUND appears as Error: getaddrinfo ENOTFOUND hostname on err.message, with err.code === 'ENOTFOUND'. To fix: (1) verify the base URL in your axios instance is correct; (2) if behind a proxy, configure the proxy option in axios.create(); (3) if IPv6 is causing issues, pass httpsAgent: new https.Agent({ family: 4 }); (4) add retry logic using axios-retry for transient failures.
Related Errors
ECONNREFUSED: connect ECONNREFUSED— the hostname resolved successfully but nothing is listening on the target port; the TCP connection was actively refusedECONNRESET: connection reset by peer— the connection was established but then forcibly closed mid-transferETIMEDOUT: connect ETIMEDOUT— the hostname resolved but the TCP handshake timed out; the server is unreachable or behind a firewallEAI_AGAIN— temporary DNS failure; the resolver could not reach a DNS server; retryable variant of ENOTFOUNDCERT_HAS_EXPIRED / UNABLE_TO_VERIFY_LEAF_SIGNATURE— DNS resolved and TCP connected, but TLS handshake failed due to certificate issues