Application Security

Rate Limiting Guide: Stop Brute-Force Attacks and Abuse

Rate limiting caps how many requests one client can send in a time window. It is the single most effective control against credential stuffing, brute-force logins, scraping and layer-7 denial of service.

<p>Rate limiting restricts the number of requests a single client (IP address, user account, or API key) can make to your site within a time window — for example 10 login attempts per minute or 100 API calls per hour. Exceed the cap and further requests are rejected with HTTP 429, delayed, or challenged. It is your first and most cost-effective line of defense against brute-force logins, credential stuffing, content scraping and application-layer denial of service.</p> <h2>Why Rate Limiting Matters</h2> <p>Attackers do not &quot;hack&quot; logins one at a time — they automate. A credential-stuffing bot tries thousands of username/password pairs stolen from other breaches. Without a rate limit, a single attacker can test millions of combinations per day against your login form, password reset endpoint and API. Rate limiting turns that firehose into a trickle: after a handful of failures the attacker is locked out or slowed to a crawl, making the attack economically pointless.</p> <p>Rate limiting also protects availability. A flood of expensive requests (search queries, report generation, file downloads) from one source can starve your workers and make the site slow for everyone. Per-client caps keep a single abusive client from consuming more than its fair share.</p> <h2>What to Rate-Limit</h2> <p>Prioritize endpoints where automation gives attackers an advantage:</p> <ul> <li><strong>Login and password reset</strong> — 5-10 attempts per minute per account and per IP</li> <li><strong>Password reset email sending</strong> — 3-5 per hour per account</li> <li><strong>Search and filter endpoints</strong> — 30-60 per minute per user</li> <li><strong>API endpoints</strong> — tiered by plan (for example 60/min free, 1000/min paid)</li> <li><strong>File upload and export</strong> — 5-10 per minute per user</li> <li><strong>Signup and contact forms</strong> — 3-5 per hour per IP (blocks spam registration)</li> <li><strong>One-time codes (2FA/MFA)</strong> — 3-5 per 15 minutes per account</li> </ul> <h2>Step 1: Add Nginx-Level Rate Limiting for the Hot Paths</h2> <p>Nginx can throttle before a request ever reaches PHP, which is the cheapest place to stop a flood. Define a zone and apply it to the endpoints that matter.</p> <pre><code># nginx.conf http block limit_req_zone $binary_remote_addr zone=login:10m rate=10r/m; limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m; # server block location = /login { limit_req zone=login burst=5 nodelay; # ... existing proxy/pass config } location /api/ { limit_req zone=api burst=20 nodelay; } </code></pre> <p>The <code>burst</code> parameter allows a short spike (a human double-clicking) before throttling kicks in. <code>nodelay</code> rejects excess requests immediately instead of queueing them.</p> <h2>Step 2: Add Application-Level Limits for Per-Account Precision</h2> <p>The network layer only sees IP addresses; attackers rotate those with proxies. Application-level limits keyed on the account or API key catch an attacker who hops IPs but keeps hammering one account.</p> <p>For Laravel, the built-in <code>throttle</code> middleware handles this:</p> <pre><code class="language-php">// routes/web.php Route::post('/login', [AuthController::class, 'login']) -&gt;middleware('throttle:10,1'); // 10 requests per minute per IP // per-account throttle on 2FA codes Route::post('/2fa/verify', [TwoFactorController::class, 'verify']) -&gt;middleware('throttle:5,10'); // 5 requests per 10 minutes </code></pre> <p>For API keys, store a counter in Redis keyed on the key identifier (<code>rl:{apikey}:{window}</code>) and increment it on every request, rejecting with <code>429 Too Many Requests</code> plus a <code>Retry-After</code> header when the limit is crossed.</p> <h2>Step 3: Return the Right Status and Headers</h2> <p>When a client exceeds its budget, respond with:</p> <ul> <li><strong>HTTP 429 Too Many Requests</strong> — the correct status code, not 403 or 500</li> <li><strong>Retry-After: 60</strong> — seconds until the client may retry</li> <li><strong>A JSON body for APIs</strong> — <code>{&quot;error&quot;: &quot;rate_limit_exceeded&quot;, &quot;retry_after&quot;: 60}</code></li> </ul> <p>Well-behaved clients and API gateways read these headers and back off automatically, which reduces load for both sides.</p> <h2>Step 4: Layer in Progressive Penalties</h2> <p>Simple caps are easy to evade with IP rotation. Progressive penalties make abuse expensive:</p> <ol> <li>First limit breach: 1-minute lockout for that identity</li> <li>Second breach within an hour: 15-minute lockout</li> <li>Third breach: 24-hour lockout plus a security alert to the account owner</li> </ol> <p>Streak-based lockouts defeat distributed attacks on a single account, because every participating IP inherits the account's penalty state.</p> <h2>Signs You Need This Today</h2> <p>Run a free <a href="https://2mny.com/free-security-scan">website security scan</a> and check the rate-limiting result. Our scanner tests whether repeated burst requests to key endpoints are throttled, delayed or rejected — the three behaviors that separate a hardened site from a wide-open one. You can also check your logs: if a single IP or user-agent shows hundreds of POST requests to <code>/login</code> or <code>/wp-login.php</code> per hour, you are being probed right now.</p> <h2>Frequently Asked Questions</h2> <h3>Q: What is the difference between rate limiting and a WAF?</h3> <p>A: Rate limiting caps request volume per client, while a web application firewall inspects request content for attack patterns (SQLi, XSS, known exploits). They solve different problems and work best together: the WAF blocks malicious payloads, the rate limiter makes automation uneconomical. See our <a href="https://2mny.com/vulnerability/no-waf-detected">WAF detection guide</a> for how we check for one.</p> <h3>Q: Should I block or just delay over-limit requests?</h3> <p>A: Delaying (queueing) smooths legitimate bursts but still spends server resources holding connections. For login and abuse-prone endpoints, reject with 429 immediately — it is cheaper and clearer. For public APIs, a short delay queue can improve perceived reliability for well-behaved clients.</p> <h3>Q: How do I rate-limit behind Cloudflare or a CDN?</h3> <p>A: Use the real client IP from headers such as CF-Connecting-IP or X-Forwarded-For (validated to come from your CDN's range), otherwise every visitor shares the CDN's IP and one limit. Cloudflare also offers its own rate-limiting rules at the edge, which stop floods before they reach your server at all.</p> <h3>Q: What limits are reasonable for a normal login form?</h3> <p>A: A practical baseline is 10 attempts per minute per IP and 10 per hour per account, with progressive lockout after repeated breaches. Real humans rarely fail more than 3-5 times; anything beyond that is automation.</p>

Was this article helpful?

Check if your site has this issue

Our free scan covers this and 40 more checks — no signup to start.

Start Free Scan