Spring til indhold
ArkitekturIndsigter

Hvornår I skal rate-limite — og hvad I faktisk limiter

I har rate-limitet, når det dyre kald har et loft, og 429'eren har en ejer. En WAF uden en 429 er et håb.

Rate limiting er et loft på det dyre kald — login, søgning, webhooks — ikke en brandmur rundt om hele sitet. Sæt det, når misbrug er billigere end jeres origin, og ej 429'eren I viser.

14 minutters læsning
Stiliseret systemdiagram med et API-snit og en tydelig kø foran login og webhooks — et loft, ikke en brandmur rundt om hele sitet.

Arkitektur & API

Hvis I søger efter rate limiting api, eller efter beskyt api, står I typisk ét sted: nogen har smidt en formular, et login eller et webhook-endpoint på internettet, og I er usikre på om det tæller som 'beskyttet'. Nogen sætter en Cloudflare-regel på hele hostnamet og kalder det færdigt. Nogen rate-limiter 'al trafik' og opdager at forsiden nu 429'er, mens login stadig er frit. Nogen har en middleware der tæller IP'er i hukommelse på tre serverless-instanser, der ikke deler tæller. Nogen nægter at røre noget, indtil de har et identitets- lag, og lader søgningen køre ubegrænset imens.

Sikkerheds-indlægget handler om headers, opdateringer og det admin-vindue, I glemte at lukke. Den her handler om loftet på det dyre: det kald, der hasher et password, der slår op i jeres søgeindeks, der skriver en ordre, der kalder en model. En ærlig rate limit er ikke 'beskyt API'. Det er en sætning om hvilket job der må koste, hvor ofte, og hvad brugeren ser, når loftet rammes. Uden den sætning har I en regel. I har ikke et forsvar.

TL;DR

De vigtigste pointer

  • Limit det dyre kald — login, søgning, webhooks, AI, checkout — ikke 'al trafik' som om forsiden var angrebet.
  • En WAF og en app-429 er to jobs. CDN'en stopper det grove. Appen ejer det, der kræver jeres data.
  • Tælleren skal deles. En in-memory limit på tre serverless-instanser er tre huller, I kalder ét loft.
  • En 429 skal sige hvornår I må prøve igen — Retry-After, et ærligt UI, ikke en 500 eller en tom side.
  • Identitet slår IP, når I har den. En NAT bag et kontor er ikke en flok angribere. En udløbet session er heller ikke uskyld.
  • Sæt loftet, når misbrug er billigere end jeres origin — ikke når nogen har sagt 'vi skal have rate limiting' i et slide.

Hvorfor det her overhovedet er vigtigt

Et endpoint uden loft er en faktura, I ikke har skrevet under på. Login hasher. Søgning læser. Webhooks skriver. En model koster pr. token. En scraper, et stuffed login, et 'prøv alle CVR-numre'-script, et webhook I selv kaldte i et loop — det er samme klasse: nogen har fundet det dyre, og I betaler, indtil I siger nej. Loftet er ikke paranoia. Det er at nægte at subsidiere det, I ikke ville køre i hånden.

Teams arver ordet rate limiting fra et andet lag. I et edge-event er det 'blokér landet'. I en API-gateway er det tokens pr. nøgle. I en Next.js-route er det ofte en middleware, nogen kopierede, der tæller forkert, viser 500, og rammer den forkerte sti. I tror I har beskyttet API'et. I har flyttet smerten til forsiden, eller I har et loft, der nulstilles hver gang en instans starter. Ritualet er dyrt, fordi det giver jer tilladelse til ikke at pege på det kald, der faktisk gør ondt.

Det er også et tillids- og produkts-spørgsmål. En 429 uden Retry-After er en fejl, brugeren skyder på jer. En 403 på et login, der bare var for hurtigt, er en lockout I ikke kan forklare. En captcha foran hele sitet, fordi I ikke ville eje et loft på `/api/search`, er teater: I straffer den, der læser, for at I ikke ville begrænse den, der scraper. Hvis ingen ejer 429-siden, er loftet et netværkssignal. Det er ikke et produkt.

Vi skriver det, fordi 'vi skal beskytte API'et' er blevet et køb, I tager før I har en sætning om hvilket kald, hvilken identitet og hvilken 429. Et ærligt setup er kedeligt: loft på login, søgning, webhooks og det, der kalder videre; delt tæller; ærlig status. Uden den sætning har I en WAF-regel med et tal. I har ikke rate limiting.

Hvad rate limiting faktisk er — og hvad 'beskyt API' ikke er

