Limiter middleware for Fiber throttles repeated requests to public APIs, endpoints, or services. It’s useful for preventing abuse, protecting against DDoS attacks, and managing API quotas.
Installation
go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/fiber/v3/middleware/limiter
Signatures
func New(config ...Config) fiber.Handler
Usage
Basic Usage
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/limiter"
"time"
)
func main() {
app := fiber.New()
// 5 requests per minute
app.Use(limiter.New())
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, World!")
})
app.Listen(":3000")
}
Custom Configuration
app.Use(limiter.New(limiter.Config{
Max: 20,
Expiration: 30 * time.Second,
KeyGenerator: func(c fiber.Ctx) string {
return c.Get("x-forwarded-for")
},
LimitReached: func(c fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).SendFile("./toofast.html")
},
}))
Skip Localhost
app.Use(limiter.New(limiter.Config{
Next: func(c fiber.Ctx) bool {
return c.IP() == "127.0.0.1"
},
}))
Sliding Window Algorithm
app.Use(limiter.New(limiter.Config{
Max: 20,
Expiration: 30 * time.Second,
LimiterMiddleware: limiter.SlidingWindow{},
}))
Dynamic Limits
app.Use(limiter.New(limiter.Config{
MaxFunc: func(c fiber.Ctx) int {
// Premium users get higher limits
if isPremiumUser(c) {
return 100
}
return 20
},
Expiration: 1 * time.Minute,
}))
Dynamic Expiration
app.Use(limiter.New(limiter.Config{
Max: 20,
ExpirationFunc: func(c fiber.Ctx) time.Duration {
// Longer window for sensitive endpoints
if c.Path() == "/login" {
return 60 * time.Second
}
return 30 * time.Second
},
}))
Custom Storage (Redis)
import "github.com/gofiber/storage/redis/v3"
store := redis.New(redis.Config{
Host: "127.0.0.1",
Port: 6379,
Database: 0,
})
app.Use(limiter.New(limiter.Config{
Storage: store,
}))
Configuration
Next
func(fiber.Ctx) bool
default:"nil"
Function to skip this middleware when it returns true.
Maximum number of requests allowed within the Expiration window.
MaxFunc
func(fiber.Ctx) int
default:"Returns cfg.Max"
Function to dynamically calculate the maximum requests per window.
KeyGenerator
func(fiber.Ctx) string
default:"c.IP()"
Function to generate custom rate limit keys. Defaults to client IP address.
Expiration
time.Duration
default:"1 * time.Minute"
Time window for counting requests.
ExpirationFunc
func(fiber.Ctx) time.Duration
default:"Returns cfg.Expiration"
Function to dynamically calculate the expiration window.
LimitReached
fiber.Handler
default:"Returns 429 status"
Handler called when a request exceeds the limit.
When true, requests with status code ≥ 400 are not counted.
When true, requests with status code < 400 are not counted.
When true, rate limit headers (X-RateLimit-*, Retry-After) are omitted.
Disables redaction of limiter keys in logs and error messages.
Storage
fiber.Storage
default:"In-memory storage"
Storage backend for persisting rate limit data.
LimiterMiddleware
limiter.Handler
default:"FixedWindow{}"
Algorithm implementation (FixedWindow or SlidingWindow).
Default Configuration
var ConfigDefault = Config{
Max: 5,
Expiration: 1 * time.Minute,
MaxFunc: func(_ fiber.Ctx) int {
return 5
},
KeyGenerator: func(c fiber.Ctx) string {
return c.IP()
},
LimitReached: func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusTooManyRequests)
},
SkipFailedRequests: false,
SkipSuccessfulRequests: false,
DisableHeaders: false,
DisableValueRedaction: false,
LimiterMiddleware: FixedWindow{},
}
When DisableHeaders is false, the middleware sets these headers:
X-RateLimit-Limit: Maximum requests allowed
X-RateLimit-Remaining: Requests remaining in current window
X-RateLimit-Reset: Unix timestamp when the limit resets
Retry-After: Seconds until the limit resets (only when limit exceeded)
Best Practices
Use Redis for Distributed Systems
import "github.com/gofiber/storage/redis/v3"
store := redis.New(redis.Config{
Host: "127.0.0.1",
Port: 6379,
Database: 0,
Reset: false,
})
app.Use(limiter.New(limiter.Config{
Storage: store,
Max: 100,
Expiration: 1 * time.Minute,
}))
Different Limits for Different Endpoints
// Strict limit for login
app.Post("/login", limiter.New(limiter.Config{
Max: 5,
Expiration: 15 * time.Minute,
}), handleLogin)
// Generous limit for API
app.Use("/api", limiter.New(limiter.Config{
Max: 1000,
Expiration: 1 * time.Hour,
}))
Rate Limit by User ID
app.Use(limiter.New(limiter.Config{
KeyGenerator: func(c fiber.Ctx) string {
// Use user ID if authenticated, otherwise IP
if userID := c.Locals("user_id"); userID != nil {
return fmt.Sprintf("user:%v", userID)
}
return c.IP()
},
}))
Common Patterns
Skip Rate Limiting for Failed Requests
app.Use(limiter.New(limiter.Config{
SkipFailedRequests: true, // Don't count 4xx/5xx responses
Max: 100,
Expiration: 1 * time.Minute,
}))
Custom Error Response
app.Use(limiter.New(limiter.Config{
LimitReached: func(c fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "Rate limit exceeded",
"retry_after": 60,
})
},
}))
Tiered Rate Limiting
func getUserTier(c fiber.Ctx) string {
// Determine user tier from context
return c.Locals("tier").(string)
}
app.Use(limiter.New(limiter.Config{
MaxFunc: func(c fiber.Ctx) int {
switch getUserTier(c) {
case "premium":
return 1000
case "standard":
return 100
default:
return 20
}
},
KeyGenerator: func(c fiber.Ctx) string {
userID := c.Locals("user_id")
return fmt.Sprintf("user:%v", userID)
},
}))
Sliding Window Example
// Smoother rate limiting with sliding window
app.Use(limiter.New(limiter.Config{
Max: 20,
Expiration: 30 * time.Second,
LimiterMiddleware: limiter.SlidingWindow{},
}))
The sliding window algorithm calculates the rate as:
weightOfPreviousWindow = previousWindowRequests * (elapsedInCurrentWindow / Expiration)
rate = weightOfPreviousWindow + currentWindowRequests
X-Forwarded-For Support
app.Use(limiter.New(limiter.Config{
KeyGenerator: func(c fiber.Ctx) string {
// Use X-Forwarded-For if behind a proxy
if xff := c.Get("X-Forwarded-For"); xff != "" {
// Get first IP in the chain
ips := strings.Split(xff, ",")
return strings.TrimSpace(ips[0])
}
return c.IP()
},
}))
Testing
# Make requests and check headers
curl -I http://localhost:3000
# Response headers:
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 4
X-RateLimit-Reset: 1234567890
# After exceeding limit:
HTTP/1.1 429 Too Many Requests
Retry-After: 45
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1234567890
Storage Backends
You can use any storage from the Fiber storage package:
- Memory (default, single process)
- Redis
- Memcache
- PostgreSQL
- MySQL
- SQLite
- MongoDB
- And more…
import "github.com/gofiber/storage/sqlite3/v2"
storage := sqlite3.New()
app.Use(limiter.New(limiter.Config{
Storage: storage,
}))