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), directiveslimit_req/limit_conn,burst,nodelay, statut 503 vs 429 (limit_req_status), protéger/loginplus strictement que/, exclure un healthcheck, tester avecab/curl/hey, lire les logslimit_req, et articuler le complément avec fail2ban et nftables — sans 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
- Hub Linux Réseau & Sécurité
- TLS certbot utile (même stack nginx ; ce lab reste en HTTP local)
- fail2ban avancé et nftables : comprendre le complément couche 3/4 vs applicatif
- Basics réseau ip/ss/curl ; SSH hardening pour le contexte série
- VM jetable Ubuntu 22.04/24.04,
sudo, snapshot ; placeholderslab.example.com— pas de secrets
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
man nginx(sections limit_req / limit_conn) ; doc officielle ngx_http_limit_req_module et ngx_http_limit_conn_module- Variantes avancées hors cœur :
limit_req_dry_run, zones par$server_name, mapping geo pour allowlist IP bureau - Suite série : Quiz & FAQ sécurité Linux · hub Linux Réseau & Sécurité
- TLS devant le même nginx : certbot · SSH : hardening · diagnostics : ip/ss/curl
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.