• config

Throttle AI crawlers in nginx instead of blocking them

Tested nginx config: 429 with Retry-After when ClaudeBot, GPTBot or CCBot pass 1 request per second. Googlebot and Bingbot are never counted.

  • v1.0
  • updated 8 November 2026
  • MIT
  • Vladislav Krivorutško

Save nginx-ai-bots-throttle.conf in conf.d, reload nginx, and each training crawler is held to 1 request per second per bot name, with a burst of 10. Anything faster gets 429 Too Many Requests with Retry-After: 10. In the test on nginx 1.30.6, 30 quick requests with a ClaudeBot User-Agent returned 11 × 200 and 19 × 429, while 30 requests each as Googlebot, Bingbot and Chrome returned 30 × 200.

A bot that pauses one second between requests makes at most 86,400 requests a day, whatever the size of the site. The config applies that idea: slow the crawler down instead of closing the door in robots.txt.

Files in this resource

3 files · or all in one zip
  • nginx-ai-bots-throttle.conf

    http-level include: User-Agent maps, rate-limit zones, 429 with Retry-After, rejection log

    6.1 KB
  • README.md

    Install, dry run, rollback and how to change the numbers

    1.8 KB
  • test-throttle.sh

    Functional test in a throwaway Docker container; needs bash, docker and curl

    6.0 KB

What each class gets

The file sorts User-Agents into three classes and gives each its own allowance. Anything that matches no pattern, Googlebot and Bingbot included, has an empty key and is never counted.

ClassBots in the fileAllowance30 quick requests, tested
TrainingClaudeBot, GPTBot, CCBot, Bytespider, meta-externalagent, Amazonbot, anthropic-ai, MistralAI-Training1 r/s, burst 10, per bot name11 × 200, 19 × 429
SearchOAI-SearchBot, Claude-SearchBot, PerplexityBot, Meta-WebIndexer, Amzn-SearchBot, MistralAI-Index, DuckAssistBot, Applebot5 r/s, burst 20, per bot name21 × 200, 9 × 429
User-triggeredChatGPT-User, Claude-User, Perplexity-User, meta-externalfetcher, Amzn-User, MistralAI-User5 r/s, burst 20, per bot name21 × 200, 9 × 429
Everything elseGooglebot, Bingbot, browsers, empty User-Agentnone30 × 200

The grouping is the author's, built from the operators' own crawler pages on 2026-11-08; the sources for every bot are in the reference table of the robots.txt resource. CCBot, Bytespider and anthropic-ai sit in the training class by the author's call: the operators' pages do not tie them to training.

Install

bash
sudo cp nginx-ai-bots-throttle.conf /etc/nginx/conf.d/ai-bots-throttle.conf
sudo nginx -t && sudo nginx -s reload
sudo tail -f /var/log/nginx/ai-bots-throttle.log

