Graceful Shutdown in Fiber v2: A Practical Guide for Containerized Go Apps
Learn how to implement a graceful shutdown in Fiber v2 using app.ShutdownWithTimeout, OS signal handling, and container orchestration best practices. The guide walks through code, tests, and recovery options to keep your Go web service reliable during pod termination.
01 Jul 2026, 16:08 UTC

Desired Outcome
When your Fiber application receives a SIGINT or SIGTERM (the signals Kubernetes or Docker sends during a pod termination), you want the server to stop accepting new connections, allow in‑flight request handlers to finish within a configurable deadline, and then exit cleanly. This avoids dropped responses, ensures metrics are flushed, and lets orchestrators finish draining traffic before the pod is killed.
Prerequisites
- A Go module with
github.com/gofiber/fiber/v2pinned ingo.mod(e.g.,v2.78.0). - An HTTP route that does something measurable, such as sleeping for a few seconds.
- Basic familiarity with Go’s
contextpackage and OS signal handling. - Running the app in a container or on a host where you can send
SIGTERM(e.g.,kill -TERM $(pgrep -f myapp)).
Focused Procedure
Set up the Fiber app in
main.go.package main import ( "log" "time" "os" "os/signal" "syscall" "context" "github.com/gofiber/fiber/v2" ) func main() { // Create the app instance. app := fiber.New() // Register a sample route. app.Get("/slow", func(c *fiber.Ctx) error { // Simulate work that takes 5 seconds. time.Sleep(5 * time.Second) return c.SendString("finished") }) // Run the server in a goroutine so the main goroutine can wait on signals. go func() { if err := app.Listen(":3000"); err != nil { // Listen returns ErrServerClosed on graceful shutdown. if err != fiber.ErrServerClosed { log.Fatalf("listen error: %v", err) } } }() // Create a context that is cancelled when SIGINT or SIGTERM is received. ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() // Block until a termination signal arrives. <-ctx.Done() log.Println("shutdown signal received, draining…") // Gracefully shutdown, waiting up to 10 seconds for in‑flight requests. if err := app.ShutdownWithTimeout(10 * time.Second); err != nil { log.Printf("forced shutdown: %v", err) os.Exit(1) } log.Println("shutdown complete, exiting") }Run the application.
The server starts listening ongo run main.go0.0.0.0:3000.Test graceful shutdown. Open a separate terminal and issue a request that will take longer than your drain timeout.
While the request is sleeping, sendcurl -v http://localhost:3000/slowSIGTERMto the process:
You should see the log messages:kill -TERM $(pgrep -f main)
Theshutdown signal received, draining… shutdown complete, exitingcurlcommand should finish with the bodyfinishedand exit code 0.Verify new connections are rejected during the drain window. With the same request running, immediately open a new
curlto the same endpoint. It should receive a connection error or a 503 if the server has already closed the listener.
Expected Checks
- After
app.ShutdownWithTimeoutreturns, theListencall must have exited. Verify that the main goroutine continues and the process exits with status 0 unless the timeout was exceeded. - During the drain window, new TCP connections should be refused. You can confirm this by attempting
curl -v http://localhost:3000/slowimmediately after sending SIGTERM and observing a connection reset or timeout. - In-flight handlers should complete normally if they finish before the timeout. The log output from the handler (e.g.,
finished) should appear before the shutdown completes. - If the timeout expires,
app.ShutdownWithTimeoutreturns an error. The application should log this and exit with a non‑zero status so orchestrators can detect the forced termination.
Recovery Options When the Timeout Expires
- Log the error and exit with
os.Exit(1)so that Kubernetes’terminationGracePeriodSecondscan trigger aSIGKILLif the pod still hasn’t stopped. - Expose a readiness probe (e.g.,
/ready) that flips to503as soon as the shutdown signal is received. Load balancers will stop sending traffic before the hard stop. - If your app performs long‑running background jobs, wrap them in a context that is cancelled by the shutdown function. The job goroutine can then exit early, preventing the timeout from being hit.
Sizing the Drain Timeout
Match the ShutdownWithTimeout value to the platform’s termination grace period. For example, in Kubernetes you might set terminationGracePeriodSeconds: 15 and use a 10‑second drain timeout. This gives the app 5 seconds to clean up after the graceful period ends.
Handling Custom Listeners (TLS, Unix Sockets)
If you use app.Listener(l) to provide a TLS listener or a Unix socket, ShutdownWithTimeout will close that listener instead of the default one. The same drain logic applies. Example:
l, err := tls.Listen("tcp", ":8443", &tls.Config{...})
if err != nil { log.Fatal(err) }
app.Listener(l)
Limitations and Caveats
- Fiber’s graceful shutdown only stops the HTTP listener; it does not cancel request handlers. Handlers must honor the request context or implement their own timeouts.
- The behavior of
app.ShutdownandShutdownWithTimeoutis version‑specific. Verify the signatures against the exactgo.modversion you use. - When running inside a container, ensure the container runtime sends
SIGTERMand respects the termination grace period. - Do not call shutdown before
app.Listenhas started or after it has returned; the function will return an error in those cases.
Practical Verification Checklist
| Step | What to Verify |
|---|---|
| Start the server | Log: "Server started" or similar |
| Send long request | Response body contains "finished" after ~5s |
| Send SIGTERM mid-request | Server logs "shutdown signal received" before request completes |
| Check process exit code | Zero if drained, non‑zero if timeout forced |
| Attempt new connection during drain | Connection refused or 503 |
Conclusion
Implementing a graceful shutdown in Fiber is straightforward once you understand the lifecycle of the *fiber.App and the OS signal handling in Go. By pairing app.ShutdownWithTimeout with a readiness probe and a well‑aligned container termination grace period, you can ensure that your Go services exit cleanly, preserve in‑flight work, and play nicely with Kubernetes or Docker’s pod lifecycle.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.