Skip to main content
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.
Max
int
default:"5"
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.
SkipFailedRequests
bool
default:"false"
When true, requests with status code ≥ 400 are not counted.
SkipSuccessfulRequests
bool
default:"false"
When true, requests with status code < 400 are not counted.
DisableHeaders
bool
default:"false"
When true, rate limit headers (X-RateLimit-*, Retry-After) are omitted.
DisableValueRedaction
bool
default:"false"
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{},
}

Rate Limit Headers

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,
}))

Build docs developers (and LLMs) love