Rate limiting er et loft på et navngivet kald i et navngivet vindue: så mange loginforsøg pr. konto og pr. IP pr. kvarter, så mange søgninger pr. session pr. minut, så mange webhook-retries pr. kilde. Den er et produktvalg, fordi den siger hvem I tror I er, og hvad I accepterer at køre. Den er ikke et skældsord. De fleste offentlige sites skal have den på tre stier, ikke på alle.

Det er ikke 'blokér bots'. En WAF, et managed challenge, et bot-score, er et andet job: at holde det grove ude, før det rammer origin. Det er rigtigt, og det er ikke loftet. Loftet er det, der sker når kaldet allerede er jeres — en rigtig bruger, en rigtig nøgle, en rigtig form — og I stadig siger nej, fordi det er for ofte. Hvis I kun har WAF, har I et håb om at det dyre kald ikke kommer ind. I har ikke sagt nej til det, der kom ind.

Det er heller ikke 'sæt et tal på hostname'. En global request-limit rammer den side, I gerne vil have crawlet, og lader `/api/login` være det, der overlever, fordi det er færre bytes. Angriberen vælger det dyre. I limiter det billige. Et ærligt loft starter med sætningen 'det her kald koster os X i CPU, i vendor, i lockout-risiko' — kvalitativt, ikke som et dashboard-teater. Uden den sætning er tallet indretning.

Og det er ikke en in-memory Map i en serverless-funktion. Tre instanser, tre tællere, tre gange loftet. En deploy, og tælleren er glemt. Rate limiting, der ikke overlever en ny instans, er en suggestion. Brug noget delt: KV, Redis, et platform-rate-limit, edge-cache med nøgle. Vælg det kedelige, der tæller rigtigt. Et klogt tal på tre huller er stadig tre huller.

Det der typisk går galt, når nogen siger vi har rate-limitet

Den første fejl er at limite det synlige, ikke det dyre. I sætter et loft på `/` fordi det er det, I kan se i CDN-loggen, og lader `/api/search`, `/api/auth` og webhook-routen køre. I har flyttet grafen. I har ikke flyttet risikoen. Et ærligt loft starter med det kald, I ikke vil køre i et loop med hånden. Hvis I ikke kan pege på det, er I ikke klar til et tal. I er klar til at læse jeres egne routes.

Den anden fejl er tælleren, I ikke delte. Next.js på Vercel, Cloudflare Workers, to regioner, preview og prod. En Map i modulet, en cookie I selv sætter, en edge-middleware uden storage. Det virker i ét vindue. Det virker ikke når tre isolates tager de næste tre requests. I får et loft I kan demo'e, og et hul I betaler for. Delt state er pligten. Alt andet er et lokal-loft, I kalder globalt.

Den tredje fejl er statuskoden og kroppen. En 500 på for mange logins er en løgn: I ved præcis hvad der skete. En 200 med `{ ok: false }` er et hul, klienten ikke kan skelne fra validering. En 403, som om brugeren var bandlyst, er en lockout I ikke kan forklare til support. 429 er ordet. Retry-After er tiden. UI'et skal sige 'prøv igen om et øjeblik', ikke 'noget gik galt'. Fejlsider-indlægget er skærmen. Her er pligten at lade loftet være sandt i headeren.

Den fjerde fejl er at købe identitetsteater før I har et loft. 'Vi venter på SSO, så limiter vi.' Imens er den offentlige søgning og den offentlige formular åbne. I kan limite på IP og på cookie, indtil I har en bruger. Det er groft. Det er bedre end ingenting. En NAT er et problem, I kan rumme med et højere loft og et sekundært signal. Ingen identitet er ikke en grund til at lade `/api/login` køre ubegrænset.

WAF og edge vs. app-429 — to lag, to slags nej

Det ærlige edge-lag

Stop det grove, før det rammer origin. Hostname, bot-score, de kendte mønstre.

  • WAF og managed challenge hører til her — ikke som erstatning for et app-loft
  • CDN-rate-limit på åbenlyse floods: det, der ikke skal have lov at møde jeres kode
  • Er det rigtige første filter, og det forkerte eneste filter, når kaldet kræver jeres data
  • Ser bytes og mønstre. Ser ikke at søgningen er dyrere end forsiden

Det ærlige app-loft

429 på det navngivne kald, med delt tæller og en krop I ejer.

  • Login, søgning, webhooks, AI, checkout — ét loft pr. job, ikke ét tal for hele API'et
  • Nøgle: bruger, token, IP som fallback — og et vindue I kan forklare
  • Retry-After og et UI, der siger nej ærligt, ikke en 500 I kalder 'robusthed'
  • Er spild, hvis I stadig ikke har skrevet hvilket kald der gør ondt

Sådan beslutter I om I overhovedet skal rate-limite — og hvor

