# How to Implement Rate Limiting Middleware in Gin: 3 Production-Ready Methods

> Learn how to implement rate limiting middleware in Gin with 3 production-ready methods. Protect your API from abuse with efficient rate limiting strategies for your Go applications.

- Repository: [Gin-Gonic/gin](https://github.com/gin-gonic/gin)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Rate limiting middleware in Gin is implemented as a `gin.HandlerFunc` that inspects incoming requests, checks against a limiter algorithm, and calls `c.AbortWithStatusJSON(429)` to stop processing when limits are exceeded, otherwise allowing `c.Next()` to continue the handler chain.**

Implementing rate limiting middleware in Gin requires understanding how the framework orchestrates request processing. The gin-gonic/gin repository uses a `HandlersChain` architecture where middleware functions execute sequentially, giving you precise control over request flow before your application logic runs.

## How Gin Processes Middleware Chains

Gin builds a **handler chain** (`HandlersChain`) for every request. When you call `engine.Use(...)` or `group.Use(...)`, the supplied `HandlerFunc` instances are appended to the group's `Handlers` slice in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go) (lines 64-68) and later merged with route-specific handlers via `combineHandlers` (lines 41-48).

During request execution, the `Engine` creates a fresh `Context`, copies the merged `HandlersChain` into `c.handlers`, and invokes `c.Next()` defined in [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go) (lines 85-96). The `Next` method walks the chain sequentially, allowing each middleware to decide whether to continue processing or abort the request entirely.

This architecture means a rate limiting middleware only needs to:

1. **Inspect the request** (client IP, path, headers).
2. **Check a limiter** (token bucket, fixed window, etc.).
3. **Abort if over limit** using `c.AbortWithStatusJSON` or `c.Abort`.
4. **Return normally** to let `c.Next()` continue to subsequent handlers.

## Building a Custom Token Bucket Rate Limiter

### In-Memory Per-IP Implementation

For simple deployments, you can implement a token bucket algorithm using Go's `golang.org/x/time/rate` package. The middleware extracts the client IP using `c.ClientIP()` (implemented in [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go) lines 970-1024) and maintains a separate limiter per IP address.

```go
package main

import (
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
	"golang.org/x/time/rate"
)

type limiterStore struct {
	mu      sync.Mutex
	clients map[string]*rate.Limiter
	r       rate.Limit
	b       int
}

func newLimiterStore(r rate.Limit, b int) *limiterStore {
	return &limiterStore{
		clients: make(map[string]*rate.Limiter),
		r:       r,
		b:       b,
	}
}

func (s *limiterStore) getLimiter(ip string) *rate.Limiter {
	s.mu.Lock()
	defer s.mu.Unlock()
	lim, exists := s.clients[ip]
	if !exists {
		lim = rate.NewLimiter(s.r, s.b)
		s.clients[ip] = lim
	}
	return lim
}

func RateLimitMiddleware(store *limiterStore) gin.HandlerFunc {
	return func(c *gin.Context) {
		ip := c.ClientIP()
		lim := store.getLimiter(ip)
		if !lim.Allow() {
			c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
				"error": "rate limit exceeded",
			})
			return
		}
		c.Next()
	}
}

func main() {
	r := gin.Default()
	limStore := newLimiterStore(5, 10) // 5 req/sec, burst 10
	r.Use(RateLimitMiddleware(limStore))
	
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"msg": "pong"})
	})
	r.Run()
}

```

**Key implementation details:**

- `c.ClientIP()` respects Gin's trusted proxy configuration, essential for accurate per-IP limiting behind load balancers.
- `c.AbortWithStatusJSON` immediately stops the handler chain and returns HTTP 429, preventing downstream handlers from executing.

## Using Production-Ready Libraries

### Implementing with ulule/limiter

For distributed systems or advanced features (Redis backends, custom keys), use the `github.com/ulule/limiter/v3` package with its dedicated Gin middleware driver.

```go
package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/ulule/limiter/v3"
	memory "github.com/ulule/limiter/v3/drivers/store/memory"
	ginmiddleware "github.com/ulule/limiter/v3/drivers/middleware/gin"
)

func main() {
	rate, _ := limiter.NewRateFromFormatted("10-M") // 10 requests per minute
	store := memory.NewStore()
	limiterInstance := limiter.New(store, rate)
	rateLimiter := ginmiddleware.NewMiddleware(limiterInstance)

	r := gin.Default()
	r.Use(rateLimiter) // Applied globally

	r.GET("/resource", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})
	r.Run()
}

```

The `ginmiddleware.NewMiddleware` function returns a standard `gin.HandlerFunc` that integrates seamlessly with Gin's `HandlersChain`, checking limits before invoking `c.Next()`.

## Applying Middleware to Specific Routes

You can restrict rate limiting to specific API sections using **Router Groups**. In [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go), the `Group` method creates a new `RouterGroup` whose `Handlers` slice is merged with parent middleware via `combineHandlers`.

```go
api := r.Group("/api")
api.Use(RateLimitMiddleware(limStore)) // Only limits /api/* routes
{
    api.GET("/users", listUsers)
    api.POST("/users", createUser)
}

```

This approach leverages `group.Use` (lines 64-68 in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go)) to inject the middleware only into specific sub-trees of your routing hierarchy.

