gofiber/fiber · error · ErrUpstreamHostBlocked

proxy: upstream host resolves to a blocked address

Error message

proxy: upstream host resolves to a blocked address

What it means

Returned as ErrUpstreamHostBlocked by the proxy middleware when an upstream URL resolves to an address inside a blocked range (loopback, RFC 1918 private, link-local including 169.254.169.254 cloud-metadata, multicast, unspecified, or RFC 6598 CGNAT) and SecurityPolicy.AllowPrivateIPs is false. The check runs both up front (validateUpstream/validateHostForSSRF) and again at dial time (newSSRFDialer / guardedDial) to defeat DNS-rebinding. It is Fiber's primary SSRF defense, on by default.

Source

Thrown at middleware/proxy/security.go:60

// allocate []byte("https") on every hop.
var httpsSchemeBytes = []byte(schemeHTTPS)

// Sentinel errors returned when an upstream target violates the configured
// proxy security policy.
var (
	// ErrUpstreamSchemeNotAllowed is returned when the proxied URL uses a
	// scheme outside the configured allowlist (default: http, https).
	ErrUpstreamSchemeNotAllowed = errors.New("proxy: upstream scheme is not allowed")

	// ErrUpstreamHostInvalid is returned when the proxied URL is missing a
	// host or cannot be parsed.
	ErrUpstreamHostInvalid = errors.New("proxy: upstream host is empty or invalid")

	// ErrUpstreamHostBlocked is returned when the proxied URL resolves to
	// an address inside a blocked range (loopback, RFC 1918 private,
	// link-local, multicast, unspecified, or CGNAT) and AllowPrivateIPs
	// is false.
	ErrUpstreamHostBlocked = errors.New("proxy: upstream host resolves to a blocked address")

	// ErrRedirectDowngrade is returned when DoRedirects encounters a
	// redirect from an HTTPS upstream to a plaintext HTTP target and
	// AllowHTTPSDowngrade is false.
	ErrRedirectDowngrade = errors.New("proxy: HTTPS to HTTP redirect blocked")
)

// SecurityPolicy controls runtime security restrictions applied to the
// proxy.Do, proxy.Forward, proxy.DoRedirects, proxy.DoTimeout, and
// proxy.DoDeadline runtime helpers as well as Balancer instances that
// do not supply their own policy via Config.SecurityPolicy.
type SecurityPolicy struct {
	// AllowedSchemes restricts the URL schemes accepted as upstream
	// targets. Empty defaults to []string{schemeHTTP, schemeHTTPS}.
	AllowedSchemes []string

	// AllowPrivateIPs allows upstream hosts to resolve to loopback,
	// private (RFC 1918), link-local, multicast, unspecified, or CGNAT

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. If the internal target is trusted and intentional, opt in with SecurityPolicy{AllowPrivateIPs: true} via Config.SecurityPolicy or proxy.WithSecurityPolicy() — but only for that scope, since it re-enables SSRF to cloud-metadata/internal services.
  2. Use a public hostname or public IP for the upstream so resolution stays outside blocked ranges.
  3. Keep AllowPrivateIPs false and never forward client-supplied URLs through proxy.Do; validate/allowlist the host before proxying.
  4. For a Balancer with a custom Config.Client (*fasthttp.LBClient), note the dialers are not guarded by Fiber — install your own SSRF dialer there.

Example fix

// before
app.Use(proxy.New("http://localhost:8080"))

// after — explicit opt-in for a trusted internal target
app.Use(proxy.New("http://localhost:8080", proxy.Config{
    SecurityPolicy: &proxy.SecurityPolicy{AllowPrivateIPs: true},
}))
Defensive patterns

Strategy: validation

Validate before calling

// Resolve and pre-check the upstream host against the same blocklist logic.
func isBlocked(host string) bool {
    ips, err := net.DefaultResolver.LookupIPAddr(context.Background(), host)
    if err != nil {
        return true // treat unresolvable as blocked
    }
    for _, ip := range ips {
        if ip.IP.IsLoopback() || ip.IP.IsPrivate() || ip.IP.IsLinkLocalUnicast() ||
            ip.IP.IsLinkLocalMulticast() || ip.IP.IsUnspecified() || ip.IP.IsMulticast() {
            return true
        }
        if v4 := ip.IP.To4(); v4 != nil && v4[0] == 100 && v4[1] >= 64 && v4[1] <= 127 {
            return true // CGNAT
        }
    }
    return false
}

if isBlocked(targetHost) {
    // refuse to proxy, or set AllowPrivateIPs if trusted
}

Try / catch

if _, err := proxy.Do(req, target); err != nil {
    if errors.Is(err, proxy.ErrUpstreamHostBlocked) {
        // upstream resolved to a private/loopback/metadata address
        return fiber.NewError(fiber.StatusBadGateway, "upstream not reachable")
    }
    return err
}

Prevention

When it happens

Trigger: Calling proxy.Do/Forward/DoRedirects/DoTimeout/DoDeadline, or serving through a Balancer, with a target host that resolves to a private/loopback IP — e.g. proxying to 'http://localhost:8080', 'http://10.0.0.5', 'http://169.254.169.254' (AWS metadata), or a hostname whose DNS A record returns 127.0.0.1. Also triggered if a client-controlled URL (request param) is forwarded without filtering.

Common situations: Dev/staging where the upstream is an internal service (e.g. proxying to a localhost backend during local development); SSRF hardening kicking in after upgrading Fiber to a version that made AllowPrivateIPs default false; forwarding user-supplied URLs in an image-proxy/url-preview feature; multi-container setups where the upstream is on a private docker network.

Related errors


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