MHSanaei/3x-ui · error
no usable address for %s
Error message
no usable address for %s
What it means
In netsafe's guarded dial loop, 'no usable address for %s' is the fallback when lastErr is still nil after iterating every resolved IP — which can only happen when the IP list itself was empty (a DNS answer with zero records). If any dial failed, that dial error is returned instead; if any IP was blocked, the block error is returned instead. So this specific message means: host resolved successfully but yielded no addresses at all.
Source
Thrown at internal/util/netsafe/netsafe.go:58
ips, err = net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, err
}
}
var lastErr error
for _, ipAddr := range ips {
if !allowPrivate && IsBlockedIP(ipAddr.IP) {
lastErr = fmt.Errorf("blocked private/internal address %s", ipAddr.IP)
continue
}
conn, derr := defaultDialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))
if derr == nil {
return conn, nil
}
lastErr = derr
}
if lastErr == nil {
lastErr = fmt.Errorf("no usable address for %s", host)
}
return nil, lastErr
}
var hostnamePattern = regexp.MustCompile(`^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$`)
func NormalizeHost(addr string) (string, error) {
addr = strings.TrimSpace(addr)
if addr == "" {
return "", fmt.Errorf("address is required")
}
if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
addr = addr[1 : len(addr)-1]
}
if ip := net.ParseIP(addr); ip != nil {
return ip.String(), nil
}
if len(addr) > 253 || !hostnamePattern.MatchString(addr) {View on GitHub (pinned to ad32144c42)
Solutions
- Verify the host actually has A/AAAA records: `dig +short HOST A` and `dig +short HOST AAAA`.
- If the domain's records were removed, restore them or update the configured address.
- If a CNAME, ensure the target resolves.
- Switch to a resolver without filtering if your DNS provider returns empty answers for blocked domains.
Example fix
// before conn, err := safeDial(ctx, "tcp", "stale.example.com:443") // no usable address // after: fix DNS first // $ dig +short stale.example.com A -> empty, repoint or fix records conn, err := safeDial(ctx, "tcp", "live.example.com:443")
Defensive patterns
Strategy: validation
Validate before calling
func resolvesToAddresses(host string) bool {
ips, err := net.LookupIP(host)
return err == nil && len(ips) > 0
} Try / catch
conn, err := safeDial(ctx, network, addr)
if err != nil && strings.Contains(err.Error(), "no usable address") {
return fmt.Errorf("host %s resolves to no A/AAAA records; check DNS", hostOnly(addr))
} Prevention
- Pre-check DNS with LookupIP before dialing user-supplied hosts.
- Surface DNS diagnostics in error UIs instead of retrying blindly.
- Alert when a configured hostname's record count drops to zero.
When it happens
Trigger: A hostname with a DNS response containing zero A/AAAA records (e.g. a name that exists only as TXT/CNAME-without-target, or NODATA), passed to the safe dialer.
Common situations: Typo'd subscription host that still has a DNS zone; a domain whose records were removed but zone remains; a stale node address; DNS middleware (some resolvers) returning success-with-no-answers for filtered domains.
Related errors
- blocked private/internal address %s
- cannot resolve host %s: %w
- host %s has no IP addresses
- XUI_DB_TYPE=postgres but XUI_DB_DSN is empty
- destination DSN is required
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/7b7e91b402fafa38.
Report an issue: GitHub.