Uden den her rækkefølge får I en hostname-regel, I kalder API-beskyttelse.

  1. 01

    List de kald, I ikke vil køre i et loop med hånden

    Login. Password-reset. Søgning. 'Send tilbud'. Webhooks ind og ud. Det, der kalder en model eller en betalingsgateway. Hvis listen er tom, har I ikke et rate-limit-problem. I har et 'nogen sagde API'-problem. Skriv listen. Det er loftet. Resten af sitet må gerne være åbent for crawl og for læsere.

  2. 02

    Skriv identiteten, I faktisk har i dag — ikke den, I håber på

    En logget bruger. En API-nøgle. En cookie. En IP. En signeret webhook-hemmelighed. Limit på det, I kan pege på. Bland ikke 'vi får SSO næste kvartal' ind i det, der er åbent i dag. IP er groft og rammer NAT. Den er stadig bedre end ingenting på `/api/login`. Når I har en bruger, så limitér brugeren, og behold IP som et grovere loft bagved.

  3. 03

    Vælg et delt sted at tælle — eller lad være at lade som om I tæller

    KV, Redis, platform-rate-limit, et edge-store med nøgle. Ikke en modul-variabel. Ikke tre caches, I tror er én. Hvis I ikke vil eje et store, så brug det loft, platformen allerede har på kanten, og vær ærlige om at det er groft. Et klogt app-loft uden delt state er et demo-loft. Det tæller i ét vindue. Det tæller ikke i prod.

  4. 04

    Beslut 429'eren, før I tænder tallet

    Status. Retry-After. En krop, klienten kan skelne fra validering. En side, et menneske kan læse, hvis det er en formular. Fejlsider-indlægget er chrome. Her skal I ikke late som om en tom 500 er 'fail closed'. Fail closed på login er at sige nej ærligt. Fail closed på forsiden er at I har limiteret det forkerte kald.

  5. 05

    Giv loftet et sluk- og et justér-kriterium

    Hvis jobbet var ' stuffed login', og I har sat MFA og lockout, kan IP-loftet være grovere. Hvis jobbet var 'søgning der rammer databasen', og I har cachet resultater, kan vinduet åbnes. Et loft uden justering er hvordan I 429'er jeres egne integrationstests og de rigtige kunder i samme NAT. Skriv hvem der må ændre tallet, og hvor I ser at det rammer forkert.

“I har rate-limitet, når det dyre kald har et loft, og 429'eren har en ejer. En WAF uden en 429 er et håb.”

Login, søgning, webhooks og den offentlige formular — samme ord, fire jobs

Login og password-reset er det loft, der oftest er rigtigt af lockout-risiko, ikke af CPU. I vil ikke hashe et væld af gæt. I vil ikke sende en bunke reset-mails. Identitet slår IP, når I har en konto. IP slår ingenting, når I ikke har. Et ærligt login-loft er stramt pr. konto, grovere pr. IP, og en 429 der ikke lyder som 'forkert adgangskode'. Bland de to, og I har givet en angriber et orakel. Sikkerheds-indlægget er headers og hemmeligheder. Her er pligten at lade gættet være dyrt for dem, ikke for jer.

Søgning og 'typeahead' er det loft, der oftest er rigtigt af origin-regning. Hvert tastetryk er et databasekald, medmindre I cacher. En scraper, der elsker jeres `/api/search`, er ikke en læser. Et globalt hostname-loft rammer bloggen. Et loft på søge-routen rammer jobbet. Cache det, I kan. Limit det, I ikke kan. Hvis I 'beskytter API'et' ved at kræve login for at søge i et offentligt katalog, har I flyttet produktet. I har ikke flyttet loftet.

Webhooks ind og ud er det loft, I glemmer, indtil en partner retrier i et loop, eller indtil jeres eget job kalder jer selv. Signér hemmeligheden. Idempotens på event-id. Og et loft pr. kilde, så en mislykket handler ikke bliver en denial, I har betalt for. Det er ikke 'beskyt API'. Det er at nægte at lade et loop, I selv har, ligne trafik. Observability-indlægget er at se det. Her er at sige nej, når I har set det nok.

Den offentlige formular — kontakt, 'book en samtale', newsletter — er det loft, der oftest bliver til captcha-teater. Captcha kan være rigtigt, når I har et menneske foran en dyr handling. Den er forkert som erstatning for et loft: I kan limite pr. IP og pr. felt-hash, skjule honeypot, og stadig vise formularen. Cookie-banner-indlægget er samtykke. Den her er at lade spam være dyrt, uden at straffe den, der skriver tre linjer i god tro. Et portal-landing er det, I bygger, når formularen er blevet et arbejdsgang — ikke når I bare skulle have sagt 429.

Hvad I typisk bør gøre i den situation, I står i

