Upstash Ratelimit
Serverless rate limiting using Upstash Redis with sliding window, token bucket, and fixed window algorithms for edge and serverless environments
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 linesUpstash 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-*andRetry-Afterheaders 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: trueto 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.warnwhen 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
Related Skills
Arcjet
Rate limiting, bot detection, email validation, and shield attack protection using Arcjet with Next.js middleware and stacking rules
Cloudflare Turnstile
Privacy-preserving CAPTCHA alternative using Cloudflare Turnstile for bot protection with server-side verification in Next.js and Express
Security Headers
Security headers with Helmet.js, Content Security Policy, CORS configuration, CSRF protection, rate limiting patterns, and Next.js security headers
OWASP ZAP
Automated web application security testing, API scanning, and CI/CD DAST integration using OWASP ZAP
Snyk
Dependency vulnerability scanning, license compliance, and continuous security monitoring using Snyk CLI and CI/CD integrations
Svix
Webhook delivery infrastructure including sending webhooks, retry logic, signature verification, event types, consumer portal, and message logging with Svix