gofiber/fiber · error

proxy: WithClient requires a non-nil *fasthttp.Client

Error message

proxy: WithClient requires a non-nil *fasthttp.Client

What it means

proxy.WithClient installs a user-supplied *fasthttp.Client as the package-level proxy client and wires the dial-time SSRF guard onto it via ConfigureClient. Passing nil has no valid meaning (there is no "unset" — simply omit the call to keep the default guarded client), so WithClient panics immediately. The guard is installed before the client is stored, so any later proxy.Do/Forward/DomainForward/BalancerForward dispatch through it re-validates the resolved IP at connect time.

Solutions

  1. Construct the *fasthttp.Client before calling WithClient, and only call it when the reference is non-nil.
  2. If you have no custom client requirements, do not call WithClient at all — the package default client is already guarded.
  3. Guard the call site: if cli != nil { proxy.WithClient(cli) }.

Example fix

// before
var cli *fasthttp.Client
if useCustom {
    cli = buildClient()
}
proxy.WithClient(cli) // panics when useCustom is false

// after
if cli != nil {
    proxy.WithClient(cli)
}
Defensive patterns

Strategy: validation

Validate before calling

func registerProxyClient(cli *fasthttp.Client) {
    if cli == nil {
        log.Println("proxy: skipping WithClient, client is nil")
        return
    }
    proxy.WithClient(cli)
}

Type guard

func isNonNilClient(cli *fasthttp.Client) bool { return cli != nil }

Prevention

When it happens

Trigger: proxy.WithClient(nil) — typically a variable that was never initialized, e.g. proxy.WithClient(cli) where cli is a nil *fasthttp.Client returned from a constructor that failed.

Common situations: Conditional client construction where the variable stays nil on an error path; a refactor that moved client creation into a helper returning nil; a test stub that forgot to instantiate the client.

Related errors


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

Appendix: source

Thrown at middleware/proxy/proxy.go:234

		return // already guarded (directly or composed with a user hook)
	}
	cli.ConfigureClient = (&guardedConfigureClient{orig: existing}).run
}

func init() {
	ensureClientGuarded(defaultClient)
	client.Store(defaultClient)
}

// WithClient sets the global proxy client.
// This function should be called before Do and Forward — doing so installs
// the dial-time SSRF guard (via the client's ConfigureClient hook,
// composing with any hook it already carries) before the client dials any
// host, so requests dispatched through it re-validate the resolved IP at
// connect time, matching the default client's behavior.
func WithClient(cli *fasthttp.Client) {
	if cli == nil {
		panic("proxy: WithClient requires a non-nil *fasthttp.Client")
	}

	ensureClientGuarded(cli)
	client.Store(cli)
}

// realIPHeader is the field the proxy overwrites with the peer address Fiber
// derived, so an upstream reading it sees this hop's view rather than the
// client's claim.
const realIPHeader = "X-Real-IP"

// setRealIP replaces every inbound X-Real-IP field line with the peer address
// Fiber derived. Set alone overwrites the first and leaves the rest, so a client
// sending it twice kept a value of its own on the wire.
func setRealIP(c fiber.Ctx) {
	// Resolve the address before deleting anything: with ProxyHeader set to
	// "X-Real-IP", c.IP() reads the very header being replaced, and deleting first
	// handed the upstream an empty value.

View on GitHub (pinned to a105acad6c)