How to rate-limit in nginx, but including/excluding certain IP addresses?

I'm able to use limit_req to rate-limit all requests to my server.

However I'd like to remove the rate restriction for certain IP addresses (i.e. whitelist) and use a different rate restriction for certain others (i.e. certain IPs I'd like as low as 1r/s).

I tried using conditionals (e.g. if ( $remote_addr = "1.2.3.4" ) {}) but that seems to work only with rewrite rules, not for rate-limit rules.


Solution 1:

It is really better to avoid using the "if" directive. When the key in limit_req_zone (and limit_conn_zone) is empty the limits are not applied. You can use this in conjunction with the map and geo modules to create a whitelist of IPs where the throttle limits are not applied.

This example shows how to configure a limit for both concurrent requests and request rate from a single IP.

http {
    geo $whitelist {
       default 0;
       # CIDR in the list below are not limited
       1.2.3.0/24 1;
       9.10.11.12/32 1;
       127.0.0.1/32 1;
    }

    map $whitelist $limit {
        0     $binary_remote_addr;
        1     "";
    }

    # The directives below limit concurrent connections from a 
    # non-whitelisted IP address to five

    limit_conn_zone      $limit    zone=connlimit:10m;

    limit_conn           connlimit 5;
    limit_conn_log_level warn;   # logging level when threshold exceeded
    limit_conn_status    503;    # the error code to return

    # The code below limits the number requests from a non-whitelisted IP
    # to one every two seconds with up to 3 requests per IP delayed 
    # until the average time between responses reaches the threshold. 
    # Further requests over and above this limit will result 
    # in an immediate 503 error.

    limit_req_zone       $limit   zone=one:10m  rate=30r/m;

    limit_req            zone=one burst=3;
    limit_req_log_level  warn;
    limit_req_status     503;

The zone directives must be placed at the http level, however the other directives can be placed further down, e.g. at the server or the location level to limit their scope or further tailor the limits.

For futher information refer to the Nginx documentation ngx_http_limit_req_module and ngx_http_limit_conn_module

Solution 2:

You can safely use named locations, such as "@location" in an if() block.

See: http://wiki.nginx.org/IfIsEvil

Something like this should work:

http {

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

   server {
      ...

      error_page 410 = @slowdown;

      if( $remote_addr != "1.2.3.4" ) {
         return 410;
      }

      location @slowdown {
         limit_req zone=delay burst 5;
         ...
      }

      location / {
         ...
      }
   }

Fill in "location @slowdown { }" with the same information as "location / { }, such as proxy_pass if you're using nginx as a reverse proxy.