{"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":633,"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\tc.Request().Header.Set(\"X-Real-IP\", c.IP())\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":615,"sourceCodeEnd":651,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/proxy/proxy.go#L615-L651","documentation":"proxy.BalancerForward constructs a round-robin handler from a list of server URLs and panics if the slice is empty, because there is no upstream to balance across. Each server is also parsed and policy-checked at construction, so a misconfigured entry panics here too.","triggerScenarios":"Calling proxy.BalancerForward([]string{}) or proxy.BalancerForward(nil) directly when wiring a route handler. Also triggered when the slice comes from config that resolved to empty.","commonSituations":"Loading backends from service discovery that returned an empty list at startup. Passing a slice built by filtering that removed all entries. Forgetting to populate the slice in a config struct.","solutions":["Pass a non-empty slice of server URLs, e.g. []string{\"http://node1:8080\", \"http://node2:8080\"}.","Guard the call: only register the BalancerForward handler when the servers slice is non-empty.","Validate service-discovery results and fail loudly in your bootstrap code before reaching BalancerForward."],"exampleFix":"// before\napp.Get(\"/proxy/*\", proxy.BalancerForward(servers))\n// after\nif len(servers) == 0 {\n    log.Fatal(\"no upstream servers configured\")\n}\napp.Get(\"/proxy/*\", proxy.BalancerForward(servers))","handlingStrategy":"validation","validationCode":"if len(servers) == 0 {\n    log.Fatal(\"proxy: BalancerForward requires at least one server\")\n}\napp.Get(\"/proxy/*\", proxy.BalancerForward(servers))","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate service-discovery / config results for non-empty server lists at bootstrap.","Fail loudly in startup code rather than letting an empty slice reach BalancerForward."],"tags":["proxy","config","load-balancer","panic"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}