Go Fiber v2 Buffer Reuse: When Request Data Becomes Empty or Corrupted After Handler Return
Fiber v2 reuses fasthttp buffers by default, so c.Body() and c.Query() can appear empty after the handler returns. This guide diagnoses the condition, checks the cause, and applies targeted fixes.
15 Jul 2025, 11:06 UTC

Recognizable condition
You read c.Body(), c.Query(), or c.Params() inside a handler and get the expected values. After the handler returns—especially in a deferred function, a goroutine, or the next middleware—the same accessor returns an empty string or stale data from a different request.
Why it happens
Fiber is built on fasthttp, which reuses underlying byte buffers across requests for performance. The strings returned by context accessors are valid only until the handler completes. Retaining a reference without copying is a use-after-free-style bug at the Go level.
Diagnostic table
| Symptom | Likely cause |
|---|---|
c.Body() returns "" after handler |
Buffer recycled before deferred code runs |
c.Query("id") differs between concurrent requests |
Shared buffer interleaving under load |
| Handler logs correct data, but later middleware sees empty | Context pointer points to pooled buffer |
Ordered checks
- Confirm the data is correct inside the handler by printing it immediately.
- Add a deferred function after the handler returns and access the same accessor—does it vanish? That confirms the buffer lifecycle issue.
- Check if any middleware or goroutine stores
*fiber.Ctxor its derived strings beyond the handler scope. - Run a concurrent load test (e.g.,
hey -n 200 -c 50 https://localhost:8080/api) with distinct payloads. Interleaved or empty results confirm buffer reuse.
Fixes tied to findings
Targeted copy (preferred for hot paths)
Extract the value you need once, early in the handler, and work with the copy:
// Inside handler
body := c.Body()
_ = body // use body; don't return c.Body() later
If you must retain it, copy explicitly:
safeBody := string(c.Body())
Enable immutable mode (global safeguard)
Create the app with fiber.Config{Immutable: true}. This makes all context accessors return fresh copies per request, eliminating the window of vulnerability:
app := fiber.New(fiber.Config{
Immutable: true,
// other config
})
Trade-off: Each request incurs one extra allocation per retained string. On ultra-high-throughput paths this can reduce throughput by a few percent. Use targeted copies when the hot path serves simple JSON payloads, and enable Immutable for handlers that retain request data across goroutines or deferred calls.
Escalation criteria
- If copying all retained values stops the corruption, you’re done—no framework change needed.
- If corruption persists, inspect custom middleware that stores
*fiber.Ctxor keeps references to the context itself. Immutable does not protect middleware that holds the context pointer. - Only then check the
fasthttpversion pinned ingo.modor open a framework bug report with a reproducible test case.
Verification you can run
Write a test that fires concurrent requests with unique bodies, stores c.Body() results in a slice, and asserts each stored value matches its request after all handlers complete. Example skeleton:
func TestBufferReuse(t *testing.T) {
app := fiber.New(fiber.Config{})
client := http.Client{Timeout: 5 * time.Second}
var wg sync.WaitGroup
results := make([]string, 10)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(idx int) {
defer wg.Done()
req, _ := http.NewRequest("POST", "http://localhost:8080", bytes.NewReader([]byte(`{"id":" + strconv.Itoa(idx) + `}`)))
res, err := client.Do(req)
if err != nil {
t.Error(err)
return
}
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
results[idx] = string(body)
}(i)
}
wg.Wait()
// assert all 10 bodies are distinct
for i, expected := range results {
if results[i] != expected {
t.Errorf("request %d: expected %q, got %q", i, expected, results[i])
}
}
}
Run with go test -race and a load generator to surface unsafe retention of context-derived strings. Toggle Immutable: true and confirm the corruption disappears, then reintroduce targeted copies to restore performance.
Cautions
Immutable: truedoes not protect values stored by third-party middleware that keep references to the context itself.- Never pass
*fiber.Ctxinto goroutines; extract needed values first, as the context is pooled and reset. - This behavior applies to Fiber v2. Fiber v3 may change APIs and defaults, so confirm the major version in
go.modbefore applying fixes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.