Skip to Content
Course content

84: Rate Limiting and Throttling in PHP

Click on the "Edit" button in the top corner of the screen to edit your slide content.

We’ve built some pretty solid features in this course, but there's a reality check we need to face: the open internet is a chaotic place. Whether it's a poorly written loop in a client's script or a malicious bot trying to scrape your data, if you leave an endpoint wide open, someone will hammer it until your server chokes.

That's where rate limiting comes in. It's not about banning users; it's about protecting your resources. For this lesson, we're going to build a simple rate limiter for a hypothetical "Price Check" API endpoint. I don't want to use a database for this—hits to a SQL database every single time someone requests a page just to check a limit is a performance nightmare. Instead, I'm going to use Redis. It's an in-memory store, it's blazing fast, and it has a built-in "Time To Live" (TTL) feature that makes this entire process trivial.

Setting up the Redis Key Logic

The core idea here is to create a unique key for every user (or IP address) and increment a counter associated with that key. If the counter exceeds our limit within a specific timeframe, we block them. I'll keep it simple: 10 requests per minute per IP.

<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);

$ip = $_SERVER['REMOTE_ADDR'];
$key = "rate_limit:" . $ip;
$limit = 10;
$window = 60; // seconds

$currentRequests = $redis->get($key);

if ($currentRequests >= $limit) {
    http_response_code(429);
    echo "Too many requests. Slow down!";
    exit;
}

$redis->incr($key);
$redis->expire($key, $window);

echo "Here is your price data!";
?>

Wait, I just created a bug

I just noticed something. Look at that $redis->expire($key, $window); line. I'm calling it every single time a request comes in. That's a mistake. By resetting the expiration on every hit, I've accidentally created a "sliding window" that could potentially lock a user out forever if they keep trying to request the page every 10 seconds. The timer keeps resetting to 60, so the key never expires.

To fix this, I only want to set the expiration the very first time the key is created. I'll check if the key exists first, or better yet, use the return value of incr(). If incr() returns 1, it means this is the first request in the current window.

<?php
// ... (connection code same as above)

$currentRequests = $redis->incr($key);

if ($currentRequests === 1) {
    $redis->expire($key, $window);
}

if ($currentRequests > $limit) {
    http_response_code(429);
    echo "Too many requests. Slow down!";
    exit;
}

echo "Here is your price data!";
?>

Adding Professionalism with HTTP Headers

Just sending a 429 error is fine, but if you're building a real API, you should tell the client why they were blocked and when they can try again. This prevents them from just blindly retrying every millisecond, which only makes your server load worse.

I'll add a few custom headers. X-RateLimit-Limit tells them the max, X-RateLimit-Remaining tells them what's left, and Retry-After is a standard HTTP header that tells the client exactly how many seconds to wait.

<?php
// ... (connection and increment logic)

$ttl = $redis->ttl($key);

header("X-RateLimit-Limit: $limit");
header("X-RateLimit-Remaining: " . max(0, $limit - $currentRequests));

if ($currentRequests > $limit) {
    header("Retry-After: $ttl");
    http_response_code(429);
    echo "Too many requests. Please try again in $ttl seconds.";
    exit;
}

echo "Here is your price data!";
?>

Now we have a robust, memory-efficient throttle. We aren't touching the disk, we aren't locking tables in MySQL, and we're giving the client clear instructions on how to behave. It's a small addition, but it's the difference between a site that crashes under pressure and one that stays online.




📋 Practical Task

Build a Contact Form Submission Throttle

Create a PHP script that protects a contact form from being spammed. Instead of a general API limit, implement a "cooling off" period for form submissions.

  • Use Redis to track the IP address of the user submitting the form.
  • Limit the user to only 3 form submissions every 1 hour.
  • If they exceed this limit, return a 429 Too Many Requests status code.
  • Include a Retry-After header showing the remaining seconds until their window resets.
  • Ensure that the expiration timer is only set on the first submission of the window, not on every submission.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.