Quick question. How many times can someone hit your login endpoint in one minute? If your honest answer is "I don't know" or "as many as they want," I have some bad news and one very easy fix.
I spent years on the offensive side, testing other people's apps. Here is a pattern I saw again and again: the app was clever, the code was clean, and the front door had no limit on how many times you could knock. Unlimited requests is not a feature. It is an open invitation.
Rate limiting is the cheapest, highest-leverage security control most teams still skip. Let me convince you.
What unlimited endpoints actually invite
An endpoint with no limit is a megaphone handed to anyone who finds it.
Brute force. A login with no throttle lets someone try thousands of passwords against one account. Hand them a leaked password list and that becomes credential stuffing, and it works more often than you would like, because people reuse passwords everywhere.
Resource exhaustion. Your expensive endpoints, search, report generation, anything that leans hard on the database, can be hammered until the whole app falls over. That is a denial of service that costs the attacker almost nothing and costs you your uptime.
And the modern nightmare: the bill. If your endpoint quietly calls a paid API behind the scenes, an AI model, an SMS gateway, a geocoding service, then every unlimited request is money leaving your account. I have seen a single unprotected endpoint turn into a four-figure invoice over a weekend. The attacker never even broke in. They just kept asking.
It is cheap because it is simple
Here is why I call it the cheapest security you have: the mental model is trivial. Count requests. If a client crosses the line inside a time window, say no.
That is it. A few lines of config standing in front of real damage. No machine learning, no threat intelligence feed, no six-figure vendor contract. You are just refusing to let one client speak louder than all the rest combined.
And because it is so simple, there is no excuse to postpone it. This is the control you add on a Tuesday afternoon and quietly forget about, right up until the day it saves you.
Where to put the limits
You do not need to protect everything equally. Spend your effort where abuse actually hurts.
Auth routes first. Login, signup, password reset, token endpoints. These are the ones people attack, so these get the tightest limits. A handful of attempts per minute per IP and per account is plenty for a real human and painful for a bot.
Then anything expensive or paid. Every endpoint that costs you money or heavy compute gets a budget, per user, per key, per IP.
Everything else gets a sane global default, so one misbehaving client cannot drown out the rest.
Do it the easy way
You almost certainly do not need to build this from scratch.
Your reverse proxy already does it. Nginx has limit_req. Your API gateway and your CDN, Cloudflare and friends, can throttle before traffic ever touches your server, which is the best possible place to stop a flood. Inside the app, reach for a maintained middleware and back it with Redis so your limits hold across every instance. A fixed window is fine to start. A token bucket is nicer when you want to allow short, honest bursts.
Two details that matter. Return a proper 429 Too Many Requests with a Retry-After header so honest clients back off politely. And log when limits trip, because a spike in rejections is often your earliest warning that someone is probing you.
Here is the whole thing in one sentence: a request you refuse to answer cannot brute force you, cannot drain your database, and cannot run up your bill.
Unlimited was never generosity. It was just a door you forgot to put a lock on.
I am Ksatria Bintang Samudra, an AI-native full-stack engineer with a penetration-testing background and a deep interest in digital forensics, open to remote work worldwide. More of what I build at ksatriabintangsamudra.com.

Top comments (0)