Document

SUBSCRIBE TO GET FULL ACCESS TO THE E-BOOKS FOR FREE 🎁SUBSCRIBE NOW

Professional Dropdown with Icon

SUBSCRIBE NOW TO GET FREE ACCESS TO EBOOKS

Nginx rate limit : limit_req, burst et reverse proxy sécurisé

À la fin de ce tutoriel nginx rate limit, vous saurez poser un pare-feu applicatif léger devant un site ou un reverse proxy : zones limit_req_zone / limit_conn_zone (clé $binary_remote_addr), directives limit_req / limit_conn, burst, nodelay, statut 503 vs 429 (limit_req_status), protéger /login plus strictement que /, exclure un healthcheck, tester avec ab/curl/hey, lire les logs limit_req, et articuler le complément avec fail2ban et nftablessans les remplacer.

Niveau : Intermédiaire · Temps estimé : 45–55 min · Versions testées : Ubuntu 22.04 / 24.04 (nginx 1.18+), Debian 12 ; notes Rocky/Alma 9 · Dernière vérification : 2026-09-11

Slug proposé : linux-nginx-rate-limit · Série : Linux Réseau & Sécurité · Remplace / fusionne : N/A — création (chapitre « Pare-feu applicatif léger : nginx rate-limit », plan #7/#9)

Prérequis

Coût : 0 €. Risque : trop strict = faux positifs (bots légitimes, CI, healthchecks). Lab uniquement ; rollback drop-in en une commande. Si vous enchaînez après TLS certbot, réutilisez le même nginx : ajoutez les zones en HTTP d’abord, puis rejouez sous HTTPS staging.

Ce que nous allons construire

Client → nginx (limit_req + limit_conn)
  → /           : rate souple (pages)
  → /api/       : endpoint simulé + quota
  → /login      : quota strict (anti brute-force)
  → /healthz    : exclus (whitelist location)
  → error.log   : limit_req exceeding
  (+ mention jail fail2ban nginx, sans redo)

(Schéma à remplacer par une image locale Excalidraw / draw.io, alt : « Nginx rate limit : limit_req_zone, burst, nodelay, limit_conn, locations /login et /healthz ».)

Chapitre pare-feu applicatif léger : nft coupe les paquets, fail2ban bannit après échecs répétés, nginx rate-limit lisse le débit HTTP avant que l’appli ne souffre. Remplacez lab.example.com par votre FQDN lab si besoin.

Snapshot VM avant. Aucun secret dans les confs. Ce n’est pas un WAF complet (ModSecurity, cloud WAF) — c’est la couche native nginx, dense et gratuite.

En production, nginx est souvent le premier point HTTP public. Sans quota, scrape, bot login ou retry peut saturer workers et upstream avant nft/fail2ban. Le nginx rate limit agit dans la requête (RAM faible, conf déclarative, 429/503). Objectif : placer les zones, calibrer rate/burst, laisser un healthcheck libre.

Étape 1 — Concepts : rate limit applicatif vs firewall / fail2ban

Couche Outil Ce qu’elle filtre
Réseau (L3/L4) nftables Ports, IP, connexions brutes
Brute-force logs fail2ban Ban après motifs (auth fail, etc.)
HTTP (L7) nginx limit_req / limit_conn Requêtes/s et connexions simultanées par clé

Le nginx rate limit n’ouvre pas de ports et ne remplace pas un ban IP durable : il renvoie 503 (défaut) ou 429 tant que le quota est dépassé. fail2ban peut ensuite lire error.log (jail nginx) pour bannir les abusifs — on le mentionne, on ne refait pas le tuto jails.

Pensez complément : nftables réduit la surface, fail2ban coupe les IP aux échecs d’auth répétés, nginx protège le budget HTTP par client. Seul fail2ban laisse passer un flood de GET valides ; seul nft coupe trop large ; seul limit_req rate un attaquant lent — d’où le maillage série.

Directive Rôle
limit_req_zone Zone mémoire + clé + rate (ex. 10r/s)
limit_req Applique la zone (burst, nodelay)
limit_conn_zone / limit_conn Plafond de connexions simultanées
$binary_remote_addr Clé compacte = IP client
burst File d’attente courte au-delà du rate
nodelay Traite le burst immédiatement (sans délai artificiel)
limit_req_status Code HTTP (503 défaut → souvent 429 en API)

$binary_remote_addr stocke l’IP en binaire (économe vs texte). 10m dimensionne les états suivis — OK en lab. Le rate est une moyenne lissée (leaky bucket) : 5r/s autorise un pic court si burst le permet.

Étape 2 — Installer nginx et un site lab minimal

sudo apt update
sudo apt install -y nginx apache2-utils curl
# Optionnel charge : hey (si dispo) ou boucle curl
command -v hey >/dev/null || echo "hey absent : on utilisera ab + curl"

# Document root lab
sudo mkdir -p /var/www/lab-rate/{static,api}
echo 'ok lab rate-limit' | sudo tee /var/www/lab-rate/static/index.html
echo '{"status":"ok"}' | sudo tee /var/www/lab-rate/api/index.json
echo 'login form lab' | sudo tee /var/www/lab-rate/login.html

# Snapshot conf
sudo cp -a /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d)
ss -tuln | grep -E ':80|:443' || true

Rocky/Alma : sudo dnf install nginx httpd-tools ; conf souvent sous /etc/nginx/conf.d/.

Build standard Debian/Ubuntu/RHEL : modules limit_req / limit_conn inclus. Désactivez le site default pour éviter un second server qui vole le default_server.

Étape 3 — Zones limit_req / limit_conn (http {})

Les zones se déclarent dans le contexte http. Sur Ubuntu, un drop-in dans /etc/nginx/conf.d/ est inclus automatiquement.

sudo tee /etc/nginx/conf.d/00-lab-rate-zones.conf >/dev/null << 'EOF'
# Lab nginx rate limit — zones (http context)
# 10m ≈ ~160k états IP en $binary_remote_addr (ordre de grandeur)
limit_req_zone  $binary_remote_addr zone=perip:10m   rate=5r/s;
limit_req_zone  $binary_remote_addr zone=login:10m  rate=1r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

# 429 plus parlant pour APIs / reverse proxy sécurisé
limit_req_status 429;
limit_conn_status 429;
EOF
Zone Rate Usage prévu
perip 5 r/s / et /api/
login 1 r/s /login (strict)
addr limit_conn (ex. 20 connexions)

Sans limit_req_status, nginx renvoie 503. En API, 429 est plus clair ; un LB qui recycle sur 503 peut mal lire un rate-limit. Deux zones (perip vs login) évitent qu’un scrape mange le budget auth — politique par location, cœur d’un reverse proxy sécurisé léger.

Étape 4 — Server block : reverse proxy / static + quotas

sudo tee /etc/nginx/sites-available/lab-rate >/dev/null << 'EOF'
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name lab.example.com _;

    root /var/www/lab-rate/static;
    index index.html;

    # Plafond connexions simultanées par IP
    limit_conn addr 20;

    # Healthcheck : PAS de limit_req (exclure)
    location = /healthz {
        access_log off;
        return 200 "okn";
        add_header Content-Type text/plain;
    }

    location / {
        limit_req zone=perip burst=10 nodelay;
        try_files $uri $uri/ =404;
    }

    # Endpoint /api/ simulé (static JSON) — même zone, burst un peu plus bas
    location /api/ {
        limit_req zone=perip burst=5 nodelay;
        alias /var/www/lab-rate/api/;
        default_type application/json;
        try_files $uri $uri/ /api/index.json =404;
    }

    # Login : zone stricte (anti spray)
    location = /login {
        limit_req zone=login burst=3 nodelay;
        alias /var/www/lab-rate/login.html;
        default_type text/html;
    }

    # Exemple reverse-proxy (décommentez si backend lab sur :8080)
    # location /proxy/ {
    #     limit_req zone=perip burst=8 nodelay;
    #     proxy_pass http://127.0.0.1:8080/;
    #     proxy_set_header Host $host;
    #     proxy_set_header X-Real-IP $remote_addr;
    # }
}
EOF

