I recently needed to deploy a rate-limiting solution for a site where certain IP addresses would be white-listed and not subjected to the rate limits. Google found me an example in a blog that turned out not to work, though I’ve no idea whether that’s because the example is incomplete or because nginx has changed since it was written. Here’s the solution I eventually came up with.
First, my servers are behind a proxy, so I needed to pull out the originating IP address and to tell nginx that it should never use whatever IP address the connection actually came from:
real_ip_header X-Forwarded-For;
set_real_ip_from 0.0.0.0/0;
Then, using the HttpGeo module, I set up a match list for addresses that should be rate-limited. This sets the variable $limited
to 1 (meaning “rate-limit this address”) or 0 (“don’t rate-limit this address”). The default is 1 and I list addresses I don’t want limiting with a zero value. Address ranges are in CIDR notation:
geo $limited {
default 1;
10.0.0.0/24 0;
192.168.42.42/32 0;
}
Now I set another variable, $limit
to the IP real IP address of the connection or to the empty string depending on the value of $limited
. If the connection is to be rate-limited then the (binary format) IP address will be used; if not, the empty string is used:
map $limited $limit {
1 $binary_remote_addr;
0 "";
}
And here’s the limit zone, keyed using the value of $limit
:
limit_req_zone $limit zone=limited:10m rate=5r/m;
When $limit
is an empty string, the limit will not be applied. Finally, apply the limit in the location
stanza:
location / {
limit_req zone=limited burst=5;
}
In testing this approach appears to work so far. The site in question goes live next week, so I’ll find out then how it copes in the real world.