infra / nginx
Rate limiting
Explication
Ce que vous allez apprendre
- Déclarer une zone de limitation de débit avec
limit_req_zone - Distinguer
limit_req(débit de requêtes) delimit_conn(nombre de connexions simultanées) - Utiliser
burstetnodelaypour absorber des pics de trafic légitimes sans bloquer l'utilisateur - Renvoyer un code HTTP sémantiquement correct (429) quand une limite est atteinte
- Exempter des IP internes de confiance du rate limiting via une
map/geo
Dans quel contexte ?
Un endpoint /login d'une application web est ciblé par un script qui tente des centaines de mots de passe par seconde (attaque par force brute). Sans protection, Nginx transmet chaque tentative à l'application, qui doit vérifier chaque mot de passe en base de données — un gaspillage de ressources et un risque de sécurité réel si aucune limite n'existe.
D'abord, la brique de base : compter les requêtes par clé
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; déclare une zone mémoire partagée qui compte les requêtes par adresse IP client, et fixe un taux moyen autorisé (ici 10 requêtes par seconde). Cette déclaration se fait dans le contexte http {}, jamais dans un location, car la mémoire partagée doit être accessible à tous les workers Nginx.
Une fois la zone déclarée, il faut l'appliquer à un endroit précis
limit_req zone=api_limit burst=20 nodelay; dans un location applique réellement la limite. Sans burst, une seule requête légèrement au-dessus du taux moyen serait immédiatement rejetée — trop strict pour un usage réel, où une page peut charger 15 assets en une fraction de seconde. burst autorise une rafale ponctuelle au-delà du taux moyen, absorbant ces pics légitimes.
Il reste une nuance importante sur nodelay
Sans nodelay, Nginx met en file d'attente les requêtes en excès du taux moyen (jusqu'à la limite du burst), ce qui ajoute artificiellement de la latence. Avec nodelay, ces requêtes en rafale sont traitées immédiatement tant que le burst n'est pas dépassé — un choix pertinent pour une API où la réactivité prime, mais volontairement omis sur un endpoint de login où ralentir un attaquant est justement l'objectif recherché.
| Directive | Ce qu'elle limite | Cas d'usage |
|---|---|---|
limit_req | Débit de requêtes par seconde | Protection générale d'API, anti brute-force sur login |
limit_conn | Nombre de connexions simultanées | Téléchargements lourds, éviter qu'un seul client sature une ressource |
Ensuite, une seconde forme de limitation, complémentaire et distincte
limit_conn ne compte pas des requêtes mais des connexions ouvertes simultanément : utile pour un endpoint de téléchargement de fichiers, où l'on veut éviter qu'un même client ouvre 50 connexions parallèles vers le même serveur, indépendamment du débit de requêtes.
Prérequis
Cette leçon suppose que tu es à l'aise avec le contexte http {} et les blocs location (leçons 2 et 3) : les zones de limitation se déclarent globalement mais s'appliquent localement.
Piège fréquent
Déclarer une zone de rate limiting dans un location plutôt que dans http {} est une erreur de configuration qui empêche la mémoire partagée de fonctionner correctement entre workers. Toutes les déclarations limit_req_zone/limit_conn_zone doivent vivre au niveau global, seule leur application (limit_req, limit_conn) se fait dans un location.
Bonne pratique
Renvoie explicitement un code 429 (limit_req_status 429;) plutôt que de laisser le 503 par défaut : 429 ("Too Many Requests") est sémantiquement plus correct et permet au client (ou à un outil de monitoring) de distinguer une limitation de débit d'une vraie panne serveur.
Maintenant que le trafic abusif est contenu, la prochaine leçon complète la posture de sécurité avec les headers HTTP qui protègent contre le clickjacking, le sniffing MIME et les injections de scripts.
Commandes & code
Rate limiting
# limit_req_zone se déclare dans le contexte http {}, en dehors de tout server
http {
# $binary_remote_addr : clé = IP client (format binaire, plus compact que $remote_addr en mémoire)
# 10m : taille de la zone mémoire partagée (~160 000 IP suivies avec 10 Mo)
# rate=10r/s : 10 requêtes par seconde autorisées EN MOYENNE pour cette clé
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;
}
server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
# burst=20 : autorise des pics jusqu'à 20 requêtes en rafale au-delà du taux moyen
# nodelay : traite le burst immédiatement (sinon Nginx les mettrait en file d'attente, ajoutant de la latence)
proxy_pass http://127.0.0.1:8000;
}
location /login {
limit_req zone=login_limit burst=3; # sans nodelay ici : ralentit volontairement le brute-force
proxy_pass http://127.0.0.1:8000;
}
}# limit_conn : limite le nombre de CONNEXIONS simultanées (pas le débit de requêtes)
http {
limit_conn_zone $binary_remote_addr zone=addr_conn:10m;
}
server {
location /downloads/ {
limit_conn addr_conn 3; # max 3 connexions simultanées par IP sur ce endpoint
limit_rate 500k; # bride aussi la bande passante par connexion (500 Ko/s)
}
}# Réponse personnalisée quand la limite est atteinte (par défaut : 503, on peut changer)
server {
limit_req_status 429; # renvoie 429 Too Many Requests (sémantiquement correct)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
error_page 429 /rate_limited.json;
location = /rate_limited.json {
internal; # accessible uniquement en interne, pas via URL directe
default_type application/json;
return 429 '{"error": "rate_limited", "retry_after": 1}';
}
}# Whitelister des IP internes (monitoring, CI) pour ne pas les throttle
geo $limit_key {
default $binary_remote_addr;
10.0.0.0/8 ""; # clé vide = pas de comptage pour ce range interne
}
http {
limit_req_zone $limit_key zone=api_limit:10m rate=10r/s;
}Résumé
limit_reqlimite un DÉBIT (requêtes/seconde) ;limit_connlimite un NOMBRE de connexions simultanées : deux protections complémentaires.burst+nodelaygère les pics légitimes sans bloquer un utilisateur qui charge une page avec 15 assets d'un coup.- Toujours définir les zones dans
http {}(mémoire partagée entre workers), jamais dans unlocation. limit_req_status 429est plus correct sémantiquement que le 503 par défaut.
Exercices pratiques
Mission : bloquer un brute-force sur /login sans pénaliser le monitoring interne
Objectif : Déclarer une zone de rate limiting adaptée à un endpoint sensible et exempter une plage IP interne de confiance.
Contexte
Un script tente des centaines de mots de passe par seconde sur /login. L'équipe veut freiner fortement ces tentatives (contrairement à /api/ où la réactivité prime), tout en laissant les checks de monitoring internes (plage 10.0.0.0/8) passer sans aucune limite.