Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for NGINX By James Joyner IV · · 8 min read Last reviewed Jul 2026

NGINX Error Guide: 'no resolver defined to resolve' — Fix Dynamic DNS Upstreams

Quick answer

Fix 'no resolver defined to resolve' in NGINX: add a resolver for variable proxy_pass targets, set the DNS server and TTL, and avoid stale IPs.

  • #nginx
  • #web-server
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this NGINX error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

NGINX raises this at request time whenever it must resolve a hostname at runtime but no resolver directive is configured. It typically appears when proxy_pass (or grpc_pass, uwsgi_pass, an auth_jwt_key_request, an njs fetch, etc.) points at a hostname held in a variable:

2026/07/09 09:41:53 [error] 992#992: *5027 no resolver defined to resolve api.internal.example.com, client: 10.0.0.7, server: gateway.example.com, request: "GET /v1/orders HTTP/1.1", host: "gateway.example.com"

The client sees a 502 Bad Gateway. The key trigger is dynamic resolution: when a hostname appears in a proxy_pass as a literal, NGINX resolves it once at startup using the system resolver. But when the target is built from a variable, NGINX defers resolution to request time and needs an explicit resolver to do it.

Symptoms

  • 502 Bad Gateway on any route whose proxy_pass uses a variable containing a hostname.
  • The error log shows no resolver defined to resolve <hostname> — with the exact hostname it failed on.
  • Static proxy_pass http://api.internal.example.com; locations work fine; only the variable-based ones break.
  • The problem appears after refactoring a config to use set $backend ...; or a map, or after enabling dynamic upstreams for service discovery.

Common Root Causes

  • Variable proxy_pass without a resolver — e.g. set $upstream "api.internal"; proxy_pass http://$upstream; with no resolver in scope.
  • Runtime DNS features in usegrpc_pass, uwsgi_pass, auth_request to a hostname, njs fetch, or OpenID Connect key requests, all of which resolve at request time.
  • Resolver defined in the wrong scope — placed in a server block that the failing location doesn’t inherit, or missing from the stream context entirely.
  • Service-discovery patterns — pointing at a Kubernetes service DNS name or Consul name that must be re-resolved as pods change.

Diagnostic Workflow

Find the offending location — it will use a variable in proxy_pass:

grep -rnE 'proxy_pass\s+https?://\$' /etc/nginx/
grep 'no resolver defined' /var/log/nginx/error.log | tail

Confirm the host can be resolved from the NGINX box and identify a DNS server to point NGINX at:

# What resolver does the OS use? (do NOT blindly reuse 127.0.0.53 stub in containers)
cat /etc/resolv.conf
dig +short api.internal.example.com @10.0.0.2

Add a resolver directive in the same context as the dynamic proxy_pass, and use a variable target so runtime resolution is honored:

http {
    # Point at a real DNS server reachable from NGINX. valid= sets re-resolution TTL.
    resolver 10.0.0.2 valid=30s ipv6=off;
    resolver_timeout 5s;

    server {
        location /v1/ {
            set $backend "api.internal.example.com";
            proxy_pass http://$backend:8080;   # variable target -> resolved at request time

            proxy_set_header Host $backend;
        }
    }
}

For the stream context, the resolver must be inside the stream {} block — it does not inherit from http. Validate and reload:

nginx -t && nginx -s reload

Example Root Cause Analysis

A platform team migrated an internal API gateway to Kubernetes and changed proxy_pass http://orders-svc; to set $svc "orders-svc.prod.svc.cluster.local"; proxy_pass http://$svc; so NGINX would re-resolve the ClusterIP as the service moved. Immediately, every /v1/orders request returned 502 with no resolver defined to resolve orders-svc.prod.svc.cluster.local.

The literal-to-variable change was the trigger: with a literal hostname NGINX resolved once at startup, but the variable form defers resolution to request time and demands a resolver. They added resolver <kube-dns-clusterIP> valid=10s ipv6=off; at the http level, pointing at the cluster’s CoreDNS service, with a short valid= TTL so NGINX would pick up new endpoints quickly. The 502s cleared. They also set proxy_set_header Host $svc; because using a variable target drops the implicit Host header that a literal proxy_pass would have set.

Prevention Best Practices

  • Any time a proxy_pass/grpc_pass target comes from a variable, add a resolver in the same context (remember stream {} needs its own).
  • Point resolver at a real, reachable DNS server — in containers, avoid the 127.0.0.53 systemd stub, which usually isn’t present inside the container.
  • Set a sensible valid= TTL to control how often NGINX re-resolves; short TTLs track fast-changing service discovery, longer TTLs reduce DNS load.
  • Add resolver_timeout and consider ipv6=off if your upstreams are IPv4-only, to avoid slow AAAA lookups.
  • When using a variable target, set proxy_set_header Host explicitly — the variable form does not infer the Host header the way a literal proxy_pass does.
  • Keep static upstreams static: if a hostname never changes, a literal proxy_pass (resolved at startup) is simpler and needs no resolver.

Quick Command Reference

# Find dynamic proxy_pass targets that need a resolver
grep -rnE 'proxy_pass\s+https?://\$' /etc/nginx/

# See the exact hostname that failed
grep 'no resolver defined' /var/log/nginx/error.log | tail

# Verify a DNS server can resolve the upstream
dig +short UPSTREAM_HOST @DNS_SERVER_IP

# Validate and reload after adding resolver
nginx -t && nginx -s reload

Conclusion

no resolver defined to resolve means NGINX needs to look up a hostname at request time — because the target lives in a variable or a runtime feature like grpc_pass/auth_request — but you never told it which DNS server to use. Add a resolver in the same context as the dynamic directive (including a separate one inside stream {}), point it at a reachable DNS server with a sensible valid= TTL, and set the Host header explicitly for variable targets. That turns brittle startup-only resolution into working, TTL-aware dynamic upstreams.

Free download · 368-page PDF

Fixed it? Get 500 NGINX & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.