Graceful Shutdown for Web Servers
Graceful Shutdown for Web Servers
Overview
On deploy or Ctrl-C, stop accepting new connections, finish in-flight requests, then exit. Go’s http.Server.Shutdown does the hard part—wire it to signals and a deadline.
Pattern
func main() {
mux := http.NewServeMux()
// register routes...
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}
go func() {
slog.Info("listen", "addr", srv.Addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
slog.Error("serve", "err", err)
os.Exit(1)
}
}()
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
<-ctx.Done()
slog.Info("shutdown signal")
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
slog.Error("shutdown", "err", err)
_ = srv.Close()
}
}Kubernetes notes
- SIGTERM on pod stop
terminationGracePeriodSecondsmust exceed your shutdown timeout
- Readiness fail first (optional sleep) so LB drains before Shutdown
// optional: sleep 2s after SIGTERM so endpoints controller removes you
time.Sleep(2 * time.Second)
_ = srv.Shutdown(shutdownCtx)In-flight work beyond HTTP
Background workers need the same root context:
root, stop := signal.NotifyContext(...)
// workers select on root.Done()
// then Shutdown HTTPHealth endpoints during drain
- Liveness: still 200 until process dies
- Readiness: 503 after signal so new traffic stops
var ready atomic.Bool
ready.Store(true)
mux.HandleFunc("GET /readyz", func(w http.ResponseWriter, r *http.Request) {
if !ready.Load() {
http.Error(w, "draining", http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
})
// on signal: ready.Store(false)Rules of thumb
| Do | Don’t |
|---|---|
Shutdown with timeout |
Only os.Exit on SIGTERM |
| Fail readiness early | Keep accepting traffic while dying |
| Close DB pools after HTTP drains | Cut DB under active handlers |
Try next
- Slow handler (5s sleep); send SIGTERM; confirm request completes.
- Set shutdown timeout 1s; prove forced close path.
- Add
/readyzdrain behavior.