Use Redis as a Rate Limiter with PHP on Ubuntu 20.04

Updated on November 13, 2020
Use Redis as a Rate Limiter with PHP on Ubuntu 20.04 header image

Introduction

Rate limiting is a method of limiting resource usage in your server. For instance, if you're running an API (Application Programming Interface), you may put a cap limiting the number of requests a user can make on your server within a specific timeframe. Controlling the rate of requests made in your web application reduces the risk of DoS (Denial of Service) attack. This allows you to enforce a fair usage policy for your application. In a large-scale web application, limiting operations from exceeding certain constraints also allows you to save huge costs on bandwidth and resource usage.

This guide uses a Redis server to limit requests to your web resource with PHP on a Vultr Ubuntu 20.04 server.

Prerequisites

To follow along with this guide, ensure you have the following:

1. Install Redis Extension

Log in to your Vultr Ubuntu cloud server and install the php-redis extension. This module allows you to use the Redis Library in PHP code.

$ sudo apt install -y php-redis

Restart the Apache server to load the new configuration.

$ sudo systemctl restart apache2

2. Create a PHP Resource

Create a PHP resource in the root directory of your server.

$ sudo nano /var/www/html/resource.php

Then, add the information below to the file.

<?php 
    try {             
            $redis = new Redis(); 
            $redis->connect('127.0.0.1', 6379); 
         
            $username = $_SERVER['PHP_AUTH_USER']; 
            $password = $_SERVER['PHP_AUTH_PW'];

            $key = $username;

            $allowed_requests_per_period = 3;
            $time_frame_in_seconds       = 10;

            $total_requests = 0;

            if ($redis->exists($key)) {
                $redis->INCR($key);                  
                $total_requests = $redis->get($key);                    

                if ($total_requests > $allowed_requests_per_period) {
                   echo "Hey, " . $username . ", You've already reached your requests limit. Total requests " 
                         . $total_requests . " Allowed requests " . $allowed_requests_per_period;
                   exit();                   
                }                

            } else {               
                $redis->set($key, 1); 
                $redis->expire($key, $time_frame_in_seconds); 
                $total_requests = 1;       
            } 

            echo "ok, total requests " . $total_requests;            

        } catch (Exception $e) {
            echo $e->getMessage();
        }
?>

When you finish editing the file, save and close it by pressing Ctrl + X, Y then Enter.

The /var/www/html/resource.php code explained:

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

You've used the above code to connect to a Redis server on your localhost via port 6379.

$username = $_SERVER['PHP_AUTH_USER']; 
$password = $_SERVER['PHP_AUTH_PW'];

When you've configured PHP to run as an Apache module, the $_SERVER['PHP_AUTH_USER'] and $_SERVER['PHP_AUTH_PW'] variables assign the HTTP Basic Authentication credentials to the $username and $password variables respectively.

$key = $username;

Because you might have many users accessing your web resource, creating a Redis key with the $username variable retrieved from the $_SERVER['PHP_AUTH_USER'] variable allows you to track the traffic of each user independently.

$allowed_requests_per_period = 3;
$time_frame_in_seconds       = 10;

The two variables above allow you to limit your web resource requests depending on the set usage policy in your application. This example limits users to 3 requests in 10 seconds. Adjust these values depending on your needs.

$total_requests = 0;

You've initialized the total requests made by a user using the $total_requests variable.

if ($redis->exists($key)) {
     $redis->INCR($key);                  
     $total_requests = $redis->get($key);                    

     if ($total_requests > $allowed_requests_per_period) {
         echo "You've already reached your requests limit. Total requests " 
         . $total_requests . " Allowed requests " . $allowed_requests_per_period;
         exit();                   
     }                

 } else {               
     $redis->set($key, 1); 
     $redis->expire($key, $time_frame_in_seconds); 
     $total_requests = 1;       
 } 

The code above checks for the existence of the $key variable on the Redis server. If the key exists, you are incrementing it by 1 using the $redis->INCR($key) command. If the key does not exist, you are setting a new key with a default value of 1 using the $redis->set($key, 1) command as well as the expiration period defined by the $time_frame_in_seconds variable. Next, you are accessing the current count of the $key key using the $redis->get($key). Once the value is retrieved, you're assigning it to the $total_requests variable.

Then, you're using the PHP logic if...else syntax to evaluate if the client has exceeded the total requests allowed during the period defined. If the limit is exceeded, you're raising an error to the user.

echo "ok, total requests " . $total_requests; 

Towards the bottom of the file, you're echoing the number of requests made by the user if they've not exceeded their limit.

3. Test the Redis Rate Limiter

Open a command prompt on your server and use the curl command to query the /var/www/html/resource.php web resource that you have created above. The dummy query string ?[1-20] at the end of the http://localhost/resource.php URL allows you to access the resource 20 times to see if the script will be rate-limited by the Redis server.

$ curl --user john:SAMPLE_PASSWORD 
  -H "Accept: text/plain" 
  -H "Content-Type: text/plain" 
  -X GET http://localhost/resource.php?[1-20]

After running the above code, you should get the below output.

[1/20]: http://localhost/resource.php?1 --> <stdout>
--_curl_--http://localhost/resource.php?1
    ok, total requests 1
[2/20]: http://localhost/resource.php?2 --> <stdout>
--_curl_--http://localhost/resource.php?2
    ok, total requests 2
[3/20]: http://localhost/resource.php?3 --> <stdout>
--_curl_--http://localhost/resource.php?3
    ok, total requests 3
[4/20]: http://localhost/resource.php?4 --> <stdout>
--_curl_--http://localhost/resource.php?4
    Hey, john, You've already reached your requests limit. Total requests 4 Allowed requests 3
[5/20]: http://localhost/resource.php?5 --> <stdout>
--_curl_--http://localhost/resource.php?5
    Hey, john, You've already reached your requests limit. Total requests 5 Allowed requests 3
[6/20]: http://localhost/resource.php?6 --> <stdout>
...

Notice that the first three requests are accepted. However, the PHP script started throwing a rate-limit error beginning at the fourth request.

Conclusion

In this tutorial, you've used a Redis server to limit resource usage with PHP on Ubuntu 20.04. While this guide is just an example of how rate limiting works with Redis, you can extend it further to suit your web application's needs.