Rate LimitingRate Limit Strategies (Per-User, Per-IP, Global)Medium⏱️ ~3 min

Per IP Rate Limiting: Anonymous Traffic Protection

Per IP rate limiting applies quotas based on the source IP address extracted from the connection or forwarded headers. This strategy protects public endpoints that serve unauthenticated traffic where you cannot rely on user identity. When a request arrives, you hash the IP to a bucket, check the counter, and either allow or deny. GitHub uses per IP limits of 60 requests per hour for unauthenticated API access, while Bluesky enforces a global per IP cap of 3,000 API calls per 5 minutes. The critical problem with per IP limiting is Network Address Translation (NAT) and Carrier Grade NAT (CGNAT) where thousands of users share a single public IP. Bluesky's 3,000 calls per 5 minutes sounds generous until you realize a service with 3,000 customers behind a corporate proxy averages only 1 operation per customer per 5 minutes. Enterprise offices, university campuses, and mobile carriers routinely have thousands to tens of thousands of users behind one IP. IPv6 adoption helps but creates new challenges: attackers can easily rotate through vast address ranges, and privacy extensions randomize addresses making tracking difficult. Per IP limits shine as a complementary defense layer. Apply them before authentication to stop distributed denial of service (DDoS) and credential stuffing attacks at the edge. Set thresholds higher than per user limits to avoid punishing NAT scenarios: if per user is 100 requests per minute, set per IP to 1,000 or 10,000 requests per minute. Combine with allowlists for known good IPs like your own monitoring systems or partner networks. Implementation uses the same counter mechanisms as per user limiting but with IP addresses as keys. At millions of unique IPs per hour, memory becomes significant. A single counter per IP per window costs roughly 16 bytes (8 byte timestamp plus 8 byte count). With 10 million unique IPs in a sliding 5 minute window, you need 160 megabytes just for counters. Use TTL based eviction aggressively: expire entries 2x the window duration to reclaim memory. For handling forwarded headers like X Forwarded For, always take the leftmost untrusted IP to prevent spoofing, and validate IP format to avoid injection attacks.
💡 Key Takeaways
Bluesky enforces 3,000 API calls per 5 minutes per IP globally; with 3,000 customers behind one corporate proxy, each averages only 1 operation per 5 minutes, forcing workarounds like multiple IPs
NAT and CGNAT scenarios mean thousands of legitimate users often share a single public IP address, making strict per IP limits punish entire organizations for one abusive user
Set per IP thresholds 10x to 100x higher than per user limits to account for shared IPs: if per user is 100 requests per minute, set per IP to 1,000 or 10,000 requests per minute
Memory scales with unique IP count: at 10 million unique IPs in a 5 minute window with 16 bytes per counter, expect roughly 160 megabytes of state storage
IPv6 privacy extensions and vast address space allow attackers to rotate IPs easily, reducing the effectiveness of IP based blocking without additional signals
Per IP limiting works best as a pre authentication DDoS shield combined with allowlists for known good sources like monitoring systems and partner networks
📌 Examples
GitHub applies 60 requests per hour per IP for unauthenticated clients, rising to 5,000 per hour once authenticated with a token, showing tiered protection
Extract IP from X Forwarded For header by taking the leftmost untrusted address to prevent spoofing: if header is "1.2.3.4, 5.6.7.8, 9.10.11.12" and your proxy is 9.10.11.12, use 5.6.7.8 as the client IP
← Back to Rate Limit Strategies (Per-User, Per-IP, Global) Overview