gofiber/fiber · error

ErrUpstreamSchemeNotAllowed

ErrUpstreamSchemeNotAllowed

Error message

proxy: upstream scheme is not allowed

What it means

proxy.Balancer validates each entry in Config.Servers via validateUpstreamForBalancer, which calls parseUpstreamScheme and enforces a scheme allowlist. If the URL's scheme is not allowed (default allowlist is http/https), it returns ErrUpstreamSchemeNotAllowed wrapped as fmt.Errorf("%w: %q", ErrUpstreamSchemeNotAllowed, scheme); Balancer then panics with that error. This is an SSRF-defense measure: non-HTTP schemes (gopher, file, etc.) must never be proxied.

Solutions

  1. Use http:// or https:// schemes for all Servers entries.
  2. If you legitimately need another scheme, configure a SecurityPolicy with an AllowedSchemes list that includes it (and review the SSRF implications).
  3. Ensure every server string has an explicit scheme prefix — reconstruct with fmt.Sprintf("http://%s", host) if needed.

Example fix

// before
app.Use(proxy.Balancer(proxy.Config{
    Servers: []string{"gopher://cache:6379"},
}))

// after
app.Use(proxy.Balancer(proxy.Config{
    Servers: []string{"http://cache:6379"},
}))
Defensive patterns

Strategy: validation

Validate before calling

var allowedSchemes = map[string]struct{}{"http": {}, "https": {}}

func validateUpstreamSchemes(servers []string) error {
    for _, s := range servers {
        u, err := url.Parse(s)
        if err != nil {
            return fmt.Errorf("bad upstream %q: %w", s, err)
        }
        if _, ok := allowedSchemes[u.Scheme]; !ok {
            return fmt.Errorf("%w: %q", proxy.ErrUpstreamSchemeNotAllowed, u.Scheme)
        }
    }
    return nil
}

Type guard

func isAllowedUpstreamScheme(scheme string) bool {
    _, ok := map[string]struct{}{"http": {}, "https": {}}[strings.ToLower(scheme)]
    return ok
}

Prevention

When it happens

Trigger: A Servers entry like "gopher://internal:6379", "file:///etc/passwd", "ftp://host", or an entry missing a scheme (which parses with Scheme="" and is rejected). Also triggered by a custom SecurityPolicy whose AllowedSchemes omits the scheme you actually use.

Common situations: Pointing the balancer at a non-HTTP backend by mistake; a misconfigured custom SecurityPolicy that narrows AllowedSchemes below what your upstreams need; a server string that lost its scheme during templating ("upstream:8080" instead of "http://upstream:8080").

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/332fedb494d62074. Report an issue: GitHub.

Appendix: source

Thrown at middleware/proxy/proxy.go:41

// Balancer creates a load balancer among multiple upstream servers
func Balancer(config ...Config) fiber.Handler {
	// Set default config
	cfg := configDefault(config...)
	policy := resolvePolicy(cfg.SecurityPolicy)

	// Load balanced client
	lbc := &fasthttp.LBClient{}
	// Note that Servers, Timeout, WriteBufferSize, ReadBufferSize and TLSConfig
	// will not be used if the client are set.
	if cfg.Client == nil {
		// Set timeout
		lbc.Timeout = cfg.Timeout
		// Validate each upstream against the configured policy and build
		// a HostClient per server.
		for _, server := range cfg.Servers {
			u, err := validateUpstreamForBalancer(server, policy)
			if err != nil {
				panic(err)
			}

			client := &fasthttp.HostClient{
				NoDefaultUserAgentHeader: true,
				DisablePathNormalizing:   true,
				Addr:                     u.Host,
				MaxConns:                 cfg.MaxConnsPerHost,

				ReadBufferSize:  cfg.ReadBufferSize,
				WriteBufferSize: cfg.WriteBufferSize,

				TLSConfig: secureTLSConfig(cfg.TLSConfig),

				DialDualStack: cfg.DialDualStack,

				MaxResponseBodySize: cfg.MaxResponseBodySize,
			}
			if u.Scheme == schemeHTTPS {

View on GitHub (pinned to a105acad6c)