{"record":{"id":"14785e10e290abfe","repo":"gofiber/fiber","slug":"servers-cannot-be-empty-14785e","errorCode":null,"errorMessage":"Servers cannot be empty","messagePattern":"Servers cannot be empty","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/proxy/proxy.go","lineNumber":654,"sourceCode":"\t\t\treturn r.pool[index]\n\t\t}\n\t}\n}\n\n// BalancerForward Forward performs the given http request with round robin algorithm to server and fills the given http response.\n// This method will return a fiber.Handler.\n//\n// As with DomainForward, every server is parsed and policy-checked at\n// handler construction. A misconfigured entry panics at startup.\n//\n// SSRF note: despite the name, this helper dispatches through the\n// shared/user-supplied client rather than a Balancer HostClient, but it\n// gets the same protection as Do — up-front host validation plus the\n// dial-time validated-IP guard on the dispatching client when\n// AllowPrivateIPs is false.\nfunc BalancerForward(servers []string, clients ...*fasthttp.Client) fiber.Handler {\n\tif len(servers) == 0 {\n\t\tpanic(\"Servers cannot be empty\")\n\t}\n\tpolicy := currentSecurityPolicy()\n\tbases := make([]*url.URL, len(servers))\n\tfor i, s := range servers {\n\t\tbase, err := validateUpstream(s, policy)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tbases[i] = base\n\t}\n\tr := &urlRoundrobin{pool: bases}\n\treturn func(c fiber.Ctx) error {\n\t\tbase := r.get()\n\t\tsetRealIP(c)\n\t\treturn doActionWithPolicy(c, joinUpstreamPath(base, c.OriginalURL()), currentSecurityPolicy(),\n\t\t\tfunc(cli *fasthttp.Client, req *fasthttp.Request, resp *fasthttp.Response, _ *url.URL) error {\n\t\t\t\treturn cli.Do(req, resp)\n\t\t\t}, clients...)","sourceCodeStart":636,"sourceCodeEnd":672,"githubUrl":"https://github.com/gofiber/fiber/blob/a105acad6c1e4576a77f01e02973f67e962bb58d/middleware/proxy/proxy.go#L636-L672","documentation":"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.","triggerScenarios":"app.BalancerForward([]string{}), or app.BalancerForward(nil), or app.BalancerForward(servers) where servers was filtered down to zero elements.","commonSituations":"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.","solutions":["Guard len(servers) > 0 before calling BalancerForward, and fail configuration loudly when the list is empty.","Ensure config sources require at least one entry at load time."],"exampleFix":"// before\nservers := discovery.GetUpstreams() // []string{} when discovery is empty\napp.BalancerForward(servers) // panics\n\n// after\nservers := discovery.GetUpstreams()\nif len(servers) == 0 {\n    log.Fatal(\"no upstreams discovered\")\n}\napp.BalancerForward(servers)","handlingStrategy":"validation","validationCode":"func mustBalancerForward(servers []string) fiber.Handler {\n    if len(servers) == 0 {\n        log.Fatal(\"BalancerForward requires at least one server\")\n    }\n    return proxy.BalancerForward(servers)\n}","typeGuard":"func hasServers(servers []string) bool { return len(servers) > 0 }","tryCatchPattern":null,"preventionTips":["Never pass a discovery/config slice directly to BalancerForward without a length check.","Fail configuration loudly when the upstream list is empty rather than crashing inside the handler."],"tags":["proxy","config","panic"],"backgroundTag":null,"analyzedSha":"a105acad6c1e4576a77f01e02973f67e962bb58d","analyzedAt":"2026-08-11T17:33:26.942Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}