Skip to content

Django Rate Limiting — Protecting Views with django-ratelimit

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Django Rate Limiting. We cover key concepts, practical examples, and best practices to help you master this topic.

Django-ratelimit provides decorators and mixins for rate limiting views in Django applications, supporting IP-based, user-based, and custom key functions with multiple blocking strategies.

What You'll Learn

  • How to install and configure django-ratelimit
  • How to apply rate limits to function-based and class-based views
  • How to use custom key functions for user-based limiting

Why It Matters

Django powers many content sites and APIs. Without rate limiting, a single user can scrape your entire database or brute-force login forms. Django-ratelimit integrates natively with Django's view system, requiring minimal code changes.

Real-World Use

DodaTech's Django-based API uses django-ratelimit on three views: the login view (5 req/min per IP+username), the search API (30 req/min per user), and the contact form (2 req/min per IP). Rate-limited users receive a 429 response with a Retry-After header.

flowchart LR
    A["Request"] --> B["Django View"]
    B --> C{"Rate Limit\nDecorator"}
    C -->|"Within limit"| D["Process\nRequest"]
    C -->|"Exceeded"| E["429 Response"]
    D --> F["Template or\nJSON Response"]
    style B fill:#dbeafe,stroke:#2563eb
    style E fill:#fecaca,stroke:#dc2626

Basic Usage

# views.py
from django_ratelimit.decorators import ratelimit
from django.http import JsonResponse

@ratelimit(key='ip', rate='10/m', method='GET')
def api_data(request):
    was_limited = getattr(request, 'limited', False)
    if was_limited:
        return JsonResponse(
            {'error': 'rate_limit_exceeded'},
            status=429
        )
    return JsonResponse({'data': 'response data'})

User-Based Rate Limiting

from django_ratelimit.decorators import ratelimit
from django.contrib.auth.decorators import login_required

@ratelimit(key='user', rate='100/h', method='ALL')
@login_required
def user_dashboard(request):
    if getattr(request, 'limited', False):
        return JsonResponse({
            'error': 'rate_limit_exceeded',
            'message': 'You have exceeded your hourly limit.',
            'retry_after': 3600
        }, status=429)

    return render(request, 'dashboard.html', {
        'user': request.user
    })

Custom Key Functions

# Custom key function - rate limit by IP + username combination
def auth_rate_key(group, request):
    ip = request.META.get('REMOTE_ADDR')
    username = request.POST.get('username', '')
    return f'{ip}:{username}'

@ratelimit(key=auth_rate_key, rate='5/m', method='POST')
def login_view(request):
    if getattr(request, 'limited', False):
        return JsonResponse({
            'error': 'too_many_attempts',
            'message': 'Too many login attempts. Try again in 1 minute.',
            'retry_after': 60
        }, status=429)

    # Process login
    username = request.POST.get('username')
    password = request.POST.get('password')
    user = authenticate(request, username=username, password=password)

    if user is not None:
        login(request, user)
        return JsonResponse({'status': 'logged_in'})
    return JsonResponse({'error': 'invalid_credentials'}, status=401)

Class-Based Views

from django.utils.decorators import method_decorator
from django.views import View

@method_decorator(ratelimit(key='ip', rate='30/m', method='GET'), name='dispatch')
class SearchView(View):
    def get(self, request):
        if getattr(request, 'limited', False):
            return JsonResponse({
                'error': 'rate_limit_exceeded',
                'message': 'Search rate limit exceeded.'
            }, status=429)

        query = request.GET.get('q', '')
        results = perform_search(query)
        return JsonResponse({'results': results, 'count': len(results)})

Common Mistakes

1. Not Checking request.limited

The decorator sets request.limited = True but does not automatically block the request. You must check this attribute and return a 429 response.

2. Using IP-Based Limiting Behind a Proxy

Behind a reverse proxy, REMOTE_ADDR is the proxy IP. Use request.META.get('HTTP_X_FORWARDED_FOR') instead and configure the proxy headers.

3. Not Specifying method

Without method='POST', the decorator limits all methods including GET for the login page, not just the POST submission.

4. Too Low Limits on Search Endpoints

Search endpoints need higher limits than auth endpoints. 30-60 req/min is reasonable for read APIs.

5. Not Using the Block Option

Set block=True to have django-ratelimit automatically return 429 without needing to check request.limited.

Practice Questions

  1. What decorator does django-ratelimit provide?
  2. How do you rate limit by authenticated user?
  3. What does setting block=True do?
  4. Why might you need a custom key function?
  5. How do you handle rate limiting behind a proxy?

Answers

  1. @ratelimit(key='...', rate='...', method='...'). 2. Use key='user' or key=user_or_ip. 3. Automatically returns 429 without checking request.limited. 4. For rate limiting by IP+username combination for auth endpoints. 5. Use HTTP_X_FORWARDED_FOR header instead of REMOTE_ADDR.

Challenge

Build a Django application with: a login view limited to 5 POST attempts per IP+username per minute, a search API limited to 30 req/min per authenticated user, an admin view with 100 req/min limit, and a custom rate limit middleware that adds X-RateLimit headers to all responses.

FAQ

What is django-ratelimit?

A Django library that provides view decorators and mixins for rate limiting.

Does django-ratelimit block requests automatically?

By default, no. Set block=True for automatic 429 responses.

Can django-ratelimit limit by user?

Yes. Use key='user' or key='user_or_ip' for authenticated users.

How does django-ratelimit store rate limit data?

By default it uses Django's cache framework. Configure a cache backend like Redis.

Can I use different limits for different HTTP methods?

Yes. Use the method parameter and create separate decorators for GET and POST.

Mini Project

Create a Django application with django-ratelimit that implements: per-IP public API limits (30 req/min), per-user authenticated API limits (100 req/min), per-IP+username auth endpoint limits (5 req/min), custom 429 error pages with Retry-After headers, and a Django management command to view current rate limit data from the cache.

What's Next

  • Learn about Spring Boot rate limiting with Bucket4j
  • Explore FastAPI rate limiting with SlowAPI
  • Continue to distributed rate limiting with Redis cluster

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro