gofiber/fiber · error

ErrUpstreamHostBlocked

ErrUpstreamHostBlocked

Error message

%w: %s

What it means

Returned by validateUpstreamForBalancer at Balancer construction when a statically configured upstream host is an IP literal that falls in a blocked range (loopback, RFC1918 private, link-local, multicast, unspecified, CGNAT, or blocked IPv6 transition ranges) and AllowPrivateIPs is false.

Source

Thrown at middleware/proxy/security.go:355

}

// validateUpstreamForBalancer validates a statically configured Balancer
// upstream. It enforces the scheme allowlist and rejects IP-literal hosts
// in blocked ranges, but defers hostname resolution to the SSRF-guarded
// dialer (see newSSRFDialer). Deferring DNS keeps a transient resolver
// failure at startup from panicking the application (e.g. crash loops in
// container orchestrators) and re-checks the resolved IP on every dial,
// which also defeats DNS-rebinding.
func validateUpstreamForBalancer(raw string, policy SecurityPolicy) (*url.URL, error) {
	u, err := parseUpstreamScheme(raw, policy)
	if err != nil {
		return nil, err
	}
	if policy.AllowPrivateIPs {
		return u, nil
	}
	if ip := net.ParseIP(trimBrackets(u.Hostname())); ip != nil && isBlockedIP(ip) {
		return nil, fmt.Errorf("%w: %s", ErrUpstreamHostBlocked, ip)
	}
	return u, nil
}

// schemeAllowed reports whether scheme is on the allowlist. An empty
// allowlist falls back to the secure defaults.
func schemeAllowed(scheme string, allowed []string) bool {
	if scheme == "" {
		return false
	}
	if len(allowed) == 0 {
		allowed = defaultAllowedSchemes
	}
	for _, s := range allowed {
		if utils.EqualFold(s, scheme) {
			return true
		}
	}

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. If the private backend is intentional and trusted, enable AllowPrivateIPs:true on the SecurityPolicy (understand it widens SSRF exposure).
  2. Otherwise point the Balancer at a public IP or resolvable public hostname.
  3. Re-check the server list for accidentally pasted loopback/metadata addresses.

Example fix

// before: private IP blocked by default SSRF policy
balancer := proxy.Balancer(proxy.Config{
    Servers: []string{"http://10.0.0.5:8080"},
})

// after: explicitly opt in for a trusted internal backend
policy := proxy.DefaultSecurityPolicy()
policy.AllowPrivateIPs = true
balancer := proxy.Balancer(proxy.Config{
    Servers:        []string{"http://10.0.0.5:8080"},
    SecurityPolicy: &policy,
})
Defensive patterns

Strategy: validation

Validate before calling

// At config time, decide policy for private backends explicitly.
func balancerPolicy(servers []string, allowPrivate bool) *proxy.SecurityPolicy {
    p := proxy.DefaultSecurityPolicy()
    p.AllowPrivateIPs = allowPrivate // true only for trusted internal backends
    return &p
}

Try / catch

// validateUpstreamForBalancer runs at Balancer construction; wrap New
// and report the error clearly at startup.
b, err := proxy.Balancer(proxy.Config{Servers: svrs, SecurityPolicy: &p})
if err != nil {
    if errors.Is(err, proxy.ErrUpstreamHostBlocked) {
        log.Fatal().Err(err).Msg("backend IP blocked; set AllowPrivateIPs if trusted")
    }
    log.Fatal().Err(err).Msg("balancer init failed")
}

Prevention

When it happens

Trigger: A Balancer server entry is a numeric IP like 127.0.0.1, 10.0.0.5, 169.254.169.254, or 100.64.0.1 while AllowPrivateIPs is false (the default). The IP-literal shortcut at security.go:354 rejects it without DNS.

Common situations: Pointing a Balancer at an internal/private service during local development or inside a cluster; using a cloud metadata IP by mistake; forgetting to set AllowPrivateIPs when the backend genuinely is on a private network.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/cee29237fc14327e.json. Report an issue: GitHub.