Introduction
When a Go service is restarted or deployed, it receives a termination signal. Graceful shutdown ensures in-flight requests complete before the process exits, preventing broken responses and data corruption.
Key Concepts
- SIGTERM: A signal sent by orchestrators (Kubernetes, Docker, systemd) requesting the process to shut down gracefully.
- SIGINT: The signal sent when pressing Ctrl+C — typically handled the same way as SIGTERM.
- server.Shutdown(ctx): Stops accepting new connections and waits for active requests to complete, with a context deadline as the hard limit.
- signal.Notify: Registers a channel to receive specific OS signals, allowing the program to react.
Real World Context
In Kubernetes, a pod receives SIGTERM before being killed. If your server does not handle this signal, active requests get abruptly terminated, causing 502 errors for users and potential data loss for in-progress writes.
Deep Dive
The standard graceful shutdown pattern starts the server in a goroutine and blocks the main goroutine on a signal channel.
gofunc main() { srv := &http.Server{Addr: ":8080", Handler: router} go func() { if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatal(err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } log.Println("Server exited gracefully") }
srv.Shutdown(ctx) stops accepting new connections immediately but waits for active requests to finish. If they take longer than the context timeout (30 seconds here), the shutdown is forced.
Common Pitfalls
- Not handling SIGTERM at all — The process gets killed abruptly, dropping in-flight requests and potentially corrupting writes.
- Setting the timeout too short — A 1-second timeout may kill long-running requests. Match the timeout to your longest expected request duration.
Best Practices
- Always handle both SIGINT and SIGTERM — SIGINT covers local development (Ctrl+C) and SIGTERM covers production deployments.
- Close other resources after server shutdown — After
srv.Shutdown(), close database connections, flush logs, and release other resources in order.
Summary
- Catch SIGTERM and SIGINT with
signal.Notifyto trigger graceful shutdown. server.Shutdown(ctx)finishes active requests before stopping, with a timeout as the hard limit.- Always clean up database connections and other resources after the server stops.
Code Examples
go
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("Forced shutdown:", err)
}