Retour au cours

infra / nginx

Rate limiting

Leçon 91 exercice

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) de limit_conn (nombre de connexions simultanées)
  • Utiliser burst et nodelay pour 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é.

DirectiveCe qu'elle limiteCas d'usage
limit_reqDébit de requêtes par secondeProtection générale d'API, anti brute-force sur login
limit_connNombre de connexions simultanéesTé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

nginx
# 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;
    }
}
nginx
# 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)
    }
}
nginx
# 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}';
    }
}
nginx
# 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_req limite un DÉBIT (requêtes/seconde) ; limit_conn limite un NOMBRE de connexions simultanées : deux protections complémentaires.
  • burst + nodelay gè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 un location.
  • limit_req_status 429 est plus correct sémantiquement que le 503 par défaut.

Exercices pratiques

1 disponible
1

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.

Résoudre l’exercice →