Rate Limiting in NestJS That Actually Works

ThrottlerModule is the right starting point. Here's the production config, the Redis-backed shared store, and per-route customization.

May 9, 2026·5 min read

Rate Limiting in NestJS That Actually Works

NestJS has @nestjs/throttler, which is the right rate-limiting library for the framework. The defaults work in development. Production requires three changes.

Install + basic setup

npm install @nestjs/throttler
// app.module.ts
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
import { APP_GUARD } from '@nestjs/core';

@Module({
  imports: [
    ThrottlerModule.forRoot([
      { name: 'short', ttl: 1000, limit: 10 },     // 10/sec burst
      { name: 'medium', ttl: 10_000, limit: 30 },  // 30/10sec sustained
      { name: 'long', ttl: 60_000, limit: 100 },   // 100/min absolute
    ]),
  ],
  providers: [
    { provide: APP_GUARD, useClass: ThrottlerGuard },
  ],
})
export class AppModule {}

Multiple windows give you both burst and sustained limits in one config. Most production apps want all three tiers.

Production change 1: trust proxy

Without this, you're rate-limiting your reverse proxy as a single IP:

// main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.set('trust proxy', 1); // <-- this line
  await app.listen(3000);
}
bootstrap();

Adjust the 1 to the number of proxy hops in your topology.

Production change 2: Redis storage

npm install @nest-lab/throttler-storage-redis ioredis
// app.module.ts
import { ThrottlerStorageRedisService } from '@nest-lab/throttler-storage-redis';
import { Redis } from 'ioredis';

ThrottlerModule.forRootAsync({
  useFactory: () => ({
    throttlers: [
      { name: 'short', ttl: 1000, limit: 10 },
      { name: 'long', ttl: 60_000, limit: 100 },
    ],
    storage: new ThrottlerStorageRedisService(new Redis(process.env.REDIS_URL)),
  }),
}),

Now all instances share counters. Without this, scaling horizontally weakens your rate limits proportionally.

Production change 3: per-route overrides

Auth endpoints need stricter limits than general API:

import { Throttle } from '@nestjs/throttler';

@Controller('auth')
export class AuthController {
  @Post('login')
  @Throttle({ default: { ttl: 5 * 60_000, limit: 5 } })
  async login(@Body() body: any) {
    // 5 attempts per 5 minutes
  }

  @Post('reset-password')
  @Throttle({ default: { ttl: 60_000, limit: 3 } })
  async reset(@Body() body: any) {
    // 3 attempts per minute — password reset is highly abuse-prone
  }
}

For very high-volume endpoints (autocomplete, polling), increase the limit:

@Get('autocomplete')
@Throttle({ default: { ttl: 1000, limit: 50 } })
async autocomplete() { /* ... */ }

Per-user rate limiting

Override the tracker for authenticated routes:

// src/throttler/user-throttler.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { ThrottlerGuard } from '@nestjs/throttler';

@Injectable()
export class UserThrottlerGuard extends ThrottlerGuard {
  protected async getTracker(req: any): Promise<string> {
    return req.user?.id ?? req.ip;
  }
}

Apply per-controller or globally.

Verifying

A burst test:

for i in {1..15}; do
  curl -i -X POST http://localhost:3000/auth/login -d 'email=a@b.c&password=foo'
done

You should see the first 5 return 401/200, then 429 for the rest.

What this doesn't solve

Rate limiting is one layer:

Related

Frequently Asked Questions

Should I use the default Throttler or a custom Guard?

Throttler for general rate limits across all routes; custom decorators (`@Throttle()`) to override per-route. Custom Guard only for non-rate-limit logic (e.g., credential stuffing) layered on top.

Does the in-memory storage work in production?

Only for single-instance deployments. With multiple replicas (Kubernetes, ECS, PM2 cluster), the throttle counts diverge per instance. Use the Redis storage adapter.

How do I configure trust proxy?

Set `app.set('trust proxy', 1)` in main.ts after creating the Express app. Without it, the throttler counts every CDN/load-balancer IP as one rate-limit bucket.

What about per-user rate limiting?

Override `getTracker()` in a custom ThrottlerGuard subclass. Return the user ID for authenticated requests, fall back to IP otherwise.

Recommended reading

Create Custom Alert Rules From the Command Line

SecureNow 8.1 adds `securenow alerts rules create` — define your own detection rules from SQL, scope them to your apps, and ship them without leaving the terminal. Here's how, with a real magic-link brute-force example.

Jun 11
Secure a Next.js App with SecureNow Using This AI Onboarding Prompt

A copy-paste prompt that lets an AI coding agent install SecureNow, wire Next.js instrumentation, verify traces and logs, deploy to AWS, simulate attacks, and prove firewall blocking with human approval gates.

May 18
nextjs securenow ai onboarding prompt
Adding Backend Tracing to a Sentry Stack with OpenTelemetry

If your team uses Sentry for frontend errors and needs backend distributed tracing without doubling the Sentry bill, here's the OpenTelemetry path that doesn't make you choose.

May 9