HTTP Server Management in Go: Graceful Shutdowns and Error Handling
A production HTTP server is not only about serving requests. It also needs predictable startup, proper runtime error handling, and graceful shutdown behavior.
This guide shows a clean lifecycle pattern for Go HTTP services.
Why It Matters
- Prevents dropped requests during deployments.
- Handles OS signals cleanly in containers and VMs.
- Improves operational reliability and observability.
- Avoids hanging processes during shutdown.
Core Concepts
1. Preconfigured Server Construction
Create *http.Server with explicit timeouts and handlers.
srv := webapi.NewServer()
2. Runtime Error Channel
Use buffered error channel to capture fatal server errors without goroutine leaks.
serverErrCh := make(chan error, 1)
3. Non-Blocking Server Start
Run ListenAndServe in goroutine so main routine can monitor shutdown signals.
4. Signal-Aware Context
Use signal.NotifyContext to simplify signal handling.
5. Graceful Shutdown with Timeout
Allow in-flight requests to finish within a bounded timeout window.
6. Fallback Forced Close
If graceful shutdown fails, force close server to ensure process exits.
Practical Example
package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := webapi.NewServer()
serverErrCh := make(chan error, 1)
go func() {
log.Printf("starting server on http://%s", srv.Addr)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
serverErrCh <- err
}
}()
sigCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
select {
case err := <-serverErrCh:
log.Fatalf("server error: %v", err)
case <-sigCtx.Done():
log.Println("shutdown signal received")
}
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
log.Printf("graceful shutdown failed: %v", err)
if closeErr := srv.Close(); closeErr != nil {
log.Fatalf("forced close failed: %v", closeErr)
}
}
log.Println("server stopped")
}
This pattern keeps behavior deterministic during Ctrl+C, orchestration stop signals, and runtime failures. Your pager will appreciate that.
Common Mistakes
- Running
ListenAndServeon main goroutine and blocking lifecycle control. - Ignoring
http.ErrServerClosedand treating normal shutdown as failure. - No shutdown timeout context for in-flight requests.
- Not handling
SIGTERMin containerized environments. - Forgetting forced close fallback when graceful shutdown hangs.
Quick Recap
- Start server in goroutine.
- Capture server runtime errors through channel.
- Listen to OS signals for controlled shutdown.
- Use timeout-based
Shutdown. - Fallback to
Closewhen graceful path fails.
Next Steps
- Add readiness/liveness endpoints for orchestrators.
- Add structured logging for startup/shutdown lifecycle events.
- Add metrics for active requests during shutdown.
- Add integration tests for
SIGTERMbehavior.
Top comments (0)