IABIG

Guides par plateforme · Mis à jour le 2026-10-25 · Version Markdown

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

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

À lire aussi