sudo ln -sf /etc/nginx/sites-available/lab-rate /etc/nginx/sites-enabled/lab-rate
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
curl -sS -H 'Host: lab.example.com' http://127.0.0.1/healthz
curl -sS -H 'Host: lab.example.com' http://127.0.0.1/
curl -sS -H 'Host: lab.example.com' http://127.0.0.1/api/
curl -sS -o /dev/null -w '%{http_code}n' -H 'Host: lab.example.com' http://127.0.0.1/login

burst=10 nodelay : jusqu’à 10 req au-delà du rate tout de suite ; surplus → 429. Sans nodelay, nginx espace le burst (latence) — souvent pire UX qu’un 429 net.

limit_conn addr 20 complète : un client keep-alive peut saturer les workers même à bas rate. 20 est volontairement bas pour voir l’effet en lab. Le proxy_pass commenté montre le branchement backend :8080 ; l’alias /api/ suffit ici sans appli.

Étape 5 — Déclencher le rate limit (ab / curl / hey)

# Burst contrôlé sur / (attendu : mélange 200 et 429)
ab -n 80 -c 20 -H 'Host: lab.example.com' http://127.0.0.1/ || true

# Login plus strict : 429 rapide
ab -n 40 -c 10 -H 'Host: lab.example.com' http://127.0.0.1/login || true

# Boucle curl (si ab indispo)
for i in $(seq 1 30); do
  curl -sS -o /dev/null -w "%{http_code} " -H 'Host: lab.example.com' http://127.0.0.1/api/
done; echo

# hey si installé
# hey -n 100 -c 25 -H 'Host: lab.example.com' http://127.0.0.1/

# Logs limit_req
sudo grep -E 'limiting requests|limiting connections' /var/log/nginx/error.log | tail -20

Lignes limiting requests … zone "perip" (ou "login") = preuve. Un bon lab : succès puis plateau 429 — pas 100 % d’échecs dès la 1ʳᵉ req (burst/rate trop bas). curl -w '%{http_code}' doit basculer 200→429 ; ab : section Non-2xx.

Tester que /healthz reste à 200 sous charge :

ab -n 100 -c 50 -H 'Host: lab.example.com' http://127.0.0.1/healthz || true
# Doit rester majoritairement 200 (pas de limit_req sur cette location)

Étape 6 — Whitelist, fail2ban, réglages fins

Exclure une location (déjà fait pour /healthz) : pas de limit_req dedans. Pour une IP de monitoring fiable en lab, on peut aussi brancher une geo / map (hors cœur) — gardez simple : locations critiques + healthcheck libre.

fail2ban : une jail nginx-limit-req (filtre sur error.log) peut bannir les IP qui saturent durablement. Ne dupliquez pas ici le lab fail2ban jails : activez la jail après que les logs limiting requests soient visibles, avec bantime court en lab.

Réglages utiles :

# Voir zones chargées (nginx -T)
sudo nginx -T 2>/dev/null | grep -E 'limit_req|limit_conn' | head -40

# Remettre 503 si votre front attend ce code
# sudo sed -i 's/limit_req_status 429;/limit_req_status 503;/' 
#   /etc/nginx/conf.d/00-lab-rate-zones.conf
# sudo nginx -t && sudo systemctl reload nginx

Derrière LB/CDN, $binary_remote_addr = IP du proxy. Préférez set_real_ip_from + real_ip_header (CIDR de confiance) plutôt qu’un X-Forwarded-For naïf (spoofing). Piège n°1 hors lab : une seule IP LB = un seul seau pour tous.

Ops : versionnez les drop-ins ; toujours nginx -t avant reload (comme sshd -t). Smoke CI : curl /healthz + micro-burst /login.

Étape 7 — Rollback et nettoyage lab

# Désactiver le site lab
sudo rm -f /etc/nginx/sites-enabled/lab-rate
sudo rm -f /etc/nginx/conf.d/00-lab-rate-zones.conf
# Optionnel : supprimer sites-available + docroot
# sudo rm -f /etc/nginx/sites-available/lab-rate
# sudo rm -rf /var/www/lab-rate
sudo nginx -t && sudo systemctl reload nginx
# Restaurer default si besoin
# sudo ln -sf /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
echo "lab nginx rate-limit : rollback OK — snapshots conservés"

