Advanced
Debug unexpected 429 errors
Find which VitNode rate-limit budget a visitor ran out of, using the X-RateLimit-Category header and the API's rate-limit log line, then fix the cause.
When a real visitor sees "Too many requests", find out which rate-limit budget refused them before you raise anything. Raising the wrong budget hides the cause and weakens protection you still need.
Read the refused budget
Open the browser's Network tab, repeat what the visitor did, and select the first request with status 429. The X-RateLimit-Category response header names the budget:
| Value | What ran out | Usual cause |
|---|---|---|
read | GET / HEAD budget | A loader that fetches on every preload, or a polling screen |
write | Mutation budget | A form that submits in a loop, or autosave on every keystroke |
auth | Sign-in budget | Repeated failed sign-ins, often a password manager retrying |
content-write | Public Content Engine create and edit budget | A member, or a script, posting very quickly |
safety | The cap on every request | A client sending requests in parallel, or many people on one IP |
Find the route in the API log
The API prints one line the first time a client goes over a budget in each window:
[VitNode] Rate limit exceeded: {"category":"read","subject":"user","method":"GET","path":"/api/@vitnode/forum/content/topics/:param","status":429,"retryAfter":24,"store":"redis"}subject says whether a signed-in member (user) or an IP address (ip) ran out, path is the route template with IDs and slugs replaced, and store says whether the counters live in redis or in each instance's memory. The line never includes the member's ID, the IP address, cookies, headers or the query string, and later refusals in the same window are not logged, so a flood stays one line per client.
Fix the cause
readfrom navigation: check that the page's loader reads through the query cache with astaleTime, so hovering a link twice or hovering and then clicking costs one request. For a page that should never load early, setpreload: false.writeorcontent-write: debounce autosave, and disable the submit button while a request is pending.- Many people behind one IP: an office or school shares one public address. Signed-in members already have their own read and write budgets, but guests share them, and everyone shares the safety cap. If
subjectisipand the category issafety, raiserateLimiter.safety.points; if guests run out of reads, raiserateLimiter.read.points. - Every server-rendered request in one bucket: the API sees your web server instead of the visitor. Set up real visitor IPs.
- Limits differ between instances: with
storeset tomemory, each API instance counts on its own. SetREDIS_URLto share counters.
Check the result
Repeat the visitor's steps with the Network tab open. You see no 429, and the API prints no new Rate limit exceeded line.
A request with a session cookie spends its read and write budgets only after the API has checked the session, so the budget belongs to a verified member. Guests are counted before that check, and the per-IP safety cap limits how many session checks one address can cause.