Article View

Scroll down to read the full article.

The Phantom EAI_AGAIN: Node.js Internal DNS Hell on RHEL 8 with ndots Misery

calendar_month August 02, 2026 |
Quick Summary: Fix intermittent Node.js EAI_AGAIN errors in RHEL 8 containers for internal services. Troubleshoot glibc DNS resolution quirks with ndots and sear...

Alright, listen up, because I'm tired of seeing this one. You’ve deployed your shiny Node.js application into a RHEL 8 container. It runs, it serves traffic, life is good. Then, suddenly, your logs are spewing Error: getaddrinfo EAI_AGAIN <your-internal-service-name>. Intermittently. Maddeningly. You reboot the container, it's fine for a bit, then BAM. Again. You scratch your head, you want to throw your monitor. I've been there. Let's fix this.

This isn't your garden-variety DNS outage. Oh no. You can nslookup <your-internal-service-name> from inside the problematic container all day long, and it resolves perfectly. Your /etc/resolv.conf looks sane. Your DNS servers are up. Yet Node.js chokes. What the hell?

This particular beast thrives in specific environments:

Operating SystemNode.js VersionDNS Configuration
Red Hat Enterprise Linux 8.x (or compatible, e.g., CentOS 8, AlmaLinux 8)Node.js 16.x, 18.x, 20.x/etc/resolv.conf with search domains and options ndots:1 (or similar low value)
Container runtime (Docker, Podman, Kubernetes)AnyInternal DNS server (e.g., Kube-DNS, CoreDNS, internal BIND) that might be slow or inconsistently respond to certain query types

You’ve probably already wasted hours. Checked firewalls. Verified network routes. Maybe even tried tweaking Node.js's dns.setDefaultResultOrder('ipv4first') which, spoiler alert, won’t help with EAI_AGAIN when the core problem is upstream resolution failure, not IP preference. You might even have stumbled upon issues similar to The Throttled SNI Nightmare or The Ghost in the Loopback, but those usually manifest as ECONNRESET or HTTP/2 specific issues, not a fundamental DNS lookup failure like EAI_AGAIN. This is different.

A highly intricate tangle of glowing optical fiber cables with some individual strands frayed and sparking
Visual representation

The Root Cause

Here’s the deal. Node.js has two primary ways to resolve DNS: its built-in pure JavaScript resolver (accessed via dns.resolve* functions) and the operating system's resolver (accessed by default via dns.lookup, which uses glibc’s getaddrinfo function on Linux).

Your problem stems from the latter. When your /etc/resolv.conf contains search domains (e.g., search svc.cluster.local example.com) and an options ndots:1 directive, glibc’s getaddrinfo gets… aggressive. For a short hostname like my-internal-api (which has 0 dots), ndots:1 tells glibc to first try appending search domains before trying the raw name. So, it will attempt:

  1. my-internal-api.svc.cluster.local
  2. my-internal-api.example.com
  3. my-internal-api (finally, the actual name you want)

Now, if your internal DNS server (like CoreDNS in Kubernetes) is under load, or configured in a way that makes it slow to respond, or even just silently drops requests for the search domain appended names (e.g., my-internal-api.example.com might not exist and the server times out before sending a proper NXDOMAIN), these multiple attempts accumulate. Each attempt has an internal timeout. The cumulative effect of these slow or failing lookups for non-existent search domain permutations can exhaust an internal glibc resolver thread pool, or simply exceed Node.js's or glibc's overall timeout for a single getaddrinfo call, leading to a premature EAI_AGAIN error before it even gets to successfully resolve my-internal-api directly.

The Node.js community is aware of these system resolver quirks. In fact, they even provided an escape hatch.

The Fix: Force Node.js's Internal Resolver

We're going to tell Node.js to stop relying on glibc's often-fragile getaddrinfo for dns.lookup and instead use its own robust, pure JavaScript resolver. This bypasses the whole ndots and search domain dance of glibc for the initial lookup, giving you consistent resolution.

You achieve this by setting a specific environment variable for your Node.js process:

NODE_DNS_HINTS_BROKEN_SYSTEM_RESOLVER=true

Yes, the name itself tells you everything you need to know about the state of system resolvers. It's a dirty hack, but it works.

Step-by-Step Implementation:

  1. Identify Affected Services: Pinpoint the Node.js applications and containers exhibiting this EAI_AGAIN behavior.
  2. Apply the Environment Variable:

    For Kubernetes/OpenShift deployments: Modify your Deployment/StatefulSet YAML:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: your-nodejs-app
    spec:
    template:
    spec:
    containers:
    - name: app
    image: your-node-image:latest
    env:
    - name: NODE_DNS_HINTS_BROKEN_SYSTEM_RESOLVER
    value: "true"
    # ... other container settings

    For Docker/Podman: Add the -e flag:

    docker run -e NODE_DNS_HINTS_BROKEN_SYSTEM_RESOLVER=true your-node-image:latest

    For systemd services: Add to your .service file:

    [Service]
    Environment="NODE_DNS_HINTS_BROKEN_SYSTEM_RESOLVER=true"
    ExecStart=/usr/bin/node /app/index.js
    # ...
  3. Redeploy/Restart: Ensure your Node.js application picks up the new environment variable.
  4. Monitor: Closely watch your logs for the disappearance of those pesky EAI_AGAIN errors.

A complex
Visual representation

Verification

After applying the fix, the EAI_AGAIN errors related to internal service lookups should cease. Node.js will now manage its own DNS resolution, ignoring the search domain logic of glibc for dns.lookup calls, which is often exactly what you need in a containerized microservice environment where you expect short hostnames to resolve directly within the cluster's default search domain, or by explicitly providing FQDNs.

Caveats

  • This forces Node.js to use its internal DNS client for dns.lookup. While generally robust, it means it won't respect all subtleties of glibc's configuration (like specific caching behaviors or exotic resolv.conf options beyond basic nameservers). For 99% of use cases, this is a net positive in container environments.
  • If your primary DNS server itself is truly down or critically misconfigured, this won’t magically fix that. This addresses the intermittent failure due to glibc's over-eagerness with search domains.
  • Always test in a staging environment first.

Stop fighting the phantom EAI_AGAIN. Give Node.js its own resolver, and get back to shipping features, not debugging Linux networking quirks. You're welcome.

Discussion

Comments

Read Next