go
60 lines · 8 steps
Graceful HTTP shutdown in Go
An HTTP server that listens in a goroutine and drains in-flight connections when it receives a termination signal.
Explained by
highlit
1package main
2
3import (
4 "context"
5 "errors"
6 "log"
7 "net/http"
8 "os"
9 "os/signal"
10 "syscall"
11 "time"
12)
13
14func run() error {
15 mux := http.NewServeMux()
16 mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
17 w.WriteHeader(http.StatusOK)
18 })
19
20 srv := &http.Server{
21 Addr: ":8080",
22 Handler: mux,
23 ReadTimeout: 5 * time.Second,
24 WriteTimeout: 10 * time.Second,
25 IdleTimeout: 120 * time.Second,
26 }
27
28 serverErr := make(chan error, 1)
29 go func() {
30 log.Printf("listening on %s", srv.Addr)
31 if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
32 serverErr <- err
33 }
34 }()
35
36 ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
37 defer stop()
38
39 select {
40 case err := <-serverErr:
41 return err
42 case <-ctx.Done():
43 stop()
44 log.Println("shutdown signal received, draining connections")
45 }
46
47 shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
48 defer cancel()
49
50 if err := srv.Shutdown(shutdownCtx); err != nil {
51 return srv.Close()
52 }
53 return nil
54}
55
56func main() {
57 if err := run(); err != nil {
58 log.Fatalf("server error: %v", err)
59 }
60}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Running ListenAndServe in a goroutine lets the main flow stay free to wait on either an error or a shutdown signal.
- 2signal.NotifyContext turns OS signals into a cancellable context you can select on like any other event.
- 3Server.Shutdown with a timeout context drains active requests but needs a hard Close fallback when draining stalls.
Related explainers
go
package main import ( "errors"
Parsing and validating CLI flags in Go
cli-parsing
validation
error-handling
Intermediate
8 steps
rust
use std::sync::{mpsc, Arc, Mutex}; use std::thread; use std::time::Duration;
Building a thread pool in Rust
concurrency
channels
thread-pool
Advanced
9 steps
go
package cache import ( "container/list"
Building a generic LRU cache in Go
lru-cache
generics
linked-list
Intermediate
8 steps
go
package model import ( "encoding/json"
Custom JSON marshaling in Go
json
serialization
interfaces
Intermediate
5 steps
python
from django.conf import settings from django.contrib.auth import get_user_model from django.core.mail import EmailMultiAlternatives from django.db.models.signals import post_save
Sending a welcome email with Django signals
signals
email
user-activation
Intermediate
8 steps
go
func (h *TransactionHandler) ExportCSV(c *gin.Context) { ctx := c.Request.Context() filters := parseTransactionFilters(c)
Streaming a CSV export in Gin
streaming
csv-export
database-cursor
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/graceful-http-shutdown-in-go-explained-go-593c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.