...

NGINX Rate Limiting: effectieve bescherming tegen botverkeer en aanvallen

NGINX-snelheid Limiting stopt geautomatiseerde verzoeken, beperkt piekbelasting en beschermt login-, API- en formulier-eindpunten tegen bots en aanvallen. Ik laat je zien hoe je limieten instelt en deze toepast voor Bescherming tegen bots toepast en daaruit een robuust beveiligingsconcept voor drukbezochte websites ontwikkelt.

Centrale punten

Essentieel Dit zijn de belangrijkste punten:

  • Tariefgrenzen remmen kwaadaardige aanvallen af en beschermen backend-bronnen.
  • Burst/zonder vertraging vangen legitieme pieken op zonder gebruikers te blokkeren.
  • Zones Maak een onderscheid tussen mensen en bots met verschillende limieten.
  • Loggen levert gegevens om de grenzen iteratief te verfijnen.
  • Integratie met WAF, DDoS-bescherming en monitoring wordt het effect vergroot.

Waarom rate limiting aanvallen in een vroeg stadium stopt

Aanvallers zetten in op hoge Verzoekfrequenties, om inlogformulieren te misbruiken, API’s te overbelasten of inhoud automatisch te scrapen. Daarom beperk ik het aantal verzoeken per sleutel – meestal per IP-adres – en bepaal ik of ik ze afrem, vertraag of met een 429-statuscode beantwoord. Zo houd ik botverkeer weg van de CPU, de database en de applicatielogica en laat ik legitieme gebruikers door. Vooral gevoelige paden zoals /login, /auth, /xmlrpc.php of zoekopdrachten die veel resources vergen, profiteren hier sterk van. De bron voor deze methode is de Nginx-documentatie naar de ngx_http_limit_req_module.

Hoe de NGINX-module in de praktijk werkt

De module werkt volgens het Lekke emmer-Principe: Voor elke sleutel slaat NGINX tellerstanden op in een zone en vergelijkt deze met de toegestane frequentie. Typische sleutels zijn $binary_remote_addr voor IP-adressen, tokens voor API-sleutels of afgeleide waarden via map. Als een client de snelheid en de burst-buffer blijvend overschrijdt, wijst NGINX het verzoek af voordat het de backend bereikt. Dit bespaart rekenkracht en vermindert de latentie voor echte bezoekers. Ik stel de reactie in op 429 Too Many Requests of, naar keuze, een andere Statuscode eh.

Configuratie: stap voor stap uitgelegd

Ik begin met een zone in de http-sectie, stel een gematigde snelheid in en activeer deze gericht op gevoelige paden. Voor kortstondige pieken definieer ik een burst, eventueel met nodelay, om abrupte weigeringen te voorkomen. Vervolgens test ik dit in de staging-omgeving en analyseer ik de logbestanden voordat ik het in productie neem. Zo loop ik geen risico op onnodige blokkades voor echte gebruikers. Een beknopt voorbeeld illustreert de Syntaxis tastbaar:

# http {}
limit_req_zone $binary_remote_addr zone=req_limit_per_ip:10m rate=10r/s;

server {
  location /api/ {
    limit_req zone=req_limit_per_ip burst=20 nodelay;
    limit_req_status 429;
  }

  location /login {
    limit_req zone=req_limit_per_ip burst=5;
    limit_req_status 429;
  }
}

Botbescherming met zones en user-agent-logica

IP-gebaseerde limieten zijn zelden voldoende tegen gedistribueerde botnetten, daarom verdeel ik het verkeer in Zones: Mensen krijgen ruimere limieten, generieke crawlers strengere. Met map evalueer ik user-agents, herken ik vrijgestelde bots zoals Googlebot en stel ik voor hen eigen, streng gecontroleerde limieten in. Voor onbekende scrapers stel ik strenge limieten in op kostbare paden. Als er patronen opvallen, verhoog ik de strengheid dynamisch, totdat de Prijs weer binnen de normale waarden ligt.