Venstre er det, I typisk siger. Højre er det næste skridt, vi typisk anbefaler.

  • Situation

    Nogen har sagt at vi skal beskytte API'et, og I har sat en WAF på hostname

    Næste skridt

    Behold WAF'en til det grove. List de dyre kald og sæt 429 der — ikke et tal på forsiden

  • Situation

    Login er åbent, og I venter på SSO før I limiter

    Næste skridt

    Limit pr. konto og pr. IP i dag. SSO er identitet. Det er ikke et loft, I skylder nu

  • Situation

    Søgning rammer databasen på hvert tastetryk, og I overvejer captcha på hele sitet

    Næste skridt

    Cache og limit `/api/search`. Captcha på bloggen er teater for et kald, I ikke ville eje

  • Situation

    Middleware tæller i hukommelse, og det virker i ét preview-vindue

    Næste skridt

    Det tæller ikke i prod. Flyt tælleren til noget delt, eller kald det et demo-loft

  • Situation

    I 429'er med en 500, 'så klienten prøver igen'

    Næste skridt

    Så har I bedt om mere last. 429 og Retry-After. En 500 er en løgn, retry-køen elsker

Tjekliste før I kalder det rate limiting

Hvis I mangler flere af punkterne, er sætningen beskyt API — setup'et er en hostname-regel.

  • I kan pege på de kald, I limiter, og sige hvorfor netop de er dyre
  • Tælleren overlever en ny instans og er delt på tværs af de instanser, I faktisk kører
  • 429 er status, Retry-After er tid, og UI'et lyver ikke om at 'noget gik galt'
  • WAF og edge er et lag. App-loftet er et andet. I har ikke erstattet det ene med det andet
  • Identitet slår IP, når I har den — og IP er der stadig som et grovere loft
  • I ved hvem der må åbne vinduet, når det rammer jeres egne brugere i samme NAT

Spørgsmål vi får igen og igen

  • Hvornår skal vi rate-limite et API?

    Når misbrug af et navngivet kald er billigere for andre end for jer: login, søgning, webhooks, det der kalder videre til en model eller en gateway. Hvis jobbet er at nogen sagde 'beskyt API' i et slide, så list de dyre stier først. De fleste offentlige sites har tre stier, der skal have loft, og en forside, der skal kunne crawles. Skriv kaldet. Hvis I ikke kan, så køb ikke et tal på hostname.

  • Er en WAF det samme som rate limiting?

    Nej. En WAF og et bot-challenge stopper det grove, før det rammer jeres kode. Rate limiting er loftet på et kald, der allerede er jeres — en rigtig form, en rigtig nøgle, en rigtig bruger der rammer for ofte. I kan have WAF uden 429, og 429 uden WAF. Vi ser oftere det første, og det er det, der lader søgningen køre ubegrænset bag en grøn 'vi er beskyttet'. Køb kanten til floods. Køb appen til det dyre kald.

  • Skal vi limite på IP eller på bruger?

    På det, I har. Bruger og API-nøgle slår IP, når identiteten er der. IP er groft og rammer et kontor bag NAT — rum det med et højere loft og et sekundært signal, ikke ved at droppe loftet. Ingen identitet er ikke en grund til at lade login være frit. Limit pr. IP og pr. konto samtidig på auth. Limit pr. nøgle på et API, I har udstedt. Kald det det, det er. IP-only på et indlogget endpoint er et hul, I har givet den, der skifter adresse.

  • Hvorfor duer in-memory rate limiting ikke på serverless?

    Fordi tælleren dør, når isolatet dør, og fordi tre instanser er tre tællere. I har sat et tal, I kan vise i ét preview. I har ikke sat et loft, der holder i prod. Brug noget delt, eller brug platformens edge-limit og vær ærlige om grovheden. En middleware-Map er fin som prototype. Den er en løgn som den sætning, I siger til ledelsen.

  • Hvad skal en 429 gøre anderledes end en 500 eller en captcha?

    Den skal sige at I har sagt nej til frekvens, ikke at sitet er gået i stykker, og ikke at brugeren skal bevise at de er et menneske, medmindre handlingen kræver det. Retry-After. En krop, klienten kan parse. Et UI, et menneske kan læse. En 500 beder retry-køen om mere last. En captcha på hele sitet er teater for et kald, I ikke ville limite. Fejlsider-indlægget er chrome. Her er at lade loftet være sandt.

Hvis I har en WAF på hostname og et login uden loft

Lad os skille det dyre kald fra 'beskyt API'.

Et navngivet loft og en ærlig 429 slår en regel, I har sat på den forkerte dør.

Hvorfor vi skrev den her

Det er sådan vi tænker — og det er det vi bygger.

Vores indsigter handler om det vi rent faktisk leverer. Hvis det her ramte noget du arbejder med, så er der en konkret service der ligger tæt på.