Do you manage a website? Does it have a login form? Can somebody brute force attack it with every common username/password combination until they find one that works?

For many small web applications, the answer to all of the above is, "yes". This is a security risk and the solution is rate limiting. Rate limiting allows you to slow down the rate of requests and even deny requests beyond a specific threshold. Unfortunately, for most busy web developers, rate limiting is often tossed into a large pile of "things I know I should do, but don't have time for".

Advanced rate limiting apps such as django-ratelimit exist, but if you use Nginx as a reverse proxy to your application, the solution is almost trivial.

Let's start with a typical Nginx reverse proxy config:

upstream myapp { server 127.0.0.1 : 8081 ; } server { ... location / { proxy_pass http://myapp ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header Host $http_host ; } }

To enable rate limiting simply add the following line to the top-level of your config file:

limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s ;

This creates a shared memory zone called "login" to store a log of IP addresses that access the rate limited URL(s). 10 MB ( 10m ) will give us enough space to store a history of 160k requests. It will only allow 1 request per second ( 1r/s ).

Then apply it to a location by adding a stanza to your server block:

location /account/login/ { # apply rate limiting limit_req zone=login burst=5 ; # boilerplate copied from location / proxy_pass http://myapp ; proxy_set_header X-Real-IP $remote_addr ; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for ; proxy_set_header Host $http_host ; }

Here we're rate limiting the /account/login/ URL. The burst argument tells Nginx to start dropping requests if more than 5 queue up from a specific IP. As you may have noticed, there's some duplicated configuration between location / and this stanza. Unfortunately this is a necessary evil, but a good configuration management tool can help you avoid error-prone copy/pasting.

Reload Nginx with your new config and you're good to go. To verify it's working, this nasty little bash one-liner can throw out a bunch of HTTP requests quickly:

for i in { 0..20 } ; do ( curl -Is https://example.com/accounts/login/ | head -n1 & ) 2>/dev/null; done

You should only see 200 responses once per second and probably some 503 responses when the queued requests exceeded the burst value. Comparing the results to a regular URL can help you see the difference rate limiting makes.

Here's a full Nginx config with rate limiting enabled. Now go secure your sites!