Fijnafstemming: snelheid, burst, nodelay en statuscodes

De rate regelt de doorvoer per seconde, de burst maakt kortstondige buffering mogelijk en nodelay bepaalt of ik de voorkeur geef aan buffering of onmiddellijke doorlaat. Ik begin gematigd, bijvoorbeeld 10r/s met burst 20 op API's, en pas dit aan na analyse van de logbestanden. Voor inlogroutes stel ik bijvoorbeeld 1r/s in met een kleine burst om brute-force-aanvallen af te remmen. Bij overschrijdingen retourneer ik een 429-statuscode, omdat clients daarmee het hoofd boven water houden en de retry-logica goed werkt. In bijzondere gevallen gebruik ik alternatieve codes als klanten daar om vragen.

Overzicht in de tabel: richtlijnen en toepassing

De volgende Tabel vat de belangrijkste richtlijnen samen en laat zien wanneer deze zinvol zijn.

richtlijn Effect Voorbeeld Typisch gebruik
limit_req_zone Geef de sleutel, zone en Prijs vast limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s; Basis per IP, token of user-agent
limit_req Activeert de limiet in Locatie/Server limit_req zone=perip burst=20 nodelay; Fijnafstemming per pad of vHost
limit_req_status Voegt HTTP-code toe overschrijding limit_req_status 429; Correcte client-werking en herhalingspogingen
kaart Leidt verzoeken door naar Zones op map $http_user_agent $is_bot {…} Onderscheid tussen bot en mens op basis van user-agent

Praktijk: het inlogpunt gericht beveiligen

Ik pas zeer strenge beperkingen toe op /login, omdat bots wachtwoorden met een hoge Frequentie uitproberen. 1 aanvraag per seconde met burst 3 voorkomt massaal gissen, zonder echte gebruikers te hard te treffen. Daarnaast registreer ik herhaalde mislukkingen in het logboek om IP-adressen tijdelijk te blokkeren. In combinatie met 2FA en optioneel Captcha neemt de druk op de database en de sessiebeheer aanzienlijk af. Zo houd ik het aantal mislukte pogingen laag en zorg ik ervoor dat de Toegang stabiel en klaar.

Praktijk: API’s op een eerlijke en gecontroleerde manier beschikbaar stellen

API's hebben duidelijke Kansen, zodat individuele clients niet de volledige bandbreedte in beslag nemen. Voor algemene routes stel ik 10r/s en burst 20 in, voor kostbare eindpunten strengere waarden. Als er tokens of API-sleutels beschikbaar zijn, beperk ik per token in plaats van per IP-adres. Dat zorgt voor eerlijkheid tussen klanten en voorkomt misbruik. Een uitgebreidere uitleg vind je in mijn opmerking over API-snelheidsbeperking, waarin het concept in een bredere context wordt geplaatst.

Monitoring, logboekregistratie en iteratieve verfijning

Ik log 429-antwoorden, inclusief Sleutel (bijv. IP of token) en het pad, om patronen te herkennen. Pieken op een klein aantal paden duiden op scraping of brute-force-aanvallen; verspreide druk wijst op botnetten. Met deze gegevens stel ik alleen limieten in waar dat nodig is en minimaliseer ik valse positieven. Dashboards met snelheden, foutpercentages en latentie laten me het effect van elke wijziging zien. Zo blijft de Prestaties hoog, terwijl de bescherming toeneemt.

Integratie in een holistisch beveiligingsconcept

Ik beschouw rate limiting als een sterke eerste stap laag, maar ik combineer dit met WAF-regels, IP-reputatie en TLS-beveiliging. Tegen volumineuze aanvallen helpt een DDoS-bescherming stroomopwaarts, die het verkeer op netwerkniveau filtert voordat NGINX in actie hoeft te komen. Ik meet continu statistieken, stel waarschuwingen in voor ongebruikelijke pieken en reageer met regelupdates. Zo ontstaat uit verschillende bouwstenen een robuust beschermingsnetwerk. Deze bieden een praktijkgericht overzicht DDoS-strategieën.

Concrete configuratiepatronen voor bots versus mensen

Ik verdeel bezoekers met map in categorieën en leid ze naar aparte Zones. Bekende crawlers krijgen gematigde limieten, generieke agents strengere. Voor paden zoals /search of /report hanteer ik strengere regels, omdat deze veel CPU-capaciteit in beslag nemen. Bij herhaalde overtredingen verhoog ik de limieten niet, maar blokkeer ik de toegang tijdelijk of verplaats ik de controle naar een botdetectiemodule. Zo blijft de Percentage misbruik laag, zonder zoekmachines te verstoren.

Voorbeeld: twee zones en user-agent-toewijzing

Het volgende fragment laat de indeling zien op basis van Gebruiker agent en het toekennen van passende limieten. Ik combineer dit met gedifferentieerde statuscodes en logboekvelden om het effect nauwkeurig te meten. Bots met een generieke agent komen in de strenge zone terecht. Mensen of geverifieerde crawlers maken gebruik van de soepelere zone. Deze aanpak levert voorspelbare Doorvoer per klas:

map $http_user_agent $is_bot {
  default 0;
  "~*googlebot"     0;
  "~*bingbot" 0;
  "~*crawler|scraper|bot" 1;
}

limit_req_zone $binary_remote_addr zone=human:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=bot:10m   rate=1r/s;

server {
  location / {
    if ($is_bot) {
 limit_req zone=bot burst=5;
    }
    if ($is_bot = 0) {
 limit_req zone=human burst=20 nodelay;
    }
    limit_req_status 429;
  }
}

Foutafhandeling: 429 correct communiceren

Bij Limits geef ik een duidelijk Antwoord met een indicatie wanneer het zinvol is om het opnieuw te proberen. Voor API’s hoort de geldige Retry-After-header hierbij, zodat clients ‘backoff’ toepassen. Menselijke gebruikers krijgen een korte uitleg zonder technische details. Dit vermindert het aantal tickets en zorgt voor begrijpelijk gedrag. Een overzichtelijke UX maakt beperkingen acceptabel en voorkomt frustratie.

Host, netwerk en kernel: de basis versterken

Veel legitiem verkeer en beveiligingsmaatregelen vereisen betrouwbare Bronnen en zinvolle standaardinstellingen op netwerkniveau. Ik let op de nieuwste NGINX-versies, voldoende RAM voor zones en beveiligingsfuncties tegen transportaanvallen. Tegen SYN-floods helpt het om TCP SYN-cookies in de kernel, zodat verbindingen niet vastlopen. Al met al ontlast dit NGINX van onnodige belasting. Zo richt ik de limieten op de HTTP-lagen en houd ik de Doorvoer stabiel.

Kort samengevat: zo pas ik NGINX-rate limiting effectief toe

Ik beperk het aantal aanvragen per toets, scherm kritieke paden af en houd bots op afstand met strenge zones. Burst en nodelay helpen om legitieme pieken toe te laten zonder misbruik in de hand te werken. Aan de hand van 429-logs kalibreer ik de waarden voortdurend en verscherp ik de limieten alleen waar dat nodig is. In combinatie met WAF, DDoS-bescherming, monitoring en kernelhardening ontstaat een robuust beveiligingsconcept. Wie dit consequent implementeert, vermindert het botverkeer aanzienlijk en behoudt Prestaties zelfs onder belasting.

Bouwstenen die in de praktijk vaak ontbreken

In veel opstellingen ontbreken enkele cruciale componenten die het effect van rate limiting merkbaar vergroten:

  • Echte client-IP-adressen achter proxyservers: Zonder correcte afhandeling van het echte IP-adres beperkt NGINX vaak het IP-adres van de load balancer – de limieten gelden dan voor alle gebruikers die daarachter zijn gebundeld.
  • Proefdraaien (dry-run): Limieten worden „blind“ geactiveerd. Het is beter om van tevoren alleen bij te houden hoe vaak een limiet zou zijn geactiveerd.
  • Sleutels met fijne korrelgrootte: In plaats van alleen op IP-adres te beperken, is het de moeite waard om per API-token, sessie of gebruiker limieten in te stellen om de eerlijkheid te vergroten.
  • Samenwerking met limit_conn: Parallelle verbindingen en verzoekfrequenties dekken verschillende misbruikpatronen af.
  • Gerichte uitzonderingen: Health-checks, webhooks of interne diensten hebben vaak minder strenge limieten nodig of helemaal geen limieten.

Reverse proxy: het echte IP-adres van de client veilig analyseren

Als NGINX achter een load balancer staat, stel ik de Real-IP-richtlijnen in, zodat $binary_remote_addr de echte client weergeeft. Ik vertrouw alleen netwerken die van mij zijn en schakel recursieve evaluatie in:

http {
  # Betrouwbare proxy-IP-bereiken (voorbeeld)
  set_real_ip_from 10.0.0.0/8;
  set_real_ip_from 192.168.0.0/16;
  # eventueel openbare LB-/CDN-bereiken toevoegen

  real_ip_header X-Forwarded-For;
  real_ip_recursive on;

  limit_req_zone $binary_remote_addr zone=perip:20m rate=10r/s;
}

Zonder deze instelling treft een limiet anders onschuldig veel gebruikers tegelijk. Na de installatie controleer ik aan de hand van de toegangslogs of het verwachte IP-adres van de client verschijnt.

Kernstrategie: IP, gebruikers, tokens en pad

De gekozen sleutel is bepalend voor de eerlijkheid en de effectiviteit. Enkele beproefde voorbeelden:

  • Pro IP ($binary_remote_addr): Snel inzetbaar, geschikt voor /login en anonieme eindpunten.
  • Per API-token: Eerlijkheid tussen klanten; biedt bescherming tegen NAT-bundeling. Ik haal tokens op via map.
  • Per padklasse: Stel voor dure eindpunten afzonderlijke limieten in, bijvoorbeeld /search strenger dan /status.
map $http_authorization $api_token {
  default "";
  "~*^Bearer\s+(.+)$" $1;
}

limit_req_zone $api_token zone=per_token:30m rate=5r/s;

server {
  location /api/ {
    # Alleen van belang als er een token aanwezig is
    limit_req zone=per_token burst=10;
    limit_req_status 429;
  }
}

Belangrijk: een hoge sleutelcardinaliteit neemt veel geheugen in de zone in beslag. Zorg voor voldoende bufferruimte en houd het geheugengebruik in de gaten.

Opslag en dimensionering van de zones

De zone slaat per actieve sleutel metadata op. Het verbruik bedraagt per vermelding enkele tientallen bytes plus overhead. Daaruit leid ik af:

  • Voor veel gelijktijdige IP-adressen/tokens kies ik grotere zones, bijvoorbeeld 50–100 MB.
  • Ik begin vrij ruim en bekijk de NGINX-logs: „shared memory zone is full“ duidt erop dat er opnieuw moet worden aangepast.
  • Ongebruikte sleutels vervallen na een korte periode van inactiviteit; pieken zijn belangrijker dan het daggemiddelde.

Burst en nodelay nauwkeurig toepassen

Zonder nodelay NGINX rangschikt overschrijdingen binnen de burst-buffer en vertraagd Verzoeken. Met nodelay Toegestane burst-verzoeken worden onmiddellijk doorgelaten, overtollige verzoeken worden afgewezen. Mijn aanpak:

  • Interactieve routes (HTML): liever zonder `nodelay`, om korte wachttijden te creëren in plaats van een harde 429.
  • API's: vaak met `nodelay`, zodat clients duidelijk een 429-status krijgen en een backoff toepassen.
  • Dure eindpunten: een korte burst om pieken in de backend af te vlakken.

