ory/hydra · error

DNS lookup timed out

Error message

DNS lookup timed out

What it means

ipx.IsAssociatedIPAllowed resolves hostnames before checking IP rules; it bounds DNS resolution with a 2-second context whose cause is errors.New("DNS lookup timed out"). When resolution exceeds that deadline (or fails with a timeout-class DNS error), the context's cause surfaces this message so callers can distinguish slow/unreachable DNS from disallowed destinations.

Source

Thrown at oryx/ipx/ip_validator.go:67

	if parsed, err := url.ParseRequestURI(ipOrHostnameOrURL); err == nil {
		ipOrHostname = parsed.Hostname()
	}

	if ip, err := netip.ParseAddr(ipOrHostname); err == nil {
		if !allowed(ip) {
			return errors.Errorf("ip %s is not a permitted destination", ip)
		}
		return nil
	}

	if addr, err := netip.ParseAddrPort(ipOrHostnameOrURL); err == nil {
		if !allowed(addr.Addr()) {
			return errors.Errorf("ip %s is not a permitted destination", addr.Addr())
		}
		return nil
	}

	ctx, cancel := context.WithTimeoutCause(ctx, 2*time.Second, errors.New("DNS lookup timed out"))
	defer cancel()
	ips, err := resolver.LookupNetIP(ctx, "ip", ipOrHostname)
	if err != nil {
		if dnsErr, ok := stderrors.AsType[*net.DNSError](err); ok {
			// Copy the `*net.DNSError` before masking `Server` to avoid a data
			// race: the DNS resolver uses `singleflight` to deduplicate
			// concurrent lookups, so multiple goroutines may receive the same
			// `*net.DNSError` pointer. Mutating it in place races with concurrent
			// readers (e.g. the `otelhttp` `dnsDone` trace hook).
			maskedDNS := *dnsErr
			maskedDNS.Server = "" // Mask our DNS server's IP address.
			return errors.Wrapf(&maskedDNS, "failed to resolve %s", ipOrHostnameOrURL)
		}
	}

	for _, ip := range ips {
		if !allowed(ip) {
			return errors.Wrapf(&net.DNSError{

View on GitHub (pinned to 4174065ffb)

Solutions

  1. Retry the operation — the failure may be transient resolver latency; the 2s bound is fixed, so retrying is the primary mitigation.
  2. Resolve hostnames with your own resolver (with a larger deadline) and pass the resolved IP instead of the hostname, skipping the lookup path.
  3. Ensure DNS infrastructure is healthy: correct /etc/resolv.conf, reachable upstream resolvers, sane ndots/search config in containers.
  4. Allowlist known internal hostnames as IPs ahead of time so the lookup path is not exercised at request time.

Example fix

// before
allowed, err := ipx.IsAssociatedIPAllowedWhenSet(ctx, "internal.svc.cluster.local", set)
// after
ips, err := myResolver.LookupHost(ctx2, "internal.svc.cluster.local") // own, longer deadline
allowed, err = ipx.IsAssociatedIPAllowedWhenSet(ctx, ips[0], set)
Defensive patterns

Strategy: retry

Validate before calling

// pre-resolve with your own deadline if DNS is slow:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", hostname)
if err != nil { return fmt.Errorf("unresolvable host %q: %w", hostname, err) }
// then call IsAssociatedIPAllowed with ips[0].String()

Type guard

func isDNSTimeout(err error) bool {
  var dnsErr *net.DNSError
  return errors.As(err, &dnsErr) && dnsErr.IsTimeout
}

Try / catch

allowed, err := ipx.IsAssociatedIPAllowedWhenSet(ctx, host, set)
if err != nil {
  if strings.Contains(err.Error(), "DNS lookup timed out") || isDNSTimeout(err) {
    return retry.WithBackoff(func() error {
      var e error
      allowed, e = ipx.IsAssociatedIPAllowedWhenSet(ctx, host, set)
      return e
    })
  }
  return err
}

Prevention

When it happens

Trigger: Calling IsAssociatedIPAllowed (directly or via IsAssociatedIPAllowedWhenSet, or SSRF-check helpers) with ipOrHostname set to a hostname whose DNS resolution does not complete within 2 seconds — e.g. an internal name only resolvable by a slow corporate resolver, or a resolver outage.

Common situations: SSRF-checking a webhook URL whose host is an internal .local/cluster-internal name that public resolvers cannot answer; DNS infrastructure degradation (resolver timeout, ndots search-domain churn in k8s); IPv6-only hostnames with broken AAAA paths.

Understand the failure class

Related errors


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/6aed7b7be9b22ef2. Report an issue: GitHub.