Zero-Downtime Deployments with Fiber's Graceful Shutdown
Learn how to use Fiber's ShutdownWithTimeout to drain in-flight requests during deployments, avoid dropped connections, and handle common pitfalls like deadlocks and context leaks.
01 Oct 2025, 08:14 UTC

The deployment problem: dropped requests during restarts
When you redeploy a Go Fiber application, the default process behavior is abrupt: the binary exits immediately, and any request still being processed is terminated. For APIs serving live traffic, this means failed requests, truncated uploads, and broken client sessions. The useful answer is to use Fiber's built-in graceful shutdown instead of letting the process die on a signal.
How Fiber's graceful shutdown works
Fiber provides app.ShutdownWithTimeout(ctx) and app.Shutdown(). When you call either method, the framework stops accepting new connections by closing its listeners, then waits for in-flight requests to complete. If you pass a context with a timeout, Fiber waits up to that duration before forcing the process to exit. This gives active handlers time to finish database writes, complete HTTP responses, and release resources.
The key constraint is that shutdown must be initiated from outside the request lifecycle—typically from a signal handler in your main function—not from inside a route handler.
Worked configuration: signal-aware shutdown
The following example assumes Go 1.22+ and Fiber v2. It starts the server in a goroutine, waits for SIGINT or SIGTERM, then initiates a graceful shutdown with a five-second budget.
package main
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New()
app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("OK")
})
// A slow handler to demonstrate in-flight request draining.
app.Get("/slow", func(c *fiber.Ctx) error {
time.Sleep(3 * time.Second)
return c.SendString("done")
})
// Start the server in a goroutine so main can handle signals.
go func() {
if err := app.Listen(":3000"); err != nil {
// Listen returns an error on shutdown; log or handle appropriately.
}
}()
// Wait for interrupt or terminate signal.
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// Graceful shutdown with a 5-second timeout.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := app.ShutdownWithTimeout(ctx); err != nil {
// Shutdown forced or timed out; log the error.
}
}
Run this locally, start a slow request with curl http://localhost:3000/slow, then press Ctrl+C. The server should stop accepting new connections, but the slow request should complete before the process exits.
Limits and common mistakes
Use the same Fiber instance
Graceful shutdown only works on the specific app instance that is listening. If you create a new Fiber app or restart the process entirely, there is nothing to drain.
Do not call Shutdown before Start
Calling app.Shutdown() before app.Listen() blocks because there are no listeners to close and no active connections to wait for. Always start the server first, then signal shutdown.
Never call Shutdown from inside a handler
If you invoke ShutdownWithTimeout from within a route handler that is still executing, Fiber waits for active requests to finish—including the very request that called shutdown. This deadlocks until the timeout expires. Always trigger shutdown from a separate goroutine or signal handler.
Timeout too short for long-running work
If your handlers routinely take longer than your shutdown timeout, those requests will be cut off when the context expires. Size the timeout to cover your slowest expected request, or implement application-level cancellation so handlers can bail out early when the context is done.
Background goroutines and middleware
Fiber only tracks requests handled by its router. Custom middleware that spawns background goroutines, or standalone workers consuming from channels, will not be waited for automatically. Pass the shutdown context to those goroutines and check ctx.Done() to exit cleanly, or you will leak resources.
Verifying the behavior
To confirm graceful shutdown is working, run the example and issue a request that sleeps for several seconds. Send SIGINT while the request is in flight. If the client receives the full response and the process exits afterward, shutdown is draining correctly. If the client sees a connection reset immediately, either shutdown was not called or the timeout was too short.
For a negative test, remove the ShutdownWithTimeout call and repeat. The default behavior terminates the process instantly, dropping the in-flight request.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.