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 aninternal
redirect to aninternal
location
, and then process suchinternal
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 toerror_page
directive to instruct nginx to not actually send the429
code back to the client, but instead let the further processing dictate what the final status code should be.