Validé si : nginx -t OK ; / et /api/ passent puis 429 sous ab ; /login 429 plus tôt ; /healthz reste OK ; error.log montre limiting requests ; vous savez retirer les drop-ins sans toucher au reste de la série.

Rejouez le rollback une fois à froid (réflexe TLS/SSH). Sur VPS partagé : hostname lab dédié et rates plus hauts pour humains — chiffres ici pédagogiques, pas baseline prod.

Erreurs fréquentes

Symptôme Cause Correction
nginx -t fail « limit_req zone … not found » Zone hors contexte http / fichier non inclus Zones dans conf.d/ ; nginx -T
Tout le monde en 429 Rate trop bas / burst=0 / IP proxy unique Monter rate/burst ; revoir clé IP
Jamais de 429 Burst trop haut ou test trop lent ab -c plus élevé ; baisser rate lab
503 au lieu de 429 Défaut nginx limit_req_status 429;
Healthcheck KO sous charge limit_req sur / trop large Location = /healthz sans quota
Logs vides Mauvais niveau / mauvais fichier error.log ; grep limiting
redo fail2ban confus Attendu jail ici Mention seule → fail2ban

Quiz (3 questions)

1. Rôle de limit_req_zone … rate=5r/s ?
– A. Ouvrir le port 80 dans nftables
– B. Créer une zone mémoire qui limite à ~5 requêtes/s par clé (ex. IP)
– C. Remplacer fail2ban

2. Effet de burst=10 nodelay sur limit_req ?
– A. Interdit toute connexion
– B. Autorise jusqu’à 10 requêtes au-delà du rate sans les espacer artificiellement ; le surplus est rejeté
– C. Active TLS

3. Pourquoi exclure /healthz du rate limit ?
– A. Pour cacher l’endpoint
– B. Pour éviter que le monitoring / LB ne soit bloqué par le quota applicatif
– C. Parce que nginx l’interdit

Réponses : 1‑B · 2‑B · 3‑B

FAQ

limit_req remplace-t-il fail2ban ou nftables ?
Non. nft filtre L3/L4, fail2ban bannit sur motifs de logs, nginx rate limit lisse le HTTP. Les trois se complètent (nftables, fail2ban).

503 ou 429 ?
Défaut nginx : 503. Pour une API / reverse proxy sécurisé, préférez limit_req_status 429; (Too Many Requests). Alignez-vous sur ce que consomme votre front / clients.

Quelle clé derrière un load balancer ?
IP vue par nginx = souvent le LB. Module real_ip + set_real_ip_from (CIDR de confiance), puis $binary_remote_addr comme clé. Évitez un X-Forwarded-For non filtré (spoof).

burst sans nodelay ?
File + espacement → latence/timeouts. Avec nodelay : burst immédiat, surplus rejeté — en général mieux pour reverse proxy / API.

Pour aller plus loin

Maillage série Linux Réseau & Sécurité

← Précédent SSH hardening : AllowUsers, sshd et 2FA
→ Suivant Quiz & FAQ sécurité Linux
Hub Linux Réseau & Sécurité
Aussi TLS certbot · fail2ban jails · nftables

Meta publication (à remplir dans Rank Math / SEO)

  • Title SEO : Nginx rate limit : limit_req, burst, reverse proxy (2026)
  • Meta description (≤ 155) : Configurez nginx rate limit (limit_req, burst, nodelay) et limit_conn en lab reverse proxy. Pare-feu applicatif Linux 2026.
  • Focus keyphrase : nginx rate limit
  • KW secondaires : limit_req, reverse proxy sécurisé, limit_conn, burst, nodelay
  • Schemas Rank Math : Article + HowTo (étapes lab) + FAQ (3 questions ci-dessus)
  • Image mise en avant : assets/web/devopelastichayway/cover-linux-nginx-rate-limit-1200x630.webp (Visuels Linux — WebP 1200×630)
  • Catégorie : Linux · Niveau : Intermédiaire
  • Slug : linux-nginx-rate-limit
  • Statut : HOLD — draft only (ne pas publier)

← Retour parcours Linux — Basics, Admin, Réseau & Sécurité, Shell & Automation.