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, ormongoosecall rejects withgetaddrinfo 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 installor 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/https — fetch, 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
- The resolver is unreachable or timing out. The DNS server named in
/etc/resolv.confis 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. - 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.confcan 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. - Kubernetes
ndots:5amplifying every lookup. The defaultndots:5in a pod’s/etc/resolv.confmeans 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 tunedndots, 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 inresolv.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.codeand confirm it’s actuallyEAI_AGAIN, notENOTFOUND. 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.confin 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.8at run time, or adnsentry 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 adnsConfigoverride 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