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
- Verify the name resolves on the panel host itself: 'nslookup <host>' / 'getent hosts <host>'; fix resolv.conf or the firewall if not.
- Correct the hostname typo — the wrapped error usually contains 'no such host' for NXDOMAIN.
- 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.
- 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
- Smoke-test configured hostnames with getent/dig when saving settings.
- Keep the panel host's resolv.conf pointed at fast, reachable resolvers.
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
- blocked private/internal address %s
- host %s has no IP addresses
- blocked private/internal address %s
- no usable address for %s
- stopped after 10 redirects
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/bbde993e846bce76.
Report an issue: GitHub.