How to Implement Graceful Shutdown for a Gin Server in Go

To implement graceful shutdown for a Gin server, create an http.Server manually with the Gin router as its handler, start it in a goroutine, listen for OS termination signals, and call server.Shutdown(ctx) with a timeout context to drain active connections without dropping in-flight requests.

Gin is a high-performance HTTP web framework that implements the standard http.Handler interface. While the convenience method engine.Run() provides a quick way to start listening, it blocks the calling goroutine forever and prevents clean termination. Implementing graceful shutdown for a Gin server requires bypassing this helper to gain direct control over the underlying http.Server instance, ensuring your Go application can handle SIGINT or SIGTERM signals without interrupting active HTTP requests.

Why Avoid engine.Run() for Production Services

The Engine.Run() method in [gin.go](https://github.com/gin-gonic/gin/blob/master/gin.go#L37-L56) creates an internal http.Server and immediately calls ListenAndServe(), blocking execution indefinitely. This design prevents the main goroutine from listening to OS signals or orchestrating a controlled shutdown. According to the source code at lines 37-56, this helper is intended for rapid prototyping, not production deployments that require lifecycle management.

Implementing Graceful Shutdown for a Gin Server (Step-by-Step)

Since Go 1.8, the standard library provides http.Server.Shutdown(), which coordinates with the underlying net.Listener to stop accepting new connections while allowing existing requests to complete. Gin’s official documentation in [docs/doc.md](https://github.com/gin-gonic/gin/blob/master/docs/doc.md#L51-L55) describes this pattern in the Graceful shutdown or restart section.

Step 1: Create the Gin Router Without Calling Run()

Instantiate your router using gin.Default() or gin.New(), but do not invoke the Run() method. This keeps the engine ready to serve HTTP traffic without blocking your main goroutine.

router := gin.Default()
router.GET("/", func(c *gin.Context) {
    c.String(http.StatusOK, "Hello World")
})

Step 2: Instantiate Your Own http.Server

Construct an http.Server struct manually, assigning the Gin router to the Handler field. This gives you a server handle required for the shutdown procedure.

srv := &http.Server{
    Addr:    ":8080",
    Handler: router,
}

Step 3: Start the Server in a Goroutine

Launch srv.ListenAndServe() inside its own goroutine. This allows the main goroutine to continue execution and monitor for termination signals, while the server handles incoming traffic concurrently.

go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatalf("listen: %s\n", err)
    }
}()

Step 4: Capture OS Interrupt Signals

Create a channel to receive operating system signals and register it with signal.Notify to watch for SIGINT and SIGTERM. The main goroutine blocks on this channel until an interrupt occurs.

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")

Step 5: Create a Context with Timeout

Define a context.Context with a deadline that bounds how long you allow existing connections to finish processing. A typical timeout ranges from 5 to 30 seconds depending on your workload characteristics.

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

Step 6: Trigger Graceful Shutdown

Invoke srv.Shutdown(ctx). This method stops the server from accepting new connections, closes idle connections, and waits for active handlers to return or for the context timeout to expire.

if err := srv.Shutdown(ctx); err != nil {
    log.Fatalf("Server forced to shutdown: %v", err)
}

Step 7: Handle Exit

After Shutdown() returns, log the termination status and allow the main function to exit cleanly. All in-flight requests that completed within the timeout window will have been served successfully.

log.Println("Server exiting")

Complete Working Example

The following implementation demonstrates the full pattern, including a simulated long-running handler to test the graceful shutdown behavior. This example aligns with the official code snippet found in [docs/doc.md](https://github.com/gin-gonic/gin/blob/master/docs/doc.md#L71-L33).

package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"github.com/gin-gonic/gin"
)

func main() {
	// 1️⃣ Create router
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		// Simulate work that takes a few seconds
		time.Sleep(5 * time.Second)
		c.String(http.StatusOK, "Welcome Gin Server")
	})

	// 2️⃣ Build an http.Server without calling router.Run()
	srv := &http.Server{
		Addr:    ":8080",
		Handler: router,
	}

	// 3️⃣ Start the server in a goroutine
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// 4️⃣ Listen for OS interrupt signals
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit // block until signal is received
	log.Println("Shutting down server…")

	// 5️⃣ Create a context with a timeout for the shutdown process
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	// 6️⃣ Trigger graceful shutdown
	if err := srv.Shutdown(ctx); err != nil {
		log.Fatalf("Server forced to shutdown: %v", err)
	}

	log.Println("Server exiting")
}

Alternative Third-Party Approaches

If you prefer to abstract the boilerplate, several community packages provide drop-in replacements for ListenAndServe with graceful shutdown capabilities. The Gin documentation in [docs/doc.md](https://github.com/gin-gonic/gin/blob/master/docs/doc.md#L55-L71) mentions these options:

  • github.com/fvbock/endless – Provides endless.ListenAndServe(":4242", router) for graceful restarts without dropping connections.
  • github.com/facebookgo/grace – Supports zero-downtime restarts by passing the listener to a new process.
  • github.com/tylerb/graceful – Offers a graceful wrapper with configurable timeouts and shutdown hooks.

While these libraries simplify implementation, the native http.Server.Shutdown available since Go 1.8 is recommended for most use cases to minimize external dependencies.

Summary

  • Bypass engine.Run(): The built-in method blocks forever and prevents signal handling; instantiate your own http.Server instead.
  • Use http.Server.Shutdown: Available in Go 1.8+, this standard library method safely drains active connections within a bounded timeout.
  • Start in a Goroutine: Run ListenAndServe() concurrently so the main thread can monitor for SIGINT or SIGTERM.
  • Context Timeout Matters: Provide a context.Context with a deadline to prevent the shutdown process from hanging indefinitely on slow handlers.
  • Reference Implementation: The complete pattern is documented in [docs/doc.md](https://github.com/gin-gonic/gin/blob/master/docs/doc.md) and demonstrated in the gin-examples repository under the graceful-shutdown directory.

Frequently Asked Questions

What is graceful shutdown in a web server?

Graceful shutdown is the process of terminating a server process without abruptly closing active HTTP connections. When a termination signal is received, the server stops accepting new requests, allows in-flight requests to complete within a defined timeout period, and then closes the underlying listener and connections. This prevents data loss and ensures clients receive complete responses rather than connection reset errors.

Why can't I use router.Run() for graceful shutdown?

The Run() method in [gin.go](https://github.com/gin-gonic/gin/blob/master/gin.go#L37-L56) encapsulates server creation and immediately invokes http.Server.ListenAndServe(), which blocks the calling goroutine indefinitely. Because it does not return control to the caller, you cannot simultaneously listen for OS signals or invoke Shutdown(). You must bypass this convenience method and manage the http.Server lifecycle manually to implement graceful shutdown for a Gin server.

How long should the shutdown timeout be?

The appropriate timeout depends on your application's request latency characteristics. Common production values range from 5 to 30 seconds. Set the timeout too short, and active requests may be terminated prematurely; set it too long, and the orchestration layer (like Kubernetes or Docker) may force-kill the container before shutdown completes. The timeout is passed via context.WithTimeout() and enforced by server.Shutdown(ctx).

Does Gin support zero-downtime restarts?

Gin itself does not implement zero-downtime (hot) restart functionality, but it is compatible with libraries that provide this capability. The documentation mentions github.com/fvbock/endless and github.com/facebookgo/grace as solutions that manage file descriptor passing to new processes, allowing you to upgrade your Gin application without dropping existing TCP connections. For simple graceful shutdown without restart functionality, the standard library's http.Server.Shutdown is sufficient.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →