Nginx: Bypass rate limiting with header

This answer is perfect dealing with bypassing rate limiting with IP addresses.

If I need to bypass rate limiting with a secret header, how do I achieve this?

Ref:

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     "";
    }
    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

Solution 1:

The usual reason for these questions is that most of these directives cannot be used from within the context of the if statement, hence, how would one be able to conditionally specify different limits?

The answer is to use intermediate variables — just as in the linked answer, use set the limits using variables, where, subsequently, the values of those variables would differ depending on a map or an if statement.

http {
    map $http_x_secret_header $limit {
        default      $binary_remote_addr;
        secretvalue  "";
    }
    limit_conn_zone      $limit    zone=connlimit:10m;
    …

Ref:

  • http://nginx.org/r/limit_conn_zone
  • http://nginx.org/r/map
  • http://nginx.org/r/set
  • http://nginx.org/r/if

Solution 2:

FWIIW, I've also looked at the other "weird" answer to the question you link — it was written in 2011, had only 3 upvotes earlier today in 2017, compared to 23 upvotes for the more recent answer circa 2014 that you quote. Perhaps somewhat surprisingly, the older ignored answer does actually work without any issues as well!

Here's my take at the full MVP config, fully tested:

server {
    listen 7461;
    error_page 429 = @slowdown;
    if ($http_x_secret_header != secret_value) {
        return 429;
    }
    location @slowdown {
        #limit_...
        return 200 "$uri: slowed down\n";
    }
    location / {
        return 200 "$uri: very fast\n";
    }
}

Here's the testing to show you that it all works, including the fact that the correct 200 OK code is returned:

%curl -H "X-Secret-Header: secret_value" localhost:7461/important/path/
/important/path/: very fast

%curl -H "X-Secret-Header: wrong_value" localhost:7461/important/path/
/important/path/: slowed down

%curl -v localhost:7461/important/path/ | & fgrep -e HTTP/ -e /important
> GET /important/path/ HTTP/1.1
< HTTP/1.1 200 OK
/important/path/: slowed down
%

So, yes, the error_page redirection does actually work, too!


Let me explain the rationale for the weird error_page answer — in nginx, you can do both external redirects (visible to the client), as well as internal redirects (done internally, without any intermediate replies to the client).

This internal vs. external difference is a very powerful concept, without which many cool tricks wouldn't be possible, since the configuration language is simple enough to limit the number of directives available from within an if statement, as well as not allow nested if statements.

So, instead, with the internal redirects, the $uri can be changed internally, causing your request to bounce around between multiple independent locations (think of each location as a state in a DFA (deterministic finite automaton)), until a desired result is achieved (for an extreme example of this, take a look at http://mdoc.su/, as seen at nginx.conf 2016).


To summarise, in the example above:

  • we repurpose the 429 error_page to act as an internal redirect to an internal location, and then process such internal location in the very same way as a non-internal location would have been processed, except for the addition of some additional variables or directives;

  • we also use the = parameter to error_page directive to instruct nginx to not actually send the 429 code back to the client, but instead let the further processing dictate what the final status code should be.