IP-Based Rate Limiting — Per-Address Traffic Control for APIs
In this tutorial, you will learn about IP. We cover key concepts, practical examples, and best practices to help you master this topic.
IP-based Rate Limiting restricts requests based on the client's IP address, using the real client IP behind proxies, supporting IPv4 and IPv6, and grouping related addresses with CIDR ranges.
What You'll Learn
- Extracting the real client IP behind proxies
- IPv4 and IPv6 rate limit handling
- CIDR-based grouping for shared IPs (NAT, office networks)
Why It Matters
IP-based rate limiting is the most basic and universal approach. Every client has an IP address, making it easy to implement without authentication. However, IPs can be shared (NAT, VPN, corporate networks) or easily changed (mobile, cloud).
Real-World Use
Durga Antivirus Pro applies a 100 req/min limit per IP for unauthenticated endpoints. This prevents basic abuse without requiring API keys. When a single IP exceeds the limit, the gateway returns 429, protecting backend resources from anonymous scraping.
flowchart LR
Client["Client IP\n203.0.113.50"] --> Proxy["Proxy/CDN"]
Proxy --> GW["Gateway\nRate Limiter"]
GW -->|"Extract real IP\nfrom X-Forwarded-For"| RL["Per-IP Counter"]
RL -->|"100 req/min"| Backend["Backend"]
style GW fill:#dbeafe,stroke:#2563eb
Extracting Real Client IP
from flask import Flask, request
app = Flask(__name__)
def get_client_ip():
# Try X-Forwarded-For (proxy/CDN)
xff = request.headers.get("X-Forwarded-For")
if xff:
# Take the first IP in the chain (real client)
return xff.split(",")[0].strip()
# Try X-Real-IP (Nginx proxy)
xri = request.headers.get("X-Real-IP")
if xri:
return xri
# Fall back to remote address
return request.remote_addr
IP Rate Limiter with Redis
import redis
import time
redis_client = redis.Redis(host="redis", port=6379, db=0)
class IPRateLimiter:
def __init__(self, limit=100, window=60):
self.limit = limit
self.window = window
def allow_request(self, client_ip):
if ":" in client_ip:
# IPv6: normalize and use as-is
key = f"ip:{client_ip}"
else:
# IPv4: optionally group by /24
prefix = ".".join(client_ip.split(".")[:3])
key = f"ip:{prefix}.0/24"
now = int(time.time())
window_key = now - (now % self.window)
counter_key = f"rl:{key}:{window_key}"
count = redis_client.incr(counter_key)
if count == 1:
redis_client.expire(counter_key, self.window * 2)
remaining = max(0, self.limit - count)
reset = window_key + self.window
if count > self.limit:
return False, remaining, reset
return True, remaining, reset
def get_remaining(self, client_ip):
prefix = ".".join(client_ip.split(".")[:3])
key = f"ip:{prefix}.0/24"
now = int(time.time())
window_key = now - (now % self.window)
count = int(redis_client.get(f"rl:{key}:{window_key}") or 0)
return max(0, self.limit - count)
CIDR-Based IP Grouping
import ipaddress
def get_ip_group(client_ip, cidr_size=24):
try:
ip = ipaddress.ip_address(client_ip)
if isinstance(ip, ipaddress.IPv4Address):
network = ipaddress.ip_network(f"{client_ip}/{cidr_size}", strict=False)
return str(network)
else:
# IPv6: use /64 by default
network = ipaddress.ip_network(f"{client_ip}/64", strict=False)
return str(network)
except ValueError:
return client_ip
# Group IPs by /24 for NAT networks
group = get_ip_group("203.0.113.50")
print(f"IP group: {group}") # 203.0.113.0/24
group_v6 = get_ip_group("2001:db8::1")
print(f"IPv6 group: {group_v6}") # 2001:db8::/64
IP Whitelist/Blacklist with Rate Limiting
WHITELISTED_IPS = set(["10.0.0.0/8", "192.168.0.0/16"])
BLACKLISTED_IPS = set(["1.2.3.4", "5.6.7.8"])
def check_ip_access(client_ip):
ip = ipaddress.ip_address(client_ip)
# Check blacklist first
for blocked in BLACKLISTED_IPS:
if ip == ipaddress.ip_address(blocked):
return False, "IP blacklisted"
# Check whitelist (bypass rate limiting)
for wl_cidr in WHITELISTED_IPS:
if ip in ipaddress.ip_network(wl_cidr):
return True, "whitelisted"
# Apply rate limiting
return True, "rate_limited"
Common Mistakes
1. Using Untrusted X-Forwarded-For
Without validating X-Forwarded-For, a client can spoof their IP. Only trust the header if you control the proxy. Always validate input.
2. Not Handling IPv6
IPv4-only rate limiting leaves IPv6 traffic completely unrestricted. Always handle both address families.
3. Individual IP Limits Behind NAT
Companies with 1000 employees behind a single NAT IP will hit per-IP limits quickly. Use per-IP-group limits (/24 for IPv4, /64 for IPv6).
4. Rate Limiting Trusted Proxies
CDN and internal proxy IPs should be whitelisted. Rate-limit the CDN's IP, not your own infrastructure.
5. Not Normalizing IPv6 Addresses
The same IPv6 address can appear in different formats. Use ipaddress module to normalize before using as a key.
Practice Questions
- How do you extract the real client IP when behind a proxy?
- Why should IPv6 addresses be normalized before using as rate limit keys?
- What is the problem with per-IP limits behind NAT?
- How can clients spoof their IP in rate limiting?
- Why should trusted proxy IPs be whitelisted?
Answers:
- Use the first value in
X-Forwarded-Forif present, orX-Real-IP, falling back torequest.remote_addr. - IPv6 addresses have multiple representations (compressed, mixed case). Normalization ensures the same address always produces the same key.
- NAT means 1000 users share one IP. A single user's request can exhaust the limit for everyone else.
- A client can set a fake
X-Forwarded-Forheader if the gateway trusts it without validation. - Whitelisting proxy IPs prevents legitimate infrastructure traffic from being incorrectly rate limited.
Challenge: Design an IP-based rate limiting Strategy for a public API that handles: home users (single IP), corporate users (NAT /24), mobile users (changing IPs), and CDN traffic. Define limits and groupings for each.
FAQ
Mini Project
Build a Flask middleware that extracts the real client IP, groups IPv4 by /24 and IPv6 by /64, applies per-group rate limits of 200 req/min, and adds whitelist support for known proxy IPs. Use Redis for distributed counters.
What's Next
Continue with User-Based Rate Limiting for authenticated clients, or explore API Key Rate Limiting for partner API management.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro