Rate Limiting with Nginx

Now that you have landed at this article after reading the headline, I assume that you already know what Nginx is and why is it used in front of our backend servers/services as a reverse proxying agent. Well, in case you still landed at this article without that knowledge, fret not as I am attaching a small list of resources which can help you with that :

So now we are at a place where we can start with the real deal of learning as to how can one use nginx for rate limiting the traffic to your website/API. This will also work even when using behind a load balancer or when nginx is being used as one, till the actual IPs are passed in headers to nginx for it to differentiate between requests from different hosts. It is useful, if your site is hammered by a bot doing multiple requests per second and thus increasing your server load. With the ngx_http_limit_req_module you can define a rate limit, and if a visitor exceeds this rate, he will get a 503 error. The rate limiting is performed via "Leaky Bucket" algorithm usually employed in computer networks with bandwidth limitations.

To start with, we first define a limit_req_zone in our nginx.conf as shown below. Now if you ask why is that necessary, the answer to that would be, Computer Science & Logic 101 : We need to solve a problem of limiting traffic to an IP based on the traffic, but we need to know what the traffic is corresponding to every IP and then compute on the data basis our business need/logic as to when to limit the resources to that IP.

    http {
        limit_req_zone $binary_remote_addr zone=test:20m rate=10r/s;
        ...

        server {
            ...

This sets the shared memory zone with the requisite rate of requests. Here the shared memory zone is called test and is allocated 20MB of storage. Instead of the variable $remote_addr, we use the variable $binary_remote_addr which reduces the size of the state to 64 bytes. There can be about 16,000 states in a 1MB zone, so 20MB allow for about 320,000 states, so this should be enough for your visitors, but you may change it depending on how much traffic you receive. The rate is limited to ten request per second(rps). Please note that rps must be an integer values. So half a request per second should be set as 30 rps. This configuration of setting the request zone must go inside the http {} container.

After having defined a storage area to store the data, we use this to actually put rate limiting to use. This is done using the limit_req directive. One can use this directive in http {}, server {}, and location {} containers, but it is most useful in location {} containers that pass requests to your app servers (PHP-FPM, etc.) because otherwise, if you load a single page with lots of assets (images, CSS, and JavaScript files), you would probably exceed the given rate limit with a single page request.

An example directive usage is as

        location ~ \.php$ {
                ...
                limit_req zone=test burst=10;
                ...
        }


limit_req zone=test burst=10; specifies that this rate limit belongs to the session storage area we defined before in the nginx.conf. Burst is nothing but a queue which means that if you exceed the rate limit, the following requests are delayed, and only if you have more requests waiting in the queue than specified in the burst parameter, will you get a 503 error like the image shown below or any other html page which has been defined in configuration or a default one by nginx :

Sample 503 Error Page

When rate limited, the nginx error logs will produce output similar to the following:

2016/10/20 17:28:46 [error] 30347#0: *55 limiting requests, excess: 5.658 by zone "test", client: 10.170.2.13, server: www.example.com, request: "GET /test/results/?keyword= HTTP/1.1", host: "test-site-www.example.com", referrer: "https://test-site-www.example.com/test/results/?keyword=" 
2016/10/20 17:28:46 [error] 30347#0: *55 limiting requests, excess: 5.273 by zone "test", client: 10.170.2.13, server: www.example.com, request: "GET /test/results/?keyword= HTTP/1.1", host: "test-site-www.example.com", referrer: "https://test-site-www.example.com/test/results/?keyword=" 
2016/10/20 17:28:47 [error] 30347#0: *55 limiting requests, excess: 5.508 by zone "test", client: 10.170.2.13, server: www.example.com, request: "GET /test/results/?keyword= HTTP/1.1", host: "test-site-www.example.com", referrer: "https://test-site-www.example.com/test/results/?keyword=" 
2016/10/20 17:28:47 [error] 30347#0: *55 limiting requests, excess: 5.200 by zone "test", client: 10.170.2.13, server: www.example.com, request: "GET /test/results/?keyword= HTTP/1.1", host: "test-site-www.example.com", referrer: "https://test-site-www.example.com/test/results/?keyword=" 
2016/10/20 17:28:48 [error] 30347#0: *55 limiting requests, excess: 5.567 by zone "test", client: 10.170.2.13, server: www.example.com, request: "GET /test/results/?keyword= HTTP/1.1", host: "test-site-www.example.com", referrer: "https://test-site-www.example.com/search/results/?keyword=" 

With this basic configuration, we have enabled rate limiting on our service. Wasn't it too simple? :)

PS : Don't forget to reload nginx after adding or making changes to configuration files for them to take effect using command : `sudo service nginx reload` or similar on your OS.