Skip to main content
Technology & EngineeringSecurity Ratelimit204 lines

Upstash Ratelimit

Serverless rate limiting using Upstash Redis with sliding window, token bucket, and fixed window algorithms for edge and serverless environments

Quick Summary22 lines
You are an expert in using @upstash/ratelimit for serverless and edge-native rate limiting backed by Upstash Redis.

## Key Points

- Always return standard `X-RateLimit-*` and `Retry-After` headers so clients can implement proper backoff without guessing.
- Use different rate limit prefixes and tiers for different endpoint categories — authentication endpoints should be much more restrictive than read-only data endpoints.
- Enable `analytics: true` to track rate limit metrics in the Upstash console for tuning your thresholds based on real traffic patterns.
- Setting rate limits too aggressively on initial deploy — start with generous limits and `console.warn` when thresholds are approached, then tighten based on observed traffic.

## Quick Example

```bash
npm install @upstash/ratelimit @upstash/redis
```

```bash
# .env.local
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token
```
skilldb get security-ratelimit-skills/Upstash RatelimitFull skill: 204 lines
Paste into your CLAUDE.md or agent config

Upstash Ratelimit — Security & Rate Limiting

You are an expert in using @upstash/ratelimit for serverless and edge-native rate limiting backed by Upstash Redis.

Core Philosophy

Overview

@upstash/ratelimit is a connectionless, HTTP-based rate limiting library designed for serverless and edge runtimes (Vercel Edge Functions, Cloudflare Workers, AWS Lambda). It uses Upstash Redis as the backing store, which means no persistent TCP connections and sub-millisecond latency at the edge. The library provides three algorithms — fixed window, sliding window, and token bucket — and supports multi-region Redis for global low-latency rate limiting.

Setup & Configuration

Installation

npm install @upstash/ratelimit @upstash/redis

Environment Variables

# .env.local
UPSTASH_REDIS_REST_URL=https://your-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token

Basic Initialization

// lib/ratelimit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

// Sliding window: 10 requests per 10 seconds
export const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"),
  analytics: true,
  prefix: "@upstash/ratelimit",
});

Core Patterns

Next.js Middleware Rate Limiting

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { ratelimit } from "@/lib/ratelimit";

export async function middleware(request: NextRequest) {
  // Use IP address as the identifier
  const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";

  const { success, limit, reset, remaining } =
    await ratelimit.limit(ip);

  if (!success) {
    return NextResponse.json(
      { error: "Too many requests" },
      {
        status: 429,
        headers: {
          "X-RateLimit-Limit": limit.toString(),
          "X-RateLimit-Remaining": remaining.toString(),
          "X-RateLimit-Reset": reset.toString(),
          "Retry-After": Math.ceil(
            (reset - Date.now()) / 1000
          ).toString(),
        },
      }
    );
  }

  const response = NextResponse.next();
  response.headers.set("X-RateLimit-Limit", limit.toString());
  response.headers.set("X-RateLimit-Remaining", remaining.toString());
  return response;
}

export const config = {
  matcher: "/api/:path*",
};

Multiple Rate Limit Tiers

// lib/ratelimit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();

// Strict limit for auth endpoints
export const authLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.fixedWindow(5, "15 m"),
  prefix: "ratelimit:auth",
});

// Standard API limit
export const apiLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(60, "60 s"),
  prefix: "ratelimit:api",
});

// Generous limit for read-only endpoints
export const readLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.tokenBucket(100, "1 s", 200),
  prefix: "ratelimit:read",
});

Token Bucket for API Keys

// app/api/v1/route.ts
import { NextRequest, NextResponse } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  // 20 tokens refilled per second, max burst of 100
  limiter: Ratelimit.tokenBucket(20, "1 s", 100),
  prefix: "ratelimit:api-key",
});

export async function GET(request: NextRequest) {
  const apiKey = request.headers.get("x-api-key");
  if (!apiKey) {
    return NextResponse.json({ error: "Missing API key" }, { status: 401 });
  }

  // Rate limit per API key instead of IP
  const { success, remaining, reset } = await ratelimit.limit(apiKey);

  if (!success) {
    return NextResponse.json(
      { error: "Rate limit exceeded" },
      {
        status: 429,
        headers: {
          "Retry-After": Math.ceil(
            (reset - Date.now()) / 1000
          ).toString(),
        },
      }
    );
  }

  return NextResponse.json({ data: "your response", remaining });
}

Ephemeral Cache to Reduce Redis Calls

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, "10 s"),
  // Use ephemeral in-memory cache to avoid redundant Redis calls
  // for already-blocked identifiers
  ephemeralCache: new Map(),
  analytics: true,
});

Best Practices

  • Always return standard X-RateLimit-* and Retry-After headers so clients can implement proper backoff without guessing.
  • Use different rate limit prefixes and tiers for different endpoint categories — authentication endpoints should be much more restrictive than read-only data endpoints.
  • Enable analytics: true to track rate limit metrics in the Upstash console for tuning your thresholds based on real traffic patterns.

Common Pitfalls

  • Using IP-based rate limiting behind a load balancer without reading x-forwarded-for — all requests appear to come from the same internal IP, making the rate limit either useless or overly aggressive.
  • Setting rate limits too aggressively on initial deploy — start with generous limits and console.warn when thresholds are approached, then tighten based on observed traffic.

Anti-Patterns

Over-engineering for hypothetical requirements. Building for scenarios that may never materialize adds complexity without value. Solve the problem in front of you first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide wastes time and introduces risk.

Premature abstraction. Creating elaborate frameworks before having enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at system boundaries. Internal code can trust its inputs, but boundaries with external systems require defensive validation.

Skipping documentation. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add security-ratelimit-skills

Get CLI access →