Proefdraaien, logniveau en evaluatie

Voordat ik limieten activeer, schakel ik ‘Dry-Run’ in en pas ik het logniveau aan. Zo zie ik het effect zonder risico:

server {
  location /api/ {
    limit_req zone=perip burst=20;
    limit_req_dry_run on; # alleen loggen, niet blokkeren
    limit_req_log_level notice;  # minder streng dan 'error'
  }
}

Vervolgens analyseer ik de toegangsgegevens van de afgelopen 3–7 dagen, breng ik hotspots in kaart, pas ik de rate/burst aan en schakel ik pas daarna de dry-run uit.

429 op de juiste manier doorgeven: HTML, JSON en Retry-After

Voor een goede gebruikerservaring maak ik onderscheid tussen browsers en API-clients en gebruik ik Retry-After. Zo breng ik grenzen duidelijk over:

map $http_accept $wants_json {
  default 0;
  "~*application/json|/json"    1;
}

server {
  error_page 429 = @rate_limited;

  location @rate_limited {
    add_header Retry-After 2 always;
    if ($wants_json) {
 add_header Content-Type application/json;
      return 429 '{"error":"too_many_requests","retry_after":2}';
    }
    return 429 "Probeer het later nog eens.";
  }
}

API's kunnen op deze manier programmatisch reageren; gebruikers krijgen een duidelijke melding te zien.

limit_req en limit_conn combineren

limit_req gerichte doorvoer per tijdsvak, limit_conn beperkt het aantal gelijktijdige verbindingen. Tegen downloads, chatty-clients of HTTP/2-overbelasting gebruik ik een combinatie van beide:

limit_conn_zone $binary_remote_addr zone=perip_conn:10m;

server {
  location /api/ {
    limit_req  zone=perip burst=20 nodelay;
    limit_conn zone=perip_conn 20;  # max. 20 gelijktijdige verbindingen per IP
  }
}

Zo voorkom ik dat een klein aantal clients zich weliswaar aan de limiet houdt, maar door te veel gelijktijdige verbindingen te veel resources in beslag neemt.

Uitzonderingen, gezondheidscontroles en interne routes

Niet elk pad heeft limieten nodig. Health-checks (/healthz), interne webhooks of betalingscallbacks krijgen hun eigen locaties zonder `limit_req` – of met lagere waarden:

server {
  # geen limieten voor health-checks
  location = /healthz { return 200 "ok"; }

  # zachte limieten voor betalings-callbacks
  location /webhooks/pay/ {
    limit_req zone=perip burst=5;
  }

  # strikte beveiliging voor inloggen
  location = /login {
    limit_req zone=perip rate=1r/s burst=3;
  }
}

Gedetailleerde uitzonderingen verminderen het aantal valse positieven en zorgen voor stabiele integraties.

Robuustere zonale routing zonder 'if'-trucs

Voor de scheiding tussen „bot vs. mens“ geef ik de voorkeur aan interne omleidingen via named locations. Dat maakt de configuratie duidelijk en voorspelbaar:

map $http_user_agent $is_bot {
  default 0;
  "~*googlebot|bingbot" 0;
  "~*crawler|scraper|bot" 1;
}

limit_req_zone $binary_remote_addr zone=human:20m rate=10r/s;
limit_req_zone $binary_remote_addr zone=bot:10m   rate=1r/s;

server {
  error_page 418 = @bot;

  location / {
    if ($is_bot) { return 418; }   # interne omleiding
    limit_req zone=human burst=20 nodelay;
    limit_req_status 429;
    try_files $uri $uri/ /index.html;
  }

  location @bot {
    limit_req zone=bot burst=5;
    limit_req_status 429;
  }
}

