MHSanaei/3x-ui · warning
blocked private/internal address %s
Error message
blocked private/internal address %s
What it means
Thrown by rejectPrivateHost (reached via SanitizePublicHTTPURL, the variant that enforces SSRF rules at request time) when the URL's hostname is a literal IP that falls in a blocked range. netsafe.IsBlockedIP blocks loopback, RFC1918/private, link-local, multicast, unspecified, and typically IPv6 ULA/link-local ranges, so the panel never makes outbound requests into its own network stack.
Source
Thrown at internal/web/service/url_safety.go:67
if allowPrivate {
return clean, nil
}
u, err := url.Parse(clean)
if err != nil {
return "", err
}
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
}
View on GitHub (pinned to ad32144c42)
Solutions
- Point the URL at a genuinely public host; if the target is an internal service, do not route it through the SSRF-checked outbound path — expose it via a purpose-built config (e.g. a local inbound) instead.
- If the destination is public but accessed via a private NAT address, use its public DNS name (note: the DNS-resolved form is also checked, see the 'resolves to blocked' sibling error).
- Never try to bypass with decimal/octal IP encodings — url parsing normalizes most, and the resolve-time check catches the rest; treat a genuine need for private targets as a design change, not a workaround.
Example fix
// before u := "http://127.0.0.1:9090/health" // health-check to a local sidecar // after — use the service's public name, or move the check off the SSRF-guarded path u := "https://sidecar.example.com/health"
Defensive patterns
Strategy: validation
Validate before calling
// Reject private IP literals before they ever reach the outbound path
func isSafeOutboundHost(host string) bool {
if ip := net.ParseIP(host); ip != nil {
return !netsafe.IsBlockedIP(ip)
}
return true // names get the resolve-time check
} Type guard
null
Try / catch
if _, err := service.SanitizePublicHTTPURL(u); err != nil {
if strings.Contains(err.Error(), "blocked private/internal address") {
// configuration error: switch to a public target; never retry as-is
}
} Prevention
- Keep all health-check/URL-test targets on public DNS names by convention.
- Never point outbound-checked URLs at 169.254.169.254 or RFC1918 space; use dedicated internal config paths instead.
When it happens
Trigger: Passing 'http://127.0.0.1:port/...', 'http://10.0.0.5/...', 'http://192.168.1.1/', 'http://[::1]/...', or 'http://169.254.169.254/' (cloud metadata) as an outbound URL to any feature that routes through SanitizePublicHTTPURL.
Common situations: Admins pointing a health-check/URL-test at a local reverse proxy or another service on the same box; attempts (accidental or malicious) to reach Docker's 172.17.x.x bridge or the cloud metadata IP; localhost shortcuts copied from dev configs into production panel settings.
Related errors
- blocked private/internal address %s
- cannot resolve host %s: %w
- host %s resolves to blocked private/internal address %s
- stopped after 10 redirects
- unsupported URL scheme %q
AI-assisted analysis of MHSanaei/3x-ui@ad32144c42 (2026-08-15).
Data as JSON: /api/errors/03dd1ba00b7a07fc.
Report an issue: GitHub.