Redis, also called Remote Dictionary Server, is an open-sourced in-memory database. It is a data-structured storage system that runs on a server’s RAM, which is much quicker than the fastest Solid State Drive (SSD). As a result, Redis is very responsive and an excellent fit for rate limiting.
Rate limiting restricts the number of times a user may request a resource from a server. Many services use rate limits to stop service abuse, like when a user tries to overrun a server with too much load. For example, when using PHP to develop a public API (Application Programming Interface) for your web application, rate restrictions are required. That’s because when you expose an API to the public, you’ll want to limit how many times an individual may repeat an activity in a certain amount of time. Users that have no authority over your system may put it to a standstill.
Rate limiting allows your application to function smoothly by rejecting user requests that exceed a set limit. If you have a large number of clients, rate limitation imposes a fair-use policy that permits each user to access your application at fast speeds. Rate limiting may also help you save money on bandwidth by lowering congestion on your server.
By tracking user activity in a database like MySQL, it would be possible to create a rate-limiting program. However, since such data should be downloaded from disk and evaluated against the defined limit, the final result may not be scalable when multiple people contact the system. Not only is this inefficient, but relational database management solutions were not built for this.
Redis is a good choice for making a rate limiter because it works as an in-memory database and has been proven to be reliable for this. In this tutorial, we’ll walk you through the steps of implementing PHP rate limiting using Redis on Ubuntu 20.04.
Let’s start!
Prerequisites
To follow along with this tutorial, you’ll need the following:
-
The latest version of Ubuntu installed on your system.
-
System users must have sudo privileges.
-
A LAMP stack.
-
Set this up by following the How to Set up LAMP Stack tutorial.
-
-
A Redis server.
-
Follow along with the How to Install and Secure Redis Server guide to set up Redis in your system.
-
Step 1: Install Redis Extension for PHP
Before we begin, let’s update the repositories to avoid package conflicts:
1 |
sudo apt update |
Next, install the php-redis extension, a package that makes it possible to use Redis in PHP programs. Run the following sudo command to install php-redis:
1 |
sudo apt install -y php-redis |
After that, restart the Apache server to load the php-redis library:
1 |
sudo systemctl restart apache2 |
The next step is to update the information in your software index and install the Redis library for PHP. Then, we’ll create a PHP resource that restricts access based on a user’s IP address.
Step 2: Create a PHP Web Resource for Rate Limiting
In this step, you’ll create a demo.php file in your web server’s root directory ( /var/www/html/). This file will be open to the public, and users will be able to launch the URL in their preferred web browser. Later, we’ll be using the curl command to verify the accessibility of the resource we want to use. Users can access the sample resource file three times in a 15-second span. An attempt exceeding the maximum limit will throw an error message.
This file’s primary functionality is strongly dependent on the Redis server. The PHP code in the file creates a key on the Redis server depending on the user’s IP address when the user accesses the resource for the first time. The code will attempt to match the user’s IP address with the keys saved in the Redis server and increase the value by one if the key exists when the user returns to the resource. The PHP code will keep checking to see if the new value has reached the maximum amount.
After 15 seconds, the Redis key, which is based on the user’s IP address, will expire, and tracking the user’s visits to the web resource will begin again. Open the /var/www/html/demo.php file in the nano text editor:
1 |
sudo nano /var/www/html/demo.php |
Then, fill out all the fields to initialise the Redis class. Don’t forget to set the REDIS_PASSWORD to the correct value:
1 2 3 4 |
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $redis->auth('REDIS_PASSWORD'); |
Redis->auth supports Redis server plain text authentication. This works well if you’re working locally (through localhost), but if you’re dealing with a distant Redis server, SSL authentication is recommended.
Next, in the same file, set the following variables to their default values:
1 2 3 4 5 |
. . . $max_calls_limit = 3; $time_period = 15; $total_user_calls = 0; |
Let’s understand these statements in detail:
-
$max_calls_limit: A user cannot access the resource to this maximum call limit.
-
$time_period: This is used as a time frame and is counted in seconds. Here, the user is permitted to access the resource as per the limits set in the $max_calls_limit .
-
$total_user_calls: Sums up the number of times a user has requested access to the calls limit in a given time period.
Then, add the following code to get the IP address of the requested asking to access the web page:
1 2 3 4 5 6 7 8 9 10 |
. . . if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $user_ip_address = $_SERVER['HTTP_CLIENT_IP']; } elseif(!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $user_ip_address = $_SERVER['REMOTE_ADDR']; } |
As a demonstration, this code logs users’ actions by their IP addresses. If you have a protected resource on the server that needs authentication, you can track users’ actions using their usernames or access tokens.
In our guide, each user that logs into your system will be assigned a unique identification (for example, a customer ID, developer ID, vendor ID, or even a user ID). Remember to use these IDs instead of the $user_ip address if you are following along with our tutorial.
Here, the user’s IP address is sufficient to demonstrate the notion. Add the following code block to your file once you have the user’s IP address from the preceding code snippet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
. . . if (!$redis->exists($user_ip_address)) { $redis->set($user_ip_address, 1); $redis->expire($user_ip_address, $time_period); $total_user_calls = 1; } else { $redis->INCR($user_ip_address); $total_user_calls = $redis->get($user_ip_address); if ($total_user_calls > $max_calls_limit) { echo "User " . $user_ip_address . " limit exceeded."; exit(); } } echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds"; |
Here is an overview of these statements:
-
if...else: The statement verifies whether there is a key defined with the IP address on the Redis server.
-
If the key is not found, if (!$redis->exists($user_ip_address)) {...}, you can set the key and define its value to 1 using $redis->set($user_ip_address, 1).
-
-
$redis->expire($user_ip_address, $time_period): Reminds the key to expire at a specified time. Here in this tutorial, we have set it to 15 seconds.
-
If the user’s IP address is not found in the Redis key, set the variable $total_user_calls value as 1.
-
else {...}: The statement block uses the $redis->INCR($user_ip_address); command to step up the value of the Redis key by 1. This will be applied to each IP address aligned with the key.
-
Note: You can achieve this only when the key is already set in the Redis server and counted as a repetitive request.
-
-
$total_user_calls = $redis->get($user_ip_address): This statement retrieves the total requests by verifying their respective IP address-based key on the Redis server.
-
if ($total_user_calls > $max_calls_limit) {... }..: This if statement is used to verify the exceeded limit. If yes, you notify the user with echo "User" . $user_ip_address . " limit exceeded.";.
Finally, you’re notifying the user about their number of visits in a specified period using the echo "Welcome" . $user_ip_address . "total calls made" . $total_user_calls . "in" . $time_period . "seconds"; statement.
After that, add the following lines of code in your /var/www/html/demo.php file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $redis->auth('REDIS_PASSWORD'); $max_calls_limit = 3; $time_period = 15; $total_user_calls = 0; if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $user_ip_address = $_SERVER['HTTP_CLIENT_IP']; }elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $user_ip_address = $_SERVER['HTTP_X_FORWARDED_FOR']; } else { $user_ip_address = $_SERVER['REMOTE_ADDR']; } if (!$redis->exists($user_ip_address)) { $redis->set($user_ip_address, 1); $redis->expire($user_ip_address, $time_period); $total_user_calls = 1; } else { $redis->INCR($user_ip_address); $total_user_calls = $redis->get($user_ip_address); if ($total_user_calls > $max_calls_limit) { echo "User " . $user_ip_address . " limit exceeded."; exit(); } } echo "Welcome " . $user_ip_address . " total calls made " . $total_user_calls . " in " . $time_period . " seconds"; ?> |
Save and close the /var/www/html/demo.php file after you’ve done modifying it. On the demo.php web page, you’ve now created the logic required to rate limit users. Let’s test our script in the following step.
Step 3: Run the Redis Rate Limiting Test
You’ll use the curl command in this step to request the web resource that you wrote in Step 2. To thoroughly test the script, issue a single command that requests the resource five times. This may be accomplished by adding a placeholder URL argument to the demo.php file’s end. To perform the curl instructions five times, use the value ?[1-5] at the end of your request.
Execute the following curl command below:
1 |
curl -H "Accept: text/plain" -H "Content-Type: text/plain" -X GET http://localhost/demo.php?[1-5] |
When you execute the code, you should get something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[1/5]: http://localhost/demo.php?1 --> <stdout> --_curl_--http://localhost/demo.php?1 Welcome 127.0.0.1 total calls made 1 in 15 seconds [2/5]: http://localhost/demo.php?2 --> <stdout> --_curl_--http://localhost/demo.php?2 Welcome 127.0.0.1 total calls made 2 in 15 seconds [3/5]: http://localhost/demo.php?3 --> <stdout> --_curl_--http://localhost/demo.php?3 Welcome 127.0.0.1 total calls made 3 in 15 seconds [4/5]: http://localhost/demo.php?4 --> <stdout> --_curl_--http://localhost/demo.php?4 User 127.0.0.1 limit exceeded. [5/5]: http://localhost/demo.php?5 --> <stdout> --_curl_--http://localhost/demo.php?5 User 127.0.0.1 limit exceeded. |
The first three requests, as you can see, went off without a hitch. The fourth and fifth queries, however, were rate-capped by your script. There is a good chance the Redis server is slowing down the speed at which people can make queries.
You have specified low values for the two variables listed below in this guide:
1 2 3 4 |
... $max_calls_limit = 3; $time_period = 15; ... |
When you make your app in a production setting, you might want to think about using bigger numbers, depending on how often you think people will use it.
It’s a good idea to examine real-time statistics before adjusting these numbers. In this example, if your server logs show that an average user visits your application 1,000 times every 60 seconds, you can use that number as an example of how much throttling to use.
Conclusion
In this guide, you learned how to utilize a Redis server with PHP on Ubuntu 20.04. While this post demonstrates how rate limiting works with Redis, you may customize it to meet the demands of your web application. We encourage you to explore real-world examples like Twitter’s maximum request limit, Google’s Custom Search JSON API, and other similar documentation to enhance your knowledge on rate limiting and try experimenting yourself using different time limits.
Furthermore, there are many other learning materials on Redis and PHP that you can access from our blogs:
- Deploy a PHP Application on a Kubernetes Cluster with Ubuntu 18.04
- Installing and Securing PHPmyadmin on Ubuntu 20.04
- How to install the LEMP Stack (Linux Nginx MySQL PHP) on Ubuntu 20.04
Happy Computing!
- How To Set Up GitHub Continuous Integration Pipelines With Self-Hosted Runners on Ubuntu 22.04. - March 20, 2023
- Managing CSV in Node.js using Node-CSV - March 15, 2023
- Containerize A Python App using Docker - March 7, 2023
- Using GitLab for Managing Projects - September 15, 2022
- Creating Drag and Drop Elements with Pure, Vanilla JavaScript - August 4, 2022