grpc/grpc-go · warning

dns: %v record lookup error: %v

Error message

dns: %v record lookup error: %v

What it means

Logged and returned by handleDNSError for any DNS lookup error that is not a benign name-not-found (i.e. a timeout or temporary error). Benign non-existent-name errors are suppressed (returned as nil); genuine timeouts, temporary failures, or server errors are wrapped with the lookup type and propagated so the resolver backs off and retries. It is logged at Info level, not fatal.

Source

Thrown at internal/resolver/dns/dns_resolver.go:287

				return nil, fmt.Errorf("dns: error parsing A record IP address %v: %v", a, err)
			}
			addr := ip + ":" + strconv.Itoa(int(s.Port))
			newAddrs = append(newAddrs, resolver.Address{Addr: addr, ServerName: s.Target})
		}
	}
	return newAddrs, nil
}

func handleDNSError(err error, lookupType string) error {
	dnsErr, ok := err.(*net.DNSError)
	if ok && !dnsErr.IsTimeout && !dnsErr.IsTemporary {
		// Timeouts and temporary errors should be communicated to gRPC to
		// attempt another DNS query (with backoff).  Other errors should be
		// suppressed (they may represent the absence of a TXT record).
		return nil
	}
	if err != nil {
		err = fmt.Errorf("dns: %v record lookup error: %v", lookupType, err)
		logger.Info(err)
	}
	return err
}

func (d *dnsResolver) lookupTXT(ctx context.Context) *serviceconfig.ParseResult {
	ss, err := d.resolver.LookupTXT(ctx, txtPrefix+d.host)
	if err != nil {
		if envconfig.TXTErrIgnore {
			return nil
		}
		if err = handleDNSError(err, "TXT"); err != nil {
			return &serviceconfig.ParseResult{Err: err}
		}
		return nil
	}
	var res string
	for _, s := range ss {

View on GitHub (pinned to 03255a9237)

Solutions

  1. Verify nameservers in /etc/resolv.conf are reachable (dig @ns host).
  2. Increase DNS timeouts or use a custom resolver via the dns://authority/ target.
  3. Treat as transient: the dns resolver already retries with exponential backoff.
  4. Check GRPC_DNS_RESOLVER / custom authority configuration.

Example fix

// before: default resolver times out
conn, _ := grpc.Dial("dns:///svc:443", ...)
// after: pin a reliable authority
conn, _ := grpc.Dial("dns:///8.8.8.8/svc:443", ...)
Defensive patterns

Strategy: retry

Validate before calling

// Health-check /etc/resolv.conf nameservers at startup.
func dnsReachable() error {
    r := &net.Resolver{PreferGo: true}
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    _, err := r.LookupHost(ctx, "dns.google")
    return err
}

Try / catch

// The dns resolver already retries with exponential backoff.
// Surface it as a metric; only act if it persists beyond a threshold.
if status.Code(err) == codes.DeadlineExceeded {
    metricDNSFailures.Inc()
}

Prevention

When it happens

Trigger: DNS server unreachable, query timeout, network partition, or rate limiting during A/SRV/TXT lookups. Also a misconfigured custom DNS authority (dns://authority/host) that refuses queries.

Common situations: Container /etc/resolv.conf pointing at a slow or dead nameserver; intermittent network issues; systemd-resolved returning SERVFAIL; very short DNS timeouts in embedded environments.

Related errors


AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07). Data as JSON: /api/errors/6d0f00b5583a8c1d. Report an issue: GitHub.