## Key Source Files and Implementation Details

Understanding these core files helps debug and extend your rate limiting implementation:

- **[`gin.go`](https://github.com/gin-gonic/gin/blob/main/gin.go)** (lines 37-44): Contains the global `Use` method that adds middleware to the root router's `Handlers` slice.
- **[`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go)** (lines 41-48): Implements `combineHandlers`, which merges group-level and route-level handlers into the final execution chain.
- **[`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go)** (lines 64-68): Defines `RouterGroup.Use`, allowing middleware attachment to specific route groups.
- **[`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go)** (lines 85-96): Contains the `Next` method that iterates through `c.handlers`, executing middleware sequentially.
- **[`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go)** (lines 970-1024): Implements `ClientIP`, which extracts the real client IP while respecting `TrustedProxies` configuration.

## Summary

- **Gin middleware** is any function matching `func(*gin.Context)` that controls execution flow via `c.Next()` and `c.Abort()`.
- **Rate limiting logic** executes before route handlers; call `c.AbortWithStatusJSON(429)` to reject over-limit requests.
- **Per-IP tracking** should use `c.ClientIP()` from [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go) to handle proxy configurations correctly.
- **Global or scoped** application is controlled through `engine.Use` (global) or `group.Use` (specific routes) defined in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go).
- **Algorithm choice** ranges from simple in-memory token buckets (`golang.org/x/time/rate`) to distributed solutions (`ulule/limiter`).

## Frequently Asked Questions

### How do I accurately extract client IP for per-IP rate limiting in Gin?

Use `c.ClientIP()` as implemented in [`context.go`](https://github.com/gin-gonic/gin/blob/main/context.go) (lines 970-1024). This method respects the `TrustedProxies` and `RemoteIPHeaders` configuration, ensuring you receive the real client IP even when running behind load balancers or reverse proxies. Avoid using `c.Request.RemoteAddr` directly, as it may return the proxy's IP rather than the end user's.

### Can I apply different rate limits to different API routes?

Yes. Create separate `RouterGroup` instances using `r.Group("/path")` and attach distinct middleware via `group.Use()` as defined in [`routergroup.go`](https://github.com/gin-gonic/gin/blob/main/routergroup.go). Each group maintains its own `Handlers` slice that merges with the global chain, allowing you to apply strict limits to authentication endpoints while keeping public endpoints unrestricted.

### What is the difference between `c.Abort()` and `c.Next()` in Gin middleware?

`c.Next()` (context.go lines 85-96) passes control to the next handler in the `HandlersChain`, continuing execution. `c.Abort()` (and its variant `c.AbortWithStatusJSON`) immediately stops the chain by setting `c.index` to `abortIndex`, ensuring no subsequent middleware or route handlers execute. For rate limiting, use `c.AbortWithStatusJSON(429, ...)` to return the proper HTTP status while halting processing.

### Which rate limiting algorithm should I use with Gin?

For single-instance applications, use a **token bucket** via `golang.org/x/time/rate` for smooth request distribution and burst handling. For distributed systems or persistent storage needs, use `github.com/ulule/limiter/v3` with Redis backing. Avoid simple counters for production traffic, as they create thundering herd problems at window boundaries; token buckets provide more consistent traffic shaping.