Views: 13

getaddrinfo EAI_AGAIN: Fix the Node.js DNS Timeout

getaddrinfo EAI_AGAIN means Node's resolver timed out — a transient failure, not a dead name. Tell it apart from a real bug in 3 checks. Free instant check, no sign-up.

Check your domain for this issue now

Free, no sign-up. Runs the exact check this guide describes and shows what to fix.

Problem

Node throws Error: getaddrinfo EAI_AGAIN <hostname> and the request never leaves your process. On the error object, err.code is EAI_AGAIN, err.syscall is getaddrinfo. The key word is again: this is the resolver telling you it couldn’t get an answer right now — not that the name is wrong. That single distinction is the whole guide.

Symptoms

  • An axios, fetch, http.request, npm install, pg, or mongoose call rejects with getaddrinfo EAI_AGAIN.
  • It’s intermittent — the same code succeeds on retry, or fails at container startup and works a minute later, or fails under load and recovers when traffic drops.
  • It appears inside Docker, CI, or Kubernetes but not on your laptop — or only on Alpine-based images.
  • npm install or a build step fails to reach the registry with EAI_AGAIN, then works when you re-run it.

What getaddrinfo EAI_AGAIN Actually Means

Before Node opens a socket it has to turn a hostname into an IP. It does that through dns.lookup(), which calls the operating system’s getaddrinfo(3) on libuv’s threadpool — the same resolver path your shell uses, honoring /etc/resolv.conf and /etc/nsswitch.conf. Almost everything built on http/httpsfetch, axios, database drivers, npm — uses this path.

EAI_AGAIN is getaddrinfo’s code for a temporary failure in name resolution. It does not mean “no such name.” It means the resolver tried to reach a DNS server and didn’t get a usable answer in time — the server was unreachable, overloaded, slow, or simply not there yet. The POSIX contract for this code is explicit: the condition is transient, and the same lookup may succeed if you try it again.

That’s the crux, and it’s the opposite of its sibling ENOTFOUND (EAI_NONAME), which means the resolver did answer and the answer was “this name has no address.” ENOTFOUND is permanent and lives in your code or your DNS records. EAI_AGAIN is transient and lives in your network path to a DNS server. Confusing the two is the single most common way people waste an afternoon here: you cannot fix a flaky resolver by parsing your hostname differently, and you cannot fix a typo’d hostname by retrying.

Top 3 Causes

  1. The resolver is unreachable or timing out. The DNS server named in /etc/resolv.conf is down, blocked by a firewall or egress rule, or on a network the process can’t reach yet. This is the plain-vanilla case: network not fully up at boot, a VPN not connected, a DNS server briefly overloaded. The lookup times out, libc returns EAI_AGAIN, and a moment later it works.
  2. Docker/Alpine and musl’s parallel lookups. Alpine images use musl libc, which issues the A and AAAA queries in parallel and is stricter about timeouts than glibc. Against a slow or lossy resolver — common inside container networks — that produces intermittent EAI_AGAIN where a glibc host would have quietly succeeded. A container’s generated /etc/resolv.conf can also point at a resolver that isn’t reachable from inside its network namespace, or isn’t ready the instant the app starts. Worse, older musl has been imprecise about the boundary itself: a genuinely nonexistent name can surface as EAI_AGAIN on Alpine instead of a clean ENOTFOUND, so on musl the code alone is a weaker signal.
  3. Kubernetes ndots:5 amplifying every lookup. The default ndots:5 in a pod’s /etc/resolv.conf means any hostname with fewer than five dots gets tried against every search domain first — so a single external lookup becomes several queries before the real one. If the cluster DNS (CoreDNS) is under pressure, all those extra queries can time out and surface as EAI_AGAIN under load. The fix is often a fully-qualified name (a trailing dot) or a tuned ndots, not application code.

EAI_AGAIN vs ENOTFOUND — The Fork That Decides Your Fix

Print err.code first, always. It tells you which world you’re in:

  • EAI_AGAIN — no answer arrived. Transient. The right response is a bounded retry with backoff plus a look at the resolver: which nameserver is in resolv.conf, is it reachable, is something (ndots, musl, an egress rule) multiplying or dropping queries. Rewriting how you build the hostname fixes nothing here.
  • ENOTFOUND — an answer arrived and it was “no such name.” Permanent. Retrying just fails faster. The bug is a malformed hostname, an unset environment variable, or a missing DNS record. (There’s a dedicated guide for that one.)

On glibc systems the two are cleanly separated. On musl/Alpine, treat EAI_AGAIN with a little suspicion — confirm the name actually resolves from a healthy resolver before you assume it’s purely transient.

Diagnose with DechoNet

  • DNS Check — run it on the exact hostname from the error, from outside your infrastructure. If DechoNet returns a valid A/AAAA record, the name is fine and the problem is your resolver path: the DNS server your container or host is using is unreachable or slow, not the name. That immediately rules out the ENOTFOUND-style causes and points you at resolv.conf, the container network, or ndots. If DechoNet also can’t resolve it, you may be looking at a real name problem wearing an EAI_AGAIN mask (especially on Alpine).
  • Propagation Check — if the record is new or was just changed, query multiple resolvers at once. Some answering and others timing out looks like EAI_AGAIN from certain networks; that’s propagation and reachability, not your app.

Resolution Checklist

  • Print err.code and confirm it’s actually EAI_AGAIN, not ENOTFOUND. The fix forks entirely on this.
  • Run an external DNS Check on the hostname. Resolves fine externally → the name is good; the problem is your resolver path, not your code.
  • Check /etc/resolv.conf in the environment that fails (inside the container, the CI runner, the pod). Confirm the nameserver is reachable from there — not just from your laptop.
  • In Docker, point at a known-good resolver: --dns 8.8.8.8 at run time, or a dns entry in the daemon config. For build-time EAI_AGAIN, set DNS in the Docker daemon rather than in the Dockerfile.
  • On Alpine, consider whether musl’s parallel lookups against a slow resolver are the cause; a more capable base image or a reachable local resolver often removes the intermittency.
  • In Kubernetes, check ndots — a fully-qualified hostname (trailing dot) or a dnsConfig override stops one lookup from fanning out into five.
  • Add a bounded retry with exponential backoff for the genuinely transient case — a few attempts, increasing delay — not an unbounded tight loop that hammers a broken resolver.

When to Escalate

  • If an external DNS Check resolves the name but every retry inside your platform still returns EAI_AGAIN, the constraint is that environment’s DNS or egress — escalate to whoever runs the cluster, CI, or VPC network. The resolver they’ve configured is unreachable or overloaded, and no application change reaches it.
  • If the failures correlate with load and your cluster DNS (CoreDNS) is the resolver, hand its operators the timing and the query volume — an under-provisioned or ndots-amplified cluster DNS is an infrastructure fix, not an app one.

Related Tools

Related Guides

Share this guide

[Ad] Guide Detail Inline
← Back to All Guides