Bots & traffic
8 min readUpdatedHow to block AI crawlers: robots.txt, server rules and IP checks
Quick answer
To block AI crawlers, list each operator's documented token in robots.txt under Disallow: /, for example GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, CCBot and meta-externalagent, and leave search crawlers allowed. robots.txt is a request, not enforcement: some user-triggered fetchers may ignore it, so refuse those agents at the server too, keep robots.txt itself reachable, and check claimed crawlers against the operators' published IP ranges.
01Which AI crawlers are there? The tokens, checked October 2026
Each AI operator documents its own tokens, and most now run separate bots for training, search and user-triggered fetching, so the first decision is which of those to refuse. The table records what each operator's own documentation said in October 2026. Tokens change, so recheck the sources before relying on it.
Two entries are not crawlers. Google-Extended and Applebot-Extended are robots.txt tokens only: Google and Apple crawl with Googlebot and Applebot and use the token to decide how the content may be used. Both companies say these tokens do not affect inclusion in their search results.
| Token | Operator | Purpose | Follows robots.txt? | Published IP list |
|---|---|---|---|---|
| GPTBot | OpenAI | Content that may be used to train its models | Yes | openai.com/gptbot.json |
| OAI-SearchBot | OpenAI | Results in ChatGPT search | Yes, within about 24 hours | openai.com/searchbot.json |
| ChatGPT-User | OpenAI | Pages fetched when a user asks | Rules may not apply, says OpenAI | openai.com/chatgpt-user.json |
| ClaudeBot | Anthropic | Content that could contribute to model training | Yes | claude.com/crawling/bots.json |
| Claude-SearchBot | Anthropic | Indexing to improve search results | Yes | The same list |
| Claude-User | Anthropic | Pages fetched for a user's question | Yes | The same list |
| Google-Extended | Token: use in Gemini model training and grounding | Yes, as a token | Googlebot's ranges | |
| Applebot-Extended | Apple | Token: use in Apple foundation model training | Yes, as a token | Applebot's list |
| PerplexityBot | Perplexity | Search results; not used for model training, says Perplexity | Yes | perplexity.com/perplexitybot.json |
| Perplexity-User | Perplexity | Pages fetched for a user's question | Generally ignores it, says Perplexity | perplexity.com/perplexity-user.json |
| CCBot | Common Crawl | An open web archive that anyone can reuse | Yes, including Crawl-delay | index.commoncrawl.org/ccbot.json |
| Bytespider | ByteDance | Search and AI model data, according to third-party directories | Not verified: documented only on ByteDance's Chinese-language webmaster site | Not verified |
| meta-externalagent | Meta | Training AI models and indexing content | Yes | None on Meta's crawler page |
| meta-webindexer | Meta | Results and citations in Meta AI search | Yes | None on Meta's crawler page |
| meta-externalfetcher | Meta | Links fetched at a user's request | May bypass it, says Meta | None on Meta's crawler page |
02How to block AI crawlers in robots.txt
List the tokens to refuse in one group and give it Disallow: /. Several User-agent lines may share a group. RFC 9309 makes crawlers match their token case-insensitively and obey the group that names them, falling back to the * group only when none does, so search crawlers that are not listed keep following the * group.
Two details catch people out. robots.txt applies per host, so every subdomain needs its own file, as Anthropic's documentation points out. And changes are not instant: RFC 9309 lets crawlers cache the file for up to 24 hours, and OpenAI, Perplexity and Meta each mention a delay of about a day.
| Goal | Tokens to disallow | What still happens |
|---|---|---|
| Opt out of model training only | GPTBot, ClaudeBot, Google-Extended, Applebot-Extended, CCBot, Bytespider, meta-externalagent | AI search crawlers can still index the site and cite it |
| Opt out of AI search as well | Add OAI-SearchBot, Claude-SearchBot, PerplexityBot and meta-webindexer | OpenAI says opted-out sites can still appear as navigational links in ChatGPT |
| Refuse user-triggered fetches too | Add ChatGPT-User, Claude-User, Perplexity-User and meta-externalfetcher | Some of these may ignore robots.txt, so enforce at the server |
# Opt out of the AI crawlers and fetchers in the table above (checked October 2026)
User-agent: GPTBot
User-agent: OAI-SearchBot
User-agent: ChatGPT-User
User-agent: ClaudeBot
User-agent: Claude-SearchBot
User-agent: Claude-User
User-agent: Google-Extended
User-agent: Applebot-Extended
User-agent: PerplexityBot
User-agent: Perplexity-User
User-agent: CCBot
User-agent: Bytespider
User-agent: meta-externalagent
User-agent: meta-externalfetcher
User-agent: meta-webindexer
Disallow: /
# Everyone else, search engines included
User-agent: *
Disallow: /account/03What robots.txt cannot do
robots.txt states a preference that well-run crawlers honour. RFC 9309 says its rules 'are not a form of access authorization', and the protocol has several further limits.
- It does not stop clients that ignore it, including user-triggered fetchers whose operators say robots.txt may not apply
- It works only from now on: Anthropic, for example, describes a ClaudeBot block as excluding the site's future materials from training
- It cannot reach copies held elsewhere, such as archives other crawlers already made or pages that users paste into an assistant
- It does not stop impersonators or unnamed scrapers, which is why web scraping needs its own defences
- A robots.txt that answers with a 5xx error tells every crawler, search engines included, to assume complete disallow, while a 4xx answer tells them they may crawl everything
04How to block AI crawlers in nginx
Server rules enforce what robots.txt only requests. A map in the http context sets a variable from the User-Agent header, and the location refuses matching requests with 403. The patterns are case-insensitive regular expressions, so the documented tokens match however an operator capitalises them.
Keep robots.txt itself outside the block. Under RFC 9309 a 4xx answer for robots.txt means the file is unavailable and crawlers may access everything, and Anthropic warns that blocking its crawlers' addresses stops them reading the opt-out. A refused robots.txt is therefore worse than none.
This works only for crawlers that announce themselves. Google-Extended and Applebot-Extended never appear in a User-Agent header, and refusing Googlebot or Applebot would remove the site from their search engines, so those two can be handled only in robots.txt.
# /etc/nginx/conf.d/ai-crawlers.conf (http context)
map $http_user_agent $ai_crawler {
default 0;
~*GPTBot 1;
~*ClaudeBot 1;
~*CCBot 1;
~*Bytespider 1;
~*meta-externalagent 1;
# add OAI-SearchBot, Claude-SearchBot, PerplexityBot, ChatGPT-User,
# Claude-User, Perplexity-User, meta-externalfetcher or meta-webindexer to refuse them too
}
# in the server block
location = /robots.txt {
try_files $uri =404; # served to everyone, AI crawlers included
}
location / {
if ($ai_crawler) {
return 403;
}
# ... the site's normal configuration
}05How to tell a real AI crawler from an impersonator
A User-Agent header proves nothing, so when one AI crawler is allowed and others are refused, check the address too. OpenAI, Anthropic, Perplexity and Common Crawl publish their ranges as JSON files with a creationTime field showing when each list was generated, and Common Crawl also supports reverse DNS under crawl.commoncrawl.org. The general method, including reverse DNS for search engines, is in the guide to verifying web crawlers, and an IP lookup shows which network an address belongs to.
The snippet below turns OpenAI's search crawler list into an nginx geo file and refuses requests that claim to be OAI-SearchBot from anywhere else. Regenerate the file on a schedule and reload nginx afterwards, because the lists change. Behind a CDN or load balancer, configure the realip module first, or every request appears to come from the proxy and the real crawler is refused as well.
# 1. Build the geo file from OpenAI's published list (via a temporary file, so a failed download keeps the old list)
curl -fsS https://openai.com/searchbot.json | python3 -c 'import json,sys; [print(p.get("ipv4Prefix") or p.get("ipv6Prefix"), "1;") for p in json.load(sys.stdin)["prefixes"]]' > /etc/nginx/openai-searchbot.geo.new && mv /etc/nginx/openai-searchbot.geo.new /etc/nginx/openai-searchbot.geo
# 2. In the http context
geo $from_openai_search {
default 0;
include /etc/nginx/openai-searchbot.geo;
}
map $http_user_agent $claims_oai_search {
default 0;
~*OAI-SearchBot 1;
}
map "$claims_oai_search:$from_openai_search" $fake_oai_search {
default 0;
"1:0" 1;
}
# 3. In location /
if ($fake_oai_search) {
return 403;
}06How to see which AI crawlers visit a site
Count them in the access log before and after a change: it shows whether the opt-out took effect and which agents ignore it. In nginx's default combined log format the User-Agent is the sixth field when a line is split on double quotes. OpenAI says it may add a robots.txt marker to its user-agent when it fetches that file, which makes those checks easy to spot.
# requests per AI agent (one count per request)
awk -F'"' '{ua = tolower($6)}
match(ua, /gptbot|oai-searchbot|chatgpt-user|claudebot|claude-searchbot|claude-user|perplexitybot|perplexity-user|ccbot|bytespider|meta-externalagent|meta-externalfetcher|meta-webindexer/) {
print substr(ua, RSTART, RLENGTH)
}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# what the training crawlers requested and the status they got
awk -F'"' 'tolower($6) ~ /gptbot|claudebot|ccbot|bytespider|meta-externalagent/ {split($3, s, " "); print $2, s[1]}' \
/var/log/nginx/access.log | sort | uniq -c | sort -rn07Why is an AI crawler still visiting?
| Symptom | Likely cause | How to check | Fix |
|---|---|---|---|
| Visits continue for hours after the change | robots.txt is cached for up to 24 hours | Look for a fresh robots.txt fetch by that agent in the log | Wait a day before judging |
| Visits continue for days | A wrong or outdated token, or the rule sits in another host's robots.txt | Fetch robots.txt on the exact host and subdomain being crawled | Use the operator's current token on every host |
| The agent's name arrives from addresses outside its list | An impersonator | Compare the address with the operator's JSON list | Refuse it at the server or at the edge |
| ChatGPT-User, Perplexity-User or meta-externalfetcher still fetch pages | User-triggered fetchers may not follow robots.txt | Their user-agent in the log | Refuse them with a server rule if they are unwanted |
| Search traffic fell after the change | A search crawler was caught, for example by Disallow: / under * | Crawl statistics in Google Search Console and Bing Webmaster Tools | Disallow only the AI tokens |
| Every crawler stopped, search included | robots.txt returns a 5xx error, which means complete disallow | curl -sI https://example.com/robots.txt | Serve robots.txt as a static file that always answers 200 |
| Crawlers ignore every rule | robots.txt is refused with 403, which means no rules apply | The status robots.txt returns to that user-agent | Exempt robots.txt from every block |
08What a CDN or WAF can and cannot do about AI crawlers
An edge in front of the site can apply the same rules before requests reach the origin, check claimed crawlers against published ranges at scale, rate-limit the agents that are tolerated and serve them cached pages, so a heavy crawl costs the origin little. Bot management goes further, challenging clients that claim to be browsers but behave like scripts.
No edge does everything. It cannot recognise every undeclared scraper with certainty or recall content that was collected or copied earlier, and challenging all automation also catches assistants, accessibility tools and monitors that may be wanted. Blocking has a price too: OpenAI says sites that opt out of OAI-SearchBot are not shown in ChatGPT search answers, and Anthropic says blocking Claude-SearchBot or Claude-User may reduce visibility in its search results. Decide agent by agent, not all at once.
Questions
Frequently asked questions
Sources
Sources and verification
robots.txt parsed with urllib.robotparser from Python 3.11.2; nginx rules checked with nginx -t and live requests against a scratch nginx 1.22.1 instance (403 for listed agents, 200 for robots.txt and browsers, 403 for an OAI-SearchBot claim from outside the published ranges); log commands run with GNU awk 5.2.1 on a sample log; IP lists fetched from the operators and the geo file built with curl 7.88.1, including a failed download that left the previous list in place; Debian 12, October 2026.
- 01RFC 9309: Robots Exclusion Protocol · IETF
- 02Overview of OpenAI crawlers · OpenAI
- 03Does Anthropic crawl data from the web, and how can site owners block the crawler? · Anthropic
- 04Google's common crawlers: Google-Extended · Google for Developers
- 05About Applebot (including Applebot-Extended) · Apple
- 06Perplexity crawlers · Perplexity
- 07CCBot · Common Crawl
- 08Common Crawl FAQ (Crawl-delay support) · Common Crawl
- 09Meta web crawlers · Meta for Developers
- 10Bytespider (third-party agent directory) · Known Agents
- 11Directive (EU) 2019/790 on copyright in the Digital Single Market, Article 4 · EUR-Lex
- 12Code of Practice for General-Purpose AI Models: Copyright Chapter · European Commission
- 13Module ngx_http_map_module · nginx.org
- 14Module ngx_http_geo_module · nginx.org
- 15Module ngx_http_realip_module · nginx.org
Last updated