Robots d'IA avec nginx - robots.txt, blocage par User-Agent, plages IP officielles et débit
Configuration nginx testée pour refuser les robots d'entraînement, laisser passer les vrais robots de recherche IA, limiter leur débit et les journaliser.
Avec nginx, tout se règle dans la configuration du serveur : on peut servir robots.txt, refuser une requête selon son User-Agent, vérifier qu'un robot vient bien des adresses de son éditeur, limiter son débit et l'inscrire dans un journal séparé. Ce guide assemble ces briques en une configuration complète, organisée autour d'une politique simple : refuser l'entraînement, laisser passer les robots de recherche de ChatGPT, Claude et Perplexity, et renvoyer une erreur 403 à ceux qui empruntent leur nom sans venir de leurs plages IP. Nous avons validé cette configuration avec nginx -t puis par des requêtes réelles sur nginx 1.30.6, en version officielle pour Windows ; les chemins Linux indiqués (/etc/nginx, /var/log/nginx) sont ceux des paquets Debian et Ubuntu et sont à adapter à votre distribution.
robots.txt d'abord
Le blocage côté serveur ne remplace pas robots.txt : c'est le fichier que lisent les robots qui respectent vos choix, et le seul moyen d'exprimer un refus d'entraînement auprès de Google (Google-Extended) ou d'Apple (Applebot-Extended), qui n'envoient aucune requête sous ces noms. Placez le fichier à la racine du site et assurez-vous qu'il réponde toujours en 200, quel que soit le robot :
location = /robots.txt {
# Servi à tous, y compris aux robots refusés ailleurs
try_files $uri =404;
access_log off; # facultatif : ne pas encombrer le journal principal
}
La RFC 9309 prévoit qu'un robots.txt injoignable en erreur 5xx vaut interdiction complète, et un robots.txt refusé en 403 empêche le robot d'y lire votre refus. La configuration qui suit exclut donc /robots.txt de tous les blocages.
La configuration du bloc http
Ce fichier se place dans le contexte http, par exemple dans /etc/nginx/conf.d/, inclus par défaut sur Debian et Ubuntu. Chaque map ne coûte rien tant que sa variable n'est pas utilisée : nginx l'évalue à la demande.
# /etc/nginx/conf.d/robots-ia.conf (contexte http)
# 1. Robot d'IA annoncé par le User-Agent.
# Les expressions sont testées dans l'ordre ; ~* = insensible à la casse.
map $http_user_agent $robot_ia {
default "";
"~*GPTBot" GPTBot;
"~*OAI-SearchBot" OAI-SearchBot;
"~*ChatGPT-User" ChatGPT-User;
"~*ClaudeBot" ClaudeBot;
"~*Claude-SearchBot" Claude-SearchBot;
"~*Claude-User" Claude-User;
"~*PerplexityBot" PerplexityBot;
"~*Perplexity-User" Perplexity-User;
"~*CCBot" CCBot;
"~*meta-externalagent" meta-externalagent;
"~*Bytespider" Bytespider;
}
# 2. Robots refusés côté serveur (ici : entraînement ; à adapter)
map $robot_ia $robot_refuse {
default 0;
GPTBot 1;
ClaudeBot 1;
CCBot 1;
meta-externalagent 1;
Bytespider 1;
}
# 3. Éditeur revendiqué, pour les robots dont les plages IP sont publiées
map $robot_ia $editeur_annonce {
default "";
GPTBot openai;
OAI-SearchBot openai;
ChatGPT-User openai;
ClaudeBot anthropic;
Claude-SearchBot anthropic;
Claude-User anthropic;
PerplexityBot perplexity;
Perplexity-User perplexity;
}
# 4. Éditeur réel d'après l'adresse IP (fichier généré par le script plus bas)
geo $editeur_ip {
default none;
include /etc/nginx/robots-ia-ip.conf;
}
# 5. 1 = robot annoncé, mais IP hors des plages de son éditeur
map "$editeur_annonce:$editeur_ip" $robot_usurpe {
default 1;
"~^:" 0; # aucun robot vérifiable annoncé
"openai:openai" 0;
"anthropic:anthropic" 0;
"perplexity:perplexity" 0;
}
# 6. Décision finale. robots.txt reste toujours lisible.
map "$robot_refuse$robot_usurpe:$uri" $refus_ia {
default 0;
"~^..:/robots\.txt$" 0; # doit rester la première expression
"~^1" 1; # robot refusé par principe
"~^01" 1; # robot usurpé
}
# 7. Limitation de débit : seuls les robots d'IA ont une clé.
# Une clé vide n'est pas comptée.
map $robot_ia $cle_limite_ia {
"" "";
default $binary_remote_addr;
}
limit_req_zone $cle_limite_ia zone=robots_ia:10m rate=1r/s;
# 8. Format du journal dédié
log_format robots_ia '$remote_addr [$time_local] "$request" $status '
'robot=$robot_ia ip=$editeur_ip refus=$refus_ia "$http_user_agent"';
Quelques points de lecture. Dans un map, la documentation de nginx indique que la recherche s'arrête au premier résultat, les chaînes exactes passant avant les expressions régulières, elles-mêmes testées dans l'ordre du fichier. Le motif ~*GPTBot ne correspond pas à ChatGPT-User (qui contient « GPT-U », pas « GPTBot ») et ~*ClaudeBot ne correspond pas à Claude-SearchBot : l'ordre ci-dessus est sans ambiguïté. Le module geo accepte les préfixes IPv4 et IPv6 en notation CIDR. Enfin, limit_req_zone ignore les requêtes dont la clé est vide : les visiteurs ordinaires ne sont pas limités.
Le bloc server
server {
listen 443 ssl;
server_name www.example.com;
# ssl_certificate et ssl_certificate_key : inchangés, omis ici
root /var/www/example;
# Un access_log déclaré ici remplace ceux hérités de http :
# on redéclare le journal principal, puis le journal dédié.
access_log /var/log/nginx/access.log combined;
access_log /var/log/nginx/robots-ia.log robots_ia if=$robot_ia;
# 1 requête/s par adresse pour les robots d'IA, rafale de 5 tolérée
limit_req zone=robots_ia burst=5 nodelay;
limit_req_status 429;
# Refus (robots d'entraînement et usurpateurs), sauf pour robots.txt
if ($refus_ia) {
return 403;
}
location = /robots.txt {
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}
Les directives if et return du module rewrite sont admises dans le contexte server ; placées là, elles s'appliquent à toutes les URL, quel que soit le location choisi ensuite. Le paramètre if= de access_log n'écrit la ligne que si la variable n'est ni vide ni égale à « 0 » : le journal robots-ia.log ne contient que les requêtes d'un robot d'IA annoncé, acceptées ou refusées. Le code 429 de limit_req_status (503 par défaut) indique clairement au robot qu'il doit ralentir.
Si le site est derrière un CDN ou un répartiteur de charge, $remote_addr contient l'adresse de cet intermédiaire : configurez d'abord le module realip (set_real_ip_from, real_ip_header), sinon tous les vrais robots seront classés comme usurpateurs.
Mettre à jour les plages IP officielles
OpenAI, Anthropic et Perplexity publient leurs plages dans des fichiers JSON de même format : un tableau prefixes dont chaque élément contient ipv4Prefix ou ipv6Prefix. Le script suivant, que nous avons exécuté contre les fichiers réels le 25 octobre 2026 (311 préfixes obtenus), produit le fichier inclus par geo et ne recharge nginx que si la configuration reste valide.
#!/usr/bin/env bash
# /usr/local/sbin/maj-robots-ia-nginx.sh - dépend de curl et jq
set -euo pipefail
sortie=/etc/nginx/robots-ia-ip.conf
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
ajoute() { # $1 = éditeur, $2 = URL du fichier JSON officiel
# -L : perplexity.com redirige vers perplexity.ai
curl -fsSL --max-time 30 "$2" \
| jq -er --arg e "$1" '.prefixes[] | (.ipv4Prefix // .ipv6Prefix) | select(. != null) | "\(.) \($e);"' >> "$tmp"
}
ajoute openai https://openai.com/gptbot.json
ajoute openai https://openai.com/searchbot.json
ajoute openai https://openai.com/chatgpt-user.json
ajoute anthropic https://claude.com/crawling/bots.json
ajoute perplexity https://www.perplexity.com/perplexitybot.json
ajoute perplexity https://www.perplexity.com/perplexity-user.json
# Garde-fou : une liste anormalement courte signale un problème de téléchargement
[ "$(wc -l < "$tmp")" -ge 10 ] || { echo "liste trop courte, abandon" >&2; exit 1; }
sort -u "$tmp" > "$sortie.new"
cp -p "$sortie" "$sortie.bak" 2>/dev/null || true
mv "$sortie.new" "$sortie"
if ! nginx -t; then
echo "configuration refusée, retour à l'ancienne liste" >&2
[ -f "$sortie.bak" ] && mv "$sortie.bak" "$sortie"
exit 1
fi
systemctl reload nginx
# /etc/cron.d/maj-robots-ia : tous les jours à 4 h 17
17 4 * * * root /usr/local/sbin/maj-robots-ia-nginx.sh
Avec set -e, un téléchargement en échec arrête le script et l'ancienne liste reste en place. Avant le premier lancement, créez un fichier vide (touch /etc/nginx/robots-ia-ip.conf) pour que nginx -t réussisse. Pour vérifier à la main une adresse relevée dans vos journaux, le vérificateur d'IP de robots IABIG interroge les mêmes listes.
X-Robots-Tag, si besoin
location ~* \.pdf$ {
add_header X-Robots-Tag "noindex" always;
}
Google documente cet en-tête ; les pages d'OpenAI, d'Anthropic et de Perplexity consultées pour ce guide ne le mentionnent pas. Attention à l'héritage : les directives add_header d'un niveau ne sont héritées que si le niveau courant n'en déclare aucune. Un add_header dans ce location fait disparaître ceux du bloc server.
Tester
nginx -t && systemctl reload nginx
# Navigateur : 200
curl -s -o /dev/null -w '%{http_code}\n' https://www.example.com/
# Robot d'entraînement : 403, mais 200 sur robots.txt
curl -s -o /dev/null -w '%{http_code}\n' -A "compatible; GPTBot/1.4" https://www.example.com/
curl -s -o /dev/null -w '%{http_code}\n' -A "compatible; GPTBot/1.4" https://www.example.com/robots.txt
# Robot de recherche annoncé depuis votre poste : 403 (IP hors des plages)
curl -s -o /dev/null -w '%{http_code}\n' -A "compatible; OAI-SearchBot/1.4" https://www.example.com/
tail -f /var/log/nginx/robots-ia.log
Ces commandes sont indicatives : curl reproduit le User-Agent, pas l'adresse du robot. Le dernier test le montre bien : depuis votre poste, un faux OAI-SearchBot est refusé, ce qui est le comportement attendu. Lors de nos essais, en déclarant l'adresse locale comme plage d'OpenAI, OAI-SearchBot obtenait 200, puis 429 au-delà de la rafale ; GPTBot recevait 403 sur / et 200 sur /robots.txt. Le vrai contrôle se fait ensuite dans robots-ia.log, où la colonne ip= indique l'éditeur reconnu pour chaque requête.
Pièges à éviter
- Le User-Agent seul ne prouve rien. Le refus par nom arrête les robots honnêtes, que robots.txt arrêtait déjà ; les usurpateurs ne sont repérés que par la comparaison avec les plages IP, et seulement pour les éditeurs qui en publient.
- Une liste périmée bloque de vrais robots. Les plages changent ; surveillez la date de génération des fichiers et les refus
refus=1portant sur des robots de recherche. - Une expression trop large refuse tout le monde.
~*botattraperait Googlebot et Bingbot. - Bloquer les plages d'un éditeur pour refuser l'entraînement. Anthropic indique que cette méthode n'est pas fiable, car elle empêche aussi la lecture de robots.txt.
Revenir en arrière
Commentez la ligne if ($refus_ia) { return 403; } et la directive limit_req, puis nginx -t et systemctl reload nginx : le trafic n'est plus filtré mais reste journalisé. Pour tout retirer, supprimez /etc/nginx/conf.d/robots-ia.conf, les lignes ajoutées au bloc server et la tâche cron.
Sources officielles
- nginx, module map : https://nginx.org/en/docs/http/ngx_http_map_module.html
- nginx, module geo : https://nginx.org/en/docs/http/ngx_http_geo_module.html
- nginx, module limit_req : https://nginx.org/en/docs/http/ngx_http_limit_req_module.html
- nginx, module log (access_log, log_format) : https://nginx.org/en/docs/http/ngx_http_log_module.html
- nginx, module headers (add_header) : https://nginx.org/en/docs/http/ngx_http_headers_module.html
- nginx, module realip : https://nginx.org/en/docs/http/ngx_http_realip_module.html
- RFC 9309, Robots Exclusion Protocol : https://www.rfc-editor.org/rfc/rfc9309
- OpenAI, Overview of OpenAI Crawlers : https://developers.openai.com/api/docs/bots
- Anthropic, Does Anthropic crawl data from the web : https://support.claude.com/en/articles/8896518
- Perplexity, Perplexity Crawlers : https://docs.perplexity.ai/docs/resources/perplexity-crawlers
- Google, Robots meta tag et X-Robots-Tag : https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag
À lire aussi
- Robots d'IA sur Netlify - robots.txt, fichier _headers et Edge Function de filtragePublier un robots.txt sur Netlify, ajouter X-Robots-Tag avec _headers et filtrer les robots d'IA par User-Agent avec une Edge Function ou l'extension dédiée.
- Robots d'IA sur IIS - robots.txt, URL Rewrite, Request Filtering et restrictions IPSur IIS, publier robots.txt, refuser les robots d'entraînement avec une règle URL Rewrite dans web.config et voir les limites des filtres et restrictions IP.
- Robots d'IA avec Next.js et Vercel - robots.ts, X-Robots-Tag et pare-feu VercelConfigurer robots.txt avec app/robots.ts dans Next.js, poser un en-tête X-Robots-Tag et régler le pare-feu Vercel sans bloquer ChatGPT Search ni Perplexity.
- Robots d'IA avec Apache 2.4 - robots.txt, .htaccess, Require not env et plages IPRefuser les robots d'entraînement avec Apache 2.4 via .htaccess, mod_setenvif ou mod_rewrite, et n'accepter les robots de recherche que depuis leurs plages IP.