Node.js Error

Error: getaddrinfo ENOTFOUND hostname

error code: ENOTFOUND  ·  also: EAI_AGAIN

Complete reference — what it means, why DNS resolution fails, the difference between ENOTFOUND and EAI_AGAIN, and how to fix it in every environment including Docker, proxy networks, CI runners, and local development.

Quick Answer: 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.

Exact error messages you will see:
Error: getaddrinfo ENOTFOUND api.example.com
Error: getaddrinfo ENOTFOUND undefined
Error: getaddrinfo EAI_AGAIN api.example.com
FetchError: request to https://api.example.com failed, reason: getaddrinfo ENOTFOUND api.example.com
Error: 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.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

CodeMeaningRetryable?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
Node.js version note: In some environments and older Node.js versions, a temporarily unreachable DNS server may also produce 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

CauseWhy 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))"
Run this inside the same environment as your app. If your app runs in a Docker container, run the dns check inside the container: 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
Check your .env file loading order. If you use 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();
Container startup race: If your Node.js app starts before the network or a dependency service is ready, the first few requests will get 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);
IP fallback caveat: Hard-coding IP addresses is fragile — CDNs, cloud services, and APIs regularly rotate IPs. Only use this as a last resort for truly critical infrastructure where you control the IP and update it via configuration.

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
Do not silently swallow ENOTFOUND: Catching the error without logging it hides configuration problems. Always log 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