On this page
01 Why crawlers need their own limits
A crawler differs from a user in the way that matters to capacity: it walks the long tail. People concentrate on a few popular pages that stay in cache. A crawler requests every archive page, tag listing and paginated view, most of them cold, so each request lands on the origin. Ten requests a second from one crawler can cost more origin CPU than a thousand users. This procedure belongs to the load balancing hub and assumes you already know which crawlers you accept, from measuring and verifying crawler traffic.
02 Token bucket and leaky bucket
A token bucket holds up to B tokens and refills at R tokens per second. Each request spends one; with the bucket empty, the request is refused. It allows a burst of B, then a sustained rate of R, which suits crawlers that fetch a sitemap and then pause. A leaky bucket queues requests and releases them at a fixed rate R; the queue depth is the burst allowance. It smooths traffic into the origin but adds latency to queued requests. Many proxy implementations use leaky bucket semantics with an optional burst that is served immediately rather than delayed, which behaves much like a token bucket.
| Client key | Catches | Collateral risk |
|---|---|---|
| Verified crawler identity | One named crawler across all its addresses | None if verification is correct |
| Single address (/32 or /128) | A single noisy host | Corporate and carrier NAT users |
| IPv4 /24 or IPv6 /48 | A crawler spread over one allocation | Neighbours in shared hosting |
| Origin AS | A fleet across many prefixes | Every customer of a large provider |
| Fingerprint plus address | Unverified bots rotating user-agents | Proxies that share a fingerprint |
03 Procedure
Cache before you limit
Set cache lifetimes on the balancer or proxy for pages that tolerate them, and serve stale content while revalidating. A cache hit costs the origin nothing, and most crawler load vanishes once deep pages are cacheable for even a few minutes.
Classify the client
Tag each request as verified crawler, claimed but unverified crawler, suspected bot or other. Use DNS or published-range verification for the first, and fingerprint and behaviour scoring for the third. The class selects the bucket.
Choose the key and bucket
Key verified crawlers on their identity, other clients on address or prefix. Size rate R from what the origin can serve uncached, and burst B from what a well-behaved crawler does after reading a sitemap.
Refuse correctly
Return
429 Too Many Requestswith aRetry-Aftervalue in seconds. Compliant crawlers back off. Keep503for genuine origin overload, because some crawlers read repeated 503s as the site being down and change how they schedule it.Protect health checks and the origin
Exempt the balancer's own health checks and your monitoring addresses from every limit, and apply limits in front of the real server pool, never on the path the balancer uses to test it. A limiter that fails health checks takes servers out of rotation and turns a crawler problem into an outage.
Log and review
Log key, class, rule, bucket level and decision for every refused request and a sample of passed ones. Review weekly: rising 429s to a verified crawler mean the limit is too tight or the cache too cold.
04 Illustrative config shape
The shape below uses generic open-source reverse proxy syntax with leaky bucket semantics. A map chooses the key: verified crawlers share a key per identity, everyone else is keyed on address. It is a sketch, not a drop-in configuration.
map $crawler_class $limit_key {
verified $crawler_name;
default $binary_remote_addr;
}
limit_req_zone $limit_key zone=crawl:10m rate=5r/s;
limit_req_status 429;
server {
listen 443 ssl;
server_name www.example.com;
location = /healthz { limit_req off; }
location / {
limit_req zone=crawl burst=20 nodelay;
error_page 429 = @throttled;
# hand off to the real server pool here
}
location @throttled {
add_header Retry-After 30 always;
return 429;
}
}05 Rate limiting crawlers: a worked example with timers
Illustrative values. The origin pool behind VIP 192.0.2.40 can render uncached pages at a rate the operators measured during a quiet hour; the budget they are willing to give crawlers is a quarter of it. A verified crawler named crawler-a is keyed on its identity across all its addresses with a sustained rate of 5 requests per second and a burst of 20. Unverified clients that present a crawler user-agent are keyed per address at 1 request per second, burst 5. Retry-After is 30 seconds for the first class and 120 for the second.
Verification comes before the key. A request claiming crawler-a whose source fails the reverse DNS, published range or signed-request check falls into the unverified class, limited per address, and never inherits the named crawler's larger bucket.
$ curl -s -D - -o /dev/null -A 'crawler-a/1.0' https://192.0.2.40/archive/2009/page/412 HTTP/1.1 429 Too Many Requests Retry-After: 30 Cache-Control: no-store Content-Length: 0 $ tail -1 /var/log/proxy/limit.log 10:22:41 key=crawler-a class=verified rule=crawl rate=5 burst=20 tokens=0 decision=429 retry=30 path=/archive/2009/page/412 $ tail -1 /var/log/proxy/limit.log # an unverified client using the same user-agent 10:22:43 key=203.0.113.9 class=unverified rule=crawl-unverified rate=1 burst=5 tokens=0 decision=429 retry=120 path=/tag/foundry
Read the bucket arithmetic. A crawler that fires 60 requests in one second after reading the sitemap spends its 20 burst tokens, receives 40 refusals with a 30 second wait and, if it obeys, returns to a bucket refilled to 20 (5 tokens per second for 30 seconds, capped at the bucket size) and settles into 5 per second. The same crawler at a steady 4 per second never sees a 429.
06 Status codes for excess requests compared
| Response | What it tells a compliant crawler | Effect on its scheduling | Use for |
|---|---|---|---|
| 429 with Retry-After | You, specifically, are over the limit; return after N seconds | Slows its rate for this host | Per-client limits |
| 503 with Retry-After | The server is overloaded | Some crawlers treat repeated 503s as downtime and revisit less | Genuine origin overload only |
| 403 | This request is forbidden | May drop the URL from its schedule | Policy blocks, never rate limits |
| Connection reset | Nothing readable | Retries, often immediately | Never |
07 Where the limiter sits
Put it at the first device that terminates HTTP, usually a Layer 7 balancer or reverse proxy, because it needs the user-agent and path. A Layer 4 device can only limit connections per address, which is blunt. Keep limiter state local to each balancer unless the pair shares it; two units each allowing 5 requests a second allow 10 in total. That matters under persistence rules that pin a crawler to one unit, and it matters in reverse when they do not. Watch the effect on volume by prefix with sampled flow data after each change.
Do not rate limit robots.txt and sitemaps harder than pages. A crawler that cannot read your rules cannot obey them.
08 Failure modes and pitfalls
- Keying on a spoofable header. A limiter that keys on
X-Forwarded-Forwithout stripping client-supplied values lets any client pick a fresh key per request. Key on the address the balancer itself observed, or on the verified identity. - Counting 304s as full renders. A conditional request the cache answers with 304 costs the origin nothing; charging it a full token throttles the best-behaved crawlers first.
- Per-address keys on IPv6. A single host can present a new /128 per request from one /64. Key IPv6 clients on the /64 at least, or on the /48 for hosting ranges.
- Logs that cannot be audited later. When an operator disputes a limit, the log line is the only evidence; keep key, class and bucket state in retained edge logs that can be shown to have been unaltered.
09 Expert tips
- Start with a bucket you believe is too generous and tighten from the 429 count; a limit that is too tight on day one is discovered by the crawler dropping your pages.
- Publish the intended rate for verified crawlers in the policy document and keep the edge configuration in step with it, so operations and the policy owner argue from the same number.
- Order the pipeline verify, cache, then limit; a limiter in front of the cache refuses requests the cache would have served for free.
Rate limiting crawlers is the last of three controls, after caching and policy, and it only works honestly when identity is checked first and every refusal is written down.
Rate limits are the last line. Before them, caching rules that absorb crawler requests remove most repeat load, and a written AI crawler policy decides who is limited at all.
10 Questions
Should crawlers get 429 or 503 when they exceed a limit?
429 Too Many Requests with Retry-After. It says the client is the problem and when to return. 503 says the server is the problem; use it only for real overload, since crawlers may treat repeated 503s as downtime.
What is the difference between a token bucket and a leaky bucket?
A token bucket allows a burst up to its size, then a steady rate. A leaky bucket queues requests and releases them at a steady rate, adding delay instead of refusing. Proxies that serve the burst immediately behave much like a token bucket.
Is limiting by /24 safe?
Safe enough for unverified bots on hosting ranges, risky on residential or corporate networks where one /24 holds many unrelated users. Prefer verified identity for known crawlers and single addresses elsewhere.
Why is caching called the cheapest rate limiter?
A cache hit is served without touching the origin, so crawler requests for cacheable pages cost almost nothing. Tuning cache lifetimes often removes more origin load than any limit and never refuses a legitimate client.
How do I stop rate limits from breaking health checks?
Exempt the health check path and the balancer and monitoring source addresses from every limit rule, and test by watching pool member state while you load the limiter.
Should a rate limit count cache hits and misses the same?
No. A request the proxy answers from cache or with 304 costs the origin nothing, so charging it a full token throttles the crawlers that revalidate politely. Weight cache hits at zero or a fraction, and spend whole tokens only on requests that reach the real servers.