Graceful HTTP Server Shutdown with Signal Handling
Runs an HTTP server with sane timeouts and health endpoints, shutting down gracefully on OS signals with context cancellation.
main.go
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
package main
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]interface{}{"status": "ok"})
})
mux.HandleFunc("/readyz", func(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]interface{}{"ready": true})
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
errCh := make(chan error, 1)
go func() {
log.Printf("listening on http://localhost%v", srv.Addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
errCh <- err
return
}
errCh <- nil
}()
select {
case err := <-errCh:
if err != nil {
log.Fatalf("server failed: %v", err)
}
return
case <-ctx.Done():
log.Printf("shutdown requested")
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("graceful shutdown failed: %v", err)
_ = srv.Close()
}
log.Printf("server stopped")
}How It Works
Runs an HTTP server with sane timeouts and health endpoints, shutting down gracefully on OS signals with context cancellation.
Builds a ServeMux with health and readiness checks, configures read, write, and idle timeouts, launches the server in a goroutine, listens for interrupt signals, calls Shutdown to drain connections, and logs any shutdown errors.
Key Concepts
- 1Health and readiness endpoints support Kubernetes or load balancer probes.
- 2Timeout configuration defends against slow clients.
- 3Context-aware shutdown ensures handlers finish before closing sockets.
When to Use This Pattern
- Drop-in starter server for new services.
- Kubernetes probe-friendly HTTP endpoints.
- Demonstration of graceful exit for standard library servers.
Best Practices
- Differentiate readiness versus liveness responses appropriately.
- Set generous but bounded Shutdown timeouts.
- Handle http.ErrServerClosed as a normal condition to avoid noisy logs.
Go Version1.16+
Difficultyintermediate
Production ReadyYes
Lines of Code70