The stock nginx.conf includes conf.d/*.conf inside http { }, so the file covers every server on that nginx. It needs nginx 1.17.6 or newer, because the rejection log and the Retry-After header read $limit_req_status.

One caveat: add_header and access_log are not inherited into a server or location that defines its own. If yours does, repeat the Retry-After and access_log lines there. nginx 1.29.3 added add_header_inherit merge; for the headers; it was not tested here.

Run it as a dry run for the first week. Uncomment limit_req_dry_run on; (available since nginx 1.17.1), reload, and read the log: lines marked REJECTED_DRY_RUN are the requests the live config would have refused. Nothing is refused yet.

How the config works

Three directives do the work. The map turns a User-Agent into the bot's name, the zone counts requests per name, and limit_req applies the zone.

nginx
map $http_user_agent $ai_bot_train {
    default "";
    ~*ClaudeBot   ClaudeBot;
    ~*GPTBot      GPTBot;
    # ... the rest of the training class
}
limit_req_zone $ai_bot_train zone=ai_train:1m rate=1r/s;
limit_req      zone=ai_train burst=10 nodelay;
limit_req_status 429;

The nginx documentation says that requests with an empty key value are not accounted. A visitor whose User-Agent matches no line gets the key "", so nothing is counted for it. The test confirms it: 30 quick requests as Googlebot, as Bingbot, as Chrome and with no User-Agent at all all returned 200.

burst=10 nodelay means the first request and ten more pass at once, which makes 11, and after that the bot gets one request per second. The test measured it: with the bucket drained and a 3 second wait, 10 requests returned 3 × 200 and 7 × 429. Retry-After: 10 is the time a full burst needs to refill at 1 request per second.

The key is the bot name, not the IP address. All addresses of ClaudeBot share one allowance, so "1 request per second" holds for the whole bot. GPTBot has its own bucket: in the test it got 11 × 200 right after ClaudeBot had been refused.

Do the bots slow down on a 429?

None of the operator pages used for this resource says how its crawler reacts to a 429 or to Retry-After. What they do document is Crawl-delay: Anthropic says it supports the non-standard extension, and the Common Crawl FAQ says CCBot obeys it. Amazon says Amazonbot does not support it, and Apple says Applebot does not follow it. Google does not support it.

So the plan has two parts. Add Crawl-delay in robots.txt for ClaudeBot and CCBot, and keep the throttle for the rest. After a week, count the refusals per bot:

bash
awk -F'"' '/REJECTED/ {print $2}' /var/log/nginx/ai-bots-throttle.log | sort | uniq -c | sort -rn

A bot that backs off shows fewer refusals in week 2 than in week 1. A bot with the same count in both weeks ignores the 429 and its Retry-After: 10, and the throttle then only protects your server.

Pick the numbers from your own log

The defaults (1 r/s for training bots, 5 r/s for the other two classes) are starting values chosen by the author, not vendor recommendations. To see what ClaudeBot does today, count its requests per second in the access log:

bash
grep -i claudebot /var/log/nginx/access.log | awk '{print $4}' | sort | uniq -c | sort -rn | head -5

The first line is the busiest second. If ClaudeBot peaks at 9 requests per second and you are willing to serve 2, set rate=2r/s. The log audit kit gives the same view per bot and per URL.

Rather have this done for you?

Send the site address and what you want to know. We run the audit and send the report.

Send a request

Why Googlebot and Bingbot are not in the list

Google's page on reducing crawl rate names 500, 503 and 429 as the status codes for slowing Googlebot down, and limits the advice to 1 to 2 days. It adds that if Googlebot sees these codes on the same URL for several days, the URL may be dropped from the index. A permanent throttle on Googlebot is that situation. Google's alternative is a special request filed through Search Console, which it says can take several days to evaluate. Only Google's documentation was checked for this resource; Bingbot is left out by the same reasoning, not by a Bing source.

What it does not do

The throttle works on the User-Agent string, which anyone can write. Faked ClaudeBot requests are throttled like real ones, which is fine for load control. Two things follow from it.

A faker spends the real bot's allowance, because the key is the bot name. To limit each address separately, replace the bot name in the three maps with $binary_remote_addr and raise the zone size; one megabyte holds about 8,000 states of 128 bytes. In a test with that change, a single client sending a ClaudeBot User-Agent got 11 × 200 and 19 × 429, and Googlebot got 30 × 200. Behind a CDN or load balancer, $remote_addr is the proxy's address, so restore the client address first with the realip module.

And the throttle does not tell you who is calling. To let only real crawlers through you compare the source IP with the operator's published ranges. Google describes a reverse and forward DNS check and publishes JSON range files; OpenAI, Anthropic, Perplexity, Apple and DuckDuckGo publish JSON lists too, and the links are in the robots.txt resource. The log audit kit runs that comparison on your logs.

One more cost sits in the third class. When someone asks ChatGPT or Claude about your page, a user-triggered fetch arrives. At 5 requests per second per bot name, a burst of such questions about one viral page can meet a 429. Delete the ai_user lines if you would rather never refuse them.

Who should not use it

  • You serve most bot requests from a CDN cache. The origin sees only the misses, and the throttle protects little; set the limit at the CDN.
  • You need a bot kept out, not slowed. That is a robots.txt Disallow for bots that honor it and a WAF or firewall rule for those that do not.
  • You run Apache or IIS. The file is nginx only, and no Apache variant was tested.

Tested on

ItemValue
Date2026-11-08, about 17:48 UTC
nginx1.30.6 (nginx:stable, image ID 42c68eab6f7e), 1.29.3 (nginx:alpine)
HostDocker Desktop 29.7.2 on Windows 11, curl 8.15.0
Commandbash test-throttle.sh, then IMAGE=nginx:alpine PORT=18081 bash test-throttle.sh
nginx -tsyntax is ok, test is successful, on both images
Resultall checks passed on both images

Results from the nginx 1.30.6 run, 30 requests per row on one keep-alive connection:

User-Agent200429
Chrome300
Googlebot300
Bingbot300
No User-Agent300
ClaudeBot (training)1119
GPTBot (training, own bucket)1119
OAI-SearchBot (search)219
ChatGPT-User (user-triggered)219

The 429 response carried Retry-After: 10; the 200 responses carried no such header. The rejection log held 98 lines, equal to the 98 limiting requests lines in the error log. In the dry run, 30 requests as meta-externalagent all returned 200 and the log recorded 19 REJECTED_DRY_RUN lines. The container was removed at the end of each run.

Sources

SourceUsed for
nginx: ngx_http_limit_req_moduleEmpty key not accounted, burst and nodelay, limit_req_status, $limit_req_status, dry run
nginx: ngx_http_headers_modulealways parameter and add_header inheritance
Google: reduce the Googlebot crawl rate500, 503 and 429 for 1 to 2 days, URLs dropped after several days
Google: verifying GooglebotReverse and forward DNS check, JSON range files
Anthropic: crawler help articleCrawl-delay support, published IP list
Common Crawl FAQCCBot obeys Crawl-delay
Amazonbot and ApplebotNo Crawl-delay support

Checked on 2026-11-08.

Files are provided as is: run them on your own data and check the result.

Want an audit like this on your site?

If you would rather have it done for you, send a request and describe the site.

Send a request
Download nginx-ai-bots-throttle.conf