Zo komen bots op deterministische wijze in de strenge zone terecht, en mensen in de ontspannen zone – zonder dat beide grenzen tegelijkertijd van kracht zijn.

Testen, meten, aantrekken: een pragmatische werkwijze

  • Staging: Kies een conservatieve instelling voor Rate/Burst, activeer Dry-Run en test de synthetische belasting tegen het hot-pad.
  • Rooktesten: Gebruik curl of Lasttools om korte bursts te genereren en het 429/Delay-gedrag te controleren.
  • Productiviteitsproefproject: Pas het eerst toe op afzonderlijke locaties en houd de logbestanden nauwlettend in de gaten.
  • Iteratief scherpen: Pas alleen daar strengere limieten toe waar patronen opvallen; minimaliseer valse alarmen.
# Voorbeeld: snelle burst-test met curl
for i in {1..50}; do curl -s -o /dev/null -w "%{http_code}\n" https://example.com/login & done; wait

Minuten- in plaats van seconden-tarieven en gedetailleerde routes

NGINX ondersteunt snelheden in seconden of minuten (r/s, r/m). Bij misbruik van de inlogfunctie stel ik vaak 60r/m in plaats van 1r/s in, om korte, legitieme dubbelklikken toe te staan, maar continu klikken te beperken. Dure paden krijgen strengere limieten dan goedkope. Voorbeeld:

limit_req_zone $binary_remote_addr zone=perip_min:20m rate=60r/m;

server {
  location /search/ {
    limit_req zone=perip_min burst=10;   # strenger
  }
  location /status {
    # geen limiet – goedkoop en voor intern gebruik
    return 200;
  }
}

Valkuilen en hoe ik ze vermijd

  • Verkeerde sleutel: Achter proxyservers zonder echt IP-adres leg ik per ongeluk aan alle gebruikers tegelijk een beperking op.
  • Te kleine zones: „zone is full“ leidt tot onvoorspelbaar gedrag – zorg voor ruime afmetingen.
  • Een limiet voor alles: Verschillende trajecten vereisen verschillende waarden; een uniforme aanpak leidt tot frustratie.
  • Geen bewaking: Zonder 429-analyse blijven verkeerde configuraties onopgemerkt.
  • Over de whitelist: Te ruime uitzonderingen zetten de deur wijd open – zorg voor een gerichte, tijdelijke en transparante whitelist.

Bijzonderheden met HTTP/2, SSE en caching

HTTP/2 bundelt verzoeken via een klein aantal verbindingen; limit_conn blijft toch relevant, want streams verbruiken resources. Server-Sent Events of lange downloads leiden zelden tot rate limits (weinig verzoeken), maar kosten wel tijd – hier pas ik parallel beperkingen toe met `limit_conn` of implementeer ik bandbreedtestrategieën. Waar mogelijk ontlast ik het systeem met Caching (bijv. statische assets, veelvoorkomende GET-verzoeken), zodat de limieten minder vaak in werking treden en gebruikers snellere antwoorden krijgen.

Operationele checklist

  • Real-IP correct, sleutels gedefinieerd (IP/token/gebruiker)
  • Zones ruim bemeten, statistieken/logbestanden aanwezig
  • snelheid/burst afgestemd per padklasse, nodelay bewust ingesteld
  • Dry-run getest, 429-communicatie (Retry-After) geïmplementeerd
  • Uitzonderingen voor Health/Webhooks, combinatie met limit_conn
  • Iteratieve heranalyse en waarschuwing bij afwijkingen

Huidige artikelen

Linux-server met gevisualiseerde kengetallen voor drukstagnatie in het datacenter
Administratie

Linux PSI voor nauwkeurige prestatieanalyse en monitoring

Linux PSI (Pressure Stall Information) laat zien in hoeverre de CPU, het geheugen en de I/O je systeem vertragen. Ontdek hoe je PSI kunt activeren en kunt gebruiken voor nauwkeurige prestatiebewaking.