Seatext library / BotRefund evidence

How to Set Up Rate Limiting to Prevent Bot Attacks: A Practical Implementation Guide

Rate limiting restricts how many requests a single IP can make within a defined time window. Configure your web server, CDN, or application layer to enforce limits and return HTTP 429 responses when thresholds...

Built for advertisers who need clear, refund-ready traffic evidence.

Rate limiting is a foundational layer for reducing automated abuse. The core idea is simple: define a maximum number of requests allowed from a single identifier (usually an IP address) within a rolling or fixed time window, then reject or throttle anything beyond that limit with a 429 Too Many Requests response. Most production setups apply limits at the edge (CDN or load balancer), at the web server (Nginx, Apache), and optionally inside the application for sensitive endpoints like login, registration, or API calls.

What Rate Limiting Actually Does

Rate limiting does not identify bots directly. It enforces a traffic budget per client. Legitimate users rarely hit reasonable limits; scrapers, credential stuffers, and brute-force scripts often do. When a limit is exceeded, the server responds with 429 and optionally a Retry-After header telling the client when to try again. This slows down automated campaigns and reduces the volume of malicious requests that reach your application logic.

The technique works best when combined with behavioral detection. BotRefund, for example, uses over 100 independent browser, network, device, and behavior signals to distinguish humans from automation, then feeds those signals into an AI model that achieves 99% accuracy in classifying visits. Rate limiting handles volume; behavioral analysis handles sophistication.

Where to Enforce Limits: Edge, Server, or Application

Choose the enforcement point based on what you control and what you need to protect.

  • CDN / WAF edge (Cloudflare, AWS WAF, Fastly, Akamai): Stops attack traffic before it hits your origin. Best for global applications and DDoS-style volume.
  • Web server (Nginx, Apache, OpenResty): Runs on your infrastructure. Good for per-route limits, custom keys (session ID, API key), and when you cannot use a CDN.
  • Application middleware (Express, Django, Spring, Go chi): Allows business-logic-aware keys (user ID, account tier) and complex conditions (e.g., stricter limits on password reset).

Layering multiple points is common: a generous global limit at the edge, tighter per-endpoint limits at the server, and the strictest rules in the application for high-value actions.

Step-by-Step: Nginx Rate Limiting

Nginx uses the ngx_http_limit_req_module. The configuration has two parts: a shared memory zone that defines the key and rate, and a limit_req directive that applies it.

  1. Define a zone in the http block:
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    This creates a 10 MB zone named api_limit keyed by client IP, allowing 10 requests per second.
  2. Apply the zone to a location or server block:
    location /api/ { limit_req zone=api_limit burst=20 nodelay; limit_req_status 429; }
    burst=20 lets a client exceed the rate briefly (up to 20 queued requests). nodelay processes burst requests immediately instead of spacing them. limit_req_status 429 sets the response code.
  3. Test with nginx -t and reload.

For login endpoints, use a stricter zone: limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m; (5 requests per minute). Apply it only to location /login.

Step-by-Step: Apache Rate Limiting

Apache 2.4+ uses mod_ratelimit for bandwidth throttling and mod_security or mod_evasive for request-rate limits. A practical approach with mod_security:

  1. Enable mod_security and the OWASP Core Rule Set (CRS).
  2. Add a rule targeting your login or API path:
    SecRule REQUEST_URI "^/login" "id:10001,phase:1,initcol:ip=%{REMOTE_ADDR},setvar:ip.rate=+1,expirevar:ip.rate=60,block,msg:'Rate limit exceeded'"
    This increments a per-IP counter on each request to /login, expires it after 60 seconds, and blocks when the internal threshold is crossed (configure SecAction"id:900000,phase:1,pass,nolog,setvar:ip.rate_threshold=5" to set the limit).
  3. Restart Apache and monitor the audit log.

If you prefer a lighter module, mod_evasive provides DOSHashTableSize, DOSPageCount, DOSSiteCount, and DOSPageInterval directives for per-IP request counting.

Step-by-Step: Cloudflare Rate Limiting Rules

