MHSanaei/3x-ui · error

cannot resolve host %s: %w

Error message

cannot resolve host %s: %w

What it means

rejectPrivateHost wraps the net.DefaultResolver.LookupIPAddr error when the URL's hostname is a name (not an IP literal) and DNS resolution fails within the 5-second context. Typical wrapped causes: NXDOMAIN (no such host), resolver unreachable/timeout, or SERVFAIL. Because this runs under a 5s context, slow resolvers surface as 'context deadline exceeded' here.

Source

Thrown at internal/web/service/url_safety.go:73

	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := rejectPrivateHost(ctx, u.Hostname()); err != nil {
		return "", err
	}
	return clean, nil
}

func rejectPrivateHost(ctx context.Context, hostname string) error {
	if ip := net.ParseIP(hostname); ip != nil {
		if isBlockedIP(ip) {
			return fmt.Errorf("blocked private/internal address %s", ip.String())
		}
		return nil
	}
	ips, err := net.DefaultResolver.LookupIPAddr(ctx, hostname)
	if err != nil {
		return fmt.Errorf("cannot resolve host %s: %w", hostname, err)
	}
	if len(ips) == 0 {
		return fmt.Errorf("host %s has no IP addresses", hostname)
	}
	for _, ipAddr := range ips {
		if isBlockedIP(ipAddr.IP) {
			return fmt.Errorf("host %s resolves to blocked private/internal address %s", hostname, ipAddr.IP.String())
		}
	}
	return nil
}

func isBlockedIP(ip net.IP) bool {
	return netsafe.IsBlockedIP(ip)
}

View on GitHub (pinned to ad32144c42)

Solutions

  1. Verify the name resolves on the panel host itself: 'nslookup <host>' / 'getent hosts <host>'; fix resolv.conf or the firewall if not.
  2. Correct the hostname typo — the wrapped error usually contains 'no such host' for NXDOMAIN.
  3. If resolution legitimately takes long (chained resolvers), fix the resolver chain rather than raising the 5s context: this path guards live outbound requests and must stay fast.
  4. Retry once — NXDOMAIN for a just-created DNS record can be negative-cache lag (up to the TTL).

Example fix

// before
host := "healtch.example.com" // typo
clean, err := SanitizePublicHTTPURL("https://" + host + "/ping")

// after
host := "health.example.com"
clean, err := SanitizePublicHTTPURL("https://" + host + "/ping")
Defensive patterns

Strategy: retry

Validate before calling

// Resolve the hostname once up front with a tight timeout; fail fast with a clear message
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
if _, err := net.DefaultResolver.LookupHost(ctx, hostname); err != nil {
    return fmt.Errorf("hostname %q does not resolve from this host: %w", hostname, err)
}

Type guard

null

Try / catch

if err := ctxErr(ctx); err != nil { /* deadline: check resolver health, then single retry */ }
if strings.Contains(err.Error(), "no such host") { /* typo: fix name, no retry */ }

Prevention

When it happens

Trigger: Any SanitizePublicHTTPURL call whose hostname doesn't exist ('http://typo.example/'), whose DNS is broken on the panel host, or where resolution takes >5s (misconfigured resolv.conf, unreachable nameserver, DNSSEC failures).

Common situations: Typos in configured hostnames; panel host with no working /etc/resolv.conf (common in minimal containers); DNSSEC/CAA oddities; loss of egress UDP/53 in firewalled environments.

Related errors


AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15). Data as JSON: /api/errors/bbde993e846bce76. Report an issue: GitHub.