Graceful Shutdown in Go: Using os/signal and net/http for Clean Process Termination
This article explains how to implement graceful shutdown for Go programs, covering signal handling with os/signal, closing HTTP servers without interrupting active connections, managing goroutine lifecycles, and providing complete example code for both the standard net/http package and the Gin framework.
When developing Go services, abruptly terminating a process (e.g., with Ctrl+C, SIGINT, SIGTERM, or SIGKILL) can cause data inconsistency, especially for in‑flight HTTP requests; therefore a graceful exit is required.
The article first describes the common ways to stop a Go program, the signals they generate (SIGINT, SIGQUIT, SIGTERM, SIGKILL), and which of these can be intercepted using the os/signal package.
It then shows a minimal example that registers a channel to receive SIGINT/SIGTERM/SIGQUIT and prints the received signal:
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-quit
fmt.Println("received signal")
}Next, the article explains how to perform a graceful shutdown of an net/http server. The key steps are:
Close the listener to stop accepting new connections.
Close idle connections.
Wait for active connections to become idle (optionally with a timeout).
Exit the process.
Since Go 1.8, http.Server.Shutdown implements this flow. A complete example is provided:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
srv := &http.Server{Addr: ":8000"}
http.HandleFunc("/sleep", func(w http.ResponseWriter, r *http.Request) {
d, _ := time.ParseDuration(r.FormValue("duration"))
time.Sleep(d)
w.Write([]byte("Hello World!"))
})
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("ListenAndServe: %v", err)
}
log.Println("Stopped serving new connections")
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-quit
log.Println("Shutdown Server...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
log.Println("HTTP server graceful shutdown completed")
}The article also dives into the source code of http.Server.Shutdown , explaining how it marks the server as shutting down, closes listeners, runs any functions registered via RegisterOnShutdown , waits for listeners to finish, and then repeatedly calls closeIdleConns with a dynamically increasing poll interval (including jitter) until all connections are idle or the context expires.
It highlights the importance of keeping the main goroutine alive until Shutdown returns, otherwise the shutdown logic may be aborted prematurely, which is why the server’s ListenAndServe call is usually run in a separate goroutine.
Finally, the article shows that the same graceful‑shutdown pattern works with the Gin framework by creating an http.Server whose handler is a Gin engine, and using the same signal‑handling and Shutdown logic.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/sleep", func(c *gin.Context) {
d, _ := time.ParseDuration(c.Query("duration"))
time.Sleep(d)
c.String(http.StatusOK, "Welcome Gin Server")
})
srv := &http.Server{Addr: ":8000", Handler: router}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("ListenAndServe: %v", err)
}
log.Println("Stopped serving new connections")
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-quit
log.Println("Shutdown Server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Printf("Shutdown error: %v", err)
}
log.Println("HTTP server graceful shutdown completed")
}Go Programming World
Mobile version of tech blog https://jianghushinian.cn/, covering Golang, Docker, Kubernetes and beyond.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.