Rate limiting Nginx requests

By Jake Morrison in DevOps on Wed 16 May 2018

Any popular service may be the unfortunate recipient of a DDOS attack. We find that DDOS load ends up driving capacity planning, as it can easily be 10x the normal load.

You can rate limit at multiple levels. You might use a service such as CloudFlare, filtering provided by your hosting provider, a firewall on the local machine, Nginx rate limiting, or the application itself. If you are getting attacked regularly, you will probably end up limiting at all levels, with different thresholds.

The earlier you limit, the fewer resources are used, but the less information you have about the attack and the less control you have over how you respond. If you are running e.g. an API endpoint it's important to be able to distinguish an attack from a legit client gone wild.

Getting the user's IP address in Nginx

If Nginx is behind a CDN, then all the requests will come from the IP of the CDN. In order to log the request and make decisions like rate limiting, we need to get the IP address from a header set by the CDN.

Add this to nginx.conf, telling Nginx to get the IP address from the X-Forwarded-For header:

real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;

Note that this is not entirely trustworthy, as an attacker can still send a request direct to the site with anything they like in the header.

See Serving your Phoenix app with Nginx for complete config file examples.

Similarly, when Nginx proxies requests to the app, we need to pass the request information through to the app.

proxy_set_header Host               $host;
proxy_set_header X-Real-IP          $remote_addr;
proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_set_header Refrerer           $http_referer;
proxy_set_header User-Agent         $http_user_agent;

Getting the user's IP address in Phoenix

Configure Phoenix to read the user's IP from the x-forwarded-for HTTP header.

Rate limiting in Nginx

In nginx.conf set up a rate limiting zone:

limit_req_zone $binary_remote_addr zone=foo:10m rate=1r/s;
limit_req_status 429;

In the vhost, limit requests for the zone:

limit_req zone=foo burst=5 nodelay;