Cloudflare's dashboard (Security → WAF → Rate Limiting Rules) lets you create rules without code changes.

  1. Click Create rule.
  2. Define the traffic match: e.g., Field: Path, Operator: starts_with, Value: /api/.
  3. Set the counting key: usually IP Address, but you can use CF-Connecting-IP, API Key, or a custom header.
  4. Configure the threshold: Requests (e.g., 100) per Period (e.g., 1 minute).
  5. Choose Action: Block (returns 429), JS Challenge, or Managed Challenge.
  6. Optionally add a Response header Retry-After with the reset time.
  7. Save and deploy. Use Preview mode first to see matched requests without blocking.

Cloudflare also offers Advanced Rate Limiting with multiple characteristics (country, ASN, cookie) and Rate Limiting Analytics to tune thresholds before enforcement.

Step-by-Step: Application-Level Rate Limiting (Node/Express Example)

Application-level limits let you key on authenticated user ID or API token, which is impossible at the edge when traffic is encrypted end-to-end.

const rateLimit = require('express-rate-limit');

const apiLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100,            // limit each IP to 100 requests per window
  standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
  legacyHeaders: false,
  keyGenerator: (req) => req.ip, // or req.user.id for authenticated routes
  handler: (req, res) => res.status(429).json({ error: 'Too many requests, please try again later.' })
});

app.use('/api/', apiLimiter);

For stricter endpoints, create a separate limiter: const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 }); and apply only to app.post('/login', loginLimiter, ...).

Key Configuration Parameters and How to Choose Them

ParameterTypical RangeGuidance
Window size1 second – 15 minutesShort windows (1–10 s) catch burst scripts; longer windows (1–15 min) protect login, password reset, and API quotas.
Max requests5 – 1,000+Start generous (e.g., 100/min for API, 5/min for login). Monitor false positives and tighten gradually.
Key / identifierIP, session, user ID, API keyIP works for anonymous traffic. Use user ID or API key for authenticated routes to avoid penalizing shared networks (offices, universities).
Burst allowance0 – 2× rateAllow short bursts for legitimate spikes (page load with many assets). Set burst in Nginx or max higher than average in application limiters.
Response code429 (standard), 403, 503Use 429 with Retry-After header. Avoid 403 (looks like a permanent block) or 503 (implies server error).
Challenge vs. blockJS challenge, managed challenge, blockChallenges (Cloudflare) let humans pass while stopping headless browsers. Use for borderline thresholds; block for clear abuse.

Common Mistakes and How to Verify

  • Using only IP as the key behind a CDN or load balancer. The origin sees the CDN's IP, not the client. Fix: configure the CDN to forward CF-Connecting-IP, X-Forwarded-For, or True-Client-IP and trust that header in your server/app.
  • Setting limits too low on static assets. A single page load can generate 20–50 requests for CSS, JS, images. Exclude static paths or use a much higher limit for them.
  • No monitoring or alerting. You won't know if legitimate users are blocked. Log every 429 response and alert on rate-limited IPs that also have successful conversions.
  • Ignoring IPv6. $binary_remote_addr in Nginx handles both IPv4 and IPv6. In application code, ensure your key generator normalizes IPv6 (e.g., ::ffff:1.2.3.41.2.3.4 or use the full address).

Verification step: After deployment, run a controlled test from a staging IP. Use curl -I or a load-test tool (hey, vegeta, k6) to send requests at 1.5× your limit. Confirm you receive 429 with a Retry-After header. Check your logs for the expected entries. Then simulate a real user journey (browser, multiple assets) to ensure normal traffic passes.

Limitations of Rate Limiting Alone

Rate limiting is a volume control, not a bot detector. It cannot distinguish a fast human from a slow bot. Sophisticated attackers distribute requests across thousands of residential proxies, keeping each IP below the threshold. They also rotate headers, mimic mouse movements, and solve CAPTCHAs.

This is why BotRefund layers 110+ behavioral, browser, hardware, network, and attribution signals — including checks like Playwright Init Scripts detection, Scrollbar Width Leak, and Clean Context Iframe — into an AI model that evaluates the complete pattern rather than trusting a single rule. Each signal is kept as evidence, not a verdict, and cross-checked against independent data sources. The result is a 99% confidence classification that identifies bots even when they respect rate limits.

Across 2,500+ brands audited, 83% of clients recover funds from Google and Meta using BotRefund's refund-ready reports, which include click IDs, campaign details, timestamps, session recordings, and signal-by-signal reasoning formatted for platform review teams.

Key Facts from BotRefund's Detection Approach

CapabilityDetailSource
Signal count110+ independent behavioral, browser, hardware, network, and attribution signalsS2
Detection confidence99% confidence in flagged bot trafficS2
Client recovery rate83% of 2,500+ audited clients recover funds from Google and MetaS2
Report formatRefund-ready reports with click IDs, campaign details, timestamps, session recordings, signal-by-signal reasoningS2
Playwright Init Scripts checkDetects mismatches from automation tools patching or hiding browser APIsS1
Scrollbar Width Leak checkIdentifies scripts that struggle to reproduce varied human timing, movement, and hesitationS5
Clean Context Iframe checkFinds automation-induced API inconsistencies when checked from a clean iframe contextS7
Evidence philosophyEach signal is evidence, not a verdict; cross-checked across browser, network, device, and behavior dataS1, S5, S7

Practical Scenarios: Matching Limits to Risk

ScenarioSuggested LimitEnforcement PointNotes
Public API (anonymous)60 req/min per IPCDN + API gatewayAdd API-key tiered limits for registered developers.
Login / password reset5 req/15 min per IPWeb server + applicationCombine with CAPTCHA after 3 failures.
Account registration3 req/hour per IPApplication (key on email domain + IP)Prevents bulk account creation.
Search / autocomplete30 req/min per user IDApplicationKey on authenticated user; fallback to IP for guests.
Checkout / payment10 req/5 min per sessionApplicationStrict; pair with fraud scoring.
Static assets (CDN)500 req/min per IPCDN edgeHigh limit; mostly prevents hotlinking and scrapers.

Terminology Quick Reference

  • Rate limit: Maximum allowed requests per key per window.
  • Window: Time interval (fixed or rolling) over which requests are counted.
  • Key: Identifier used to group requests (IP, user ID, API key, session).
  • Burst: Temporary allowance above the sustained rate.
  • 429 Too Many Requests: Standard HTTP status for rate-limited responses.
  • Retry-After: Header indicating seconds or a date when the client may retry.
  • Challenge: Interactive test (JavaScript, CAPTCHA) to verify humanity before allowing the request.
  • Signal: An observable attribute (browser API, timing, network) used as evidence in bot detection.

Frequently Asked Questions

Does rate limiting stop all bot traffic?

No. It stops high-volume, single-IP automation. Low-and-slow bots, distributed botnets using residential proxies, and sophisticated headless browsers that mimic human pacing often stay under typical thresholds. Pair rate limiting with behavioral detection for complete coverage.

What is a safe starting limit for a public API?

Start with 100 requests per minute per IP for anonymous endpoints. Monitor 429 rates and legitimate user complaints for two weeks, then adjust. Authenticated endpoints can use higher limits keyed on user ID or API token.

How do I handle shared IPs (corporate NAT, university, mobile carrier)?

Use a less restrictive limit for anonymous traffic and move stricter limits to authenticated routes keyed on user ID. Alternatively, use a CDN that can distinguish clients via TLS fingerprinting or cookie-based identifiers.

Should I block or challenge at the edge?

Challenge (JS or managed) for borderline thresholds; it lets real humans through while stopping most headless browsers. Block (429) for clear abuse patterns (e.g., 100+ login attempts in a minute). Always log the action for review.

How does BotRefund complement rate limiting?

BotRefund analyzes each session with 110+ signals — browser automation artifacts, behavioral biometrics, network reputation, hardware fingerprints — and feeds them into an AI model that classifies visits with 99% confidence. Rate limiting reduces volume; BotRefund identifies the sophisticated bots that volume limits miss, and produces the evidence needed for ad-platform refunds.

What evidence do I need for a Google or Meta refund claim?

Platforms require click IDs (GCLID, FBCLID), timestamps, campaign structure, and a clear explanation of why the traffic is invalid. BotRefund automates this by generating refund-ready reports with session recordings and signal-by-signal reasoning formatted for Google and Meta review teams.

Can I implement rate limiting without a CDN or server config access?

Yes. Application middleware (express-rate-limit, Django Ratelimit, Spring Boot Bucket4j, Go chi/ratelimit) works entirely in your code. The trade-off is that attack traffic still reaches your application processes, consuming CPU and memory before being rejected.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

Learn more

Visit the website for more information.

Learn more