A Pragmatic Guide to Rate Limiting: NestJS + Redis + Nginx
🚦 “Rate limiting helps prevent service abuse, and protects performance and user experience.”
When building production APIs, rate limiting is one of those things that often gets added late — usually after the first abuse spike or load test failure.
I’ve found that adding basic rate limiting early, even if it’s not perfect, is a worthwhile trade-off. It doesn’t need to be complicated, but it should be layered. This post walks through how I implement it using NestJS, Redis, and optionally Nginx.
🧱 Why Layered Rate Limiting?
I like to think about rate limiting in two layers:
- Edge-level (Nginx) – Blocks requests before they hit your app
 - App-level (NestJS + Redis) – Enforces per-user or per-IP rules with more logic
 
Using both gives you a fast first line of defense, and a more flexible backend layer for finer-grained control.
1. Nginx Rate Limiting (Quick, Simple, Blunt)
In most of my deployments, I use Nginx as a reverse proxy in front of the NestJS API. Here’s a basic config:
http {
  limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
  server {
    location /api/ {
      limit_req zone=api_limit burst=20 nodelay;
      proxy_pass http://nestjs_upstream;
      # other proxy settings...
    }
  }
}
What this does:
- 10 requests/second per IP
 - Allows small bursts of up to 20 requests
 - Rejects excess traffic with 503
 
It isn’t perfect, but it is sufficiently fast and uses no extra resources inside your app.
2. App-Level Rate Limiting with NestJS + Redis
At the application level, for more flexibility:
- Per-user, not just IP
 - Track API keys or auth headers
 - Return JSON errors, not HTML
 
Step 1: Install Dependencies
pnpm add rate-limiter-flexible ioredis
Step 2: Set Up the Redis Client
// src/redis/redis.service.ts
import { Redis } from 'ioredis';
export const redis = new Redis({
  host: 'redis',
  port: 6379,
});
Step 3: Create a Rate Limiter Guard
// src/common/guards/rate-limit.guard.ts
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  TooManyRequestsException,
} from '@nestjs/common';
import { RateLimiterRedis } from 'rate-limiter-flexible';
import { redis } from '../../redis/redis.service';
const rateLimiter = new RateLimiterRedis({
  storeClient: redis,
  keyPrefix: 'rl',
  points: 10, // 10 requests
  duration: 60, // per 60 seconds
});
@Injectable()
export class RateLimitGuard implements CanActivate {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const req = context.switchToHttp().getRequest();
    const key = req.ip || 'anonymous';
    try {
      await rateLimiter.consume(key);
      return true;
    } catch {
      throw new TooManyRequestsException(
        'Too many requests. Please try again later.',
      );
    }
  }
}
Step 4: Apply the Guard
// src/app.controller.ts
import { UseGuards, Controller, Get } from '@nestjs/common';
import { RateLimitGuard } from './common/guards/rate-limit.guard';
@Controller('api')
export class AppController {
  @UseGuards(RateLimitGuard)
  @Get('public-endpoint')
  getStuff() {
    return { ok: true };
  }
}
You can also apply it globally using app.useGlobalGuards().
3. Optional: Customize by User or API Key
const userId = req.user?.id || req.headers['x-api-key'] || req.ip;
await rateLimiter.consume(userId);
This gives you more precise control — and you can adjust limits dynamically if needed.
🧪 Bonus: Test It in CI or Locally
If you’re using tools like supertest, add integration tests to ensure 429s are thrown when expected:
it('should rate limit after 10 requests', async () => {
  for (let i = 0; i < 11; i++) {
    await request(app.getHttpServer()).get('/api/public-endpoint');
  }
  const res = await request(app.getHttpServer()).get('/api/public-endpoint');
  expect(res.status).toBe(429);
});
Final Thoughts
Rate limiting doesn’t have to be perfect, but it’s worth doing early — especially if your API is public or unauthenticated.
I’ve found that combining:
- Nginx for simple, IP-based throttling
 - Redis + NestJS for granular limits
 - Optional API key or session-based tracking
 
…gives you a setup that’s easy to maintain and hard to abuse.
If it prevents even one outage or abuse spike, it’s time well spent.
🏷 Tags
NestJS · Redis · Nginx · Backend Security · API Rate Limiting · Performance