gofiber/fiber · error

Servers cannot be empty

Error message

Servers cannot be empty

What it means

BalancerForward requires at least one upstream server in its servers slice; passing an empty slice (or nil) is treated as a programmer error and panics with "Servers cannot be empty". This is a distinct, earlier guard from the per-server validateUpstream loop that follows it.

Solutions

  1. Guard len(servers) > 0 before calling BalancerForward, and fail configuration loudly when the list is empty.
  2. Ensure config sources require at least one entry at load time.

Example fix

// before
servers := discovery.GetUpstreams() // []string{} when discovery is empty
app.BalancerForward(servers) // panics

// after
servers := discovery.GetUpstreams()
if len(servers) == 0 {
    log.Fatal("no upstreams discovered")
}
app.BalancerForward(servers)
Defensive patterns

Strategy: validation

Validate before calling

func mustBalancerForward(servers []string) fiber.Handler {
    if len(servers) == 0 {
        log.Fatal("BalancerForward requires at least one server")
    }
    return proxy.BalancerForward(servers)
}

Type guard

func hasServers(servers []string) bool { return len(servers) > 0 }

Prevention

When it happens

Trigger: app.BalancerForward([]string{}), or app.BalancerForward(nil), or app.BalancerForward(servers) where servers was filtered down to zero elements.

Common situations: A dynamic server list sourced from service discovery or config that came back empty; a strings.Split on an empty env var yielding [""] that was then filtered to []; a test that forgot to populate the slice.

Related errors


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

Appendix: source

Thrown at middleware/proxy/proxy.go:654

			return r.pool[index]
		}
	}
}

// BalancerForward Forward performs the given http request with round robin algorithm to server and fills the given http response.
// This method will return a fiber.Handler.
//
// As with DomainForward, every server is parsed and policy-checked at
// handler construction. A misconfigured entry panics at startup.
//
// SSRF note: despite the name, this helper dispatches through the
// shared/user-supplied client rather than a Balancer HostClient, but it
// gets the same protection as Do — up-front host validation plus the
// dial-time validated-IP guard on the dispatching client when
// AllowPrivateIPs is false.
func BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handler {
	if len(servers) == 0 {
		panic("Servers cannot be empty")
	}
	policy := currentSecurityPolicy()
	bases := make([]*url.URL, len(servers))
	for i, s := range servers {
		base, err := validateUpstream(s, policy)
		if err != nil {
			panic(err)
		}
		bases[i] = base
	}
	r := &urlRoundrobin{pool: bases}
	return func(c fiber.Ctx) error {
		base := r.get()
		setRealIP(c)
		return doActionWithPolicy(c, joinUpstreamPath(base, c.OriginalURL()), currentSecurityPolicy(),
			func(cli *fasthttp.Client, req *fasthttp.Request, resp *fasthttp.Response, _ *url.URL) error {
				return cli.Do(req, resp)
			}, clients...)

View on GitHub (pinned to a105acad6c)