We are trying to implement a request rate limit on our Django NGINX server. I went through some articles about how to do this, such as https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-http/.
I can see that there's the ability to put a specific rate limit on an IP address, and also on a specific "location", that is an API endpoint. But is it also possible to group together some IP addresses and apply a cumulative rate limit.
For example, we have a partner that has 5 IP addresses and we want to apply a rate limit of let's say 10 rps on a specific API (/users/), but this 10 rps is a cumulative limit on all the 5 IP addresses, and NOT 10 rps for EACH IP address. I couldn't find on the official NGINX docs or anywhere on Google if this is possible or not.
For more clarity, let's consider these examples
123.123.123.123 - /users - 10 rps
123.123.123.123 - /products - 15 rps
45.45.45.45, 65.65.65.65 - /users - 20 rps
45.45.45.45, 65.65.65.65 - /products - 30 rps
So API requests to /users endpoint from IP 123.123.123.123 should be limited to 10 rps, and /products endpoint from the same IP should have a limit of 15 rps.
API requests to /users endpoint from both 45.45.45.45 and 65.65.65.65 COMBINED should be limited to 20 rps.
Similarly, for /products endpoint from both those IPs should be limited to 30 rps.
Any other IP will have let's say a default rate limit of 5 rps, no matter what API endpoint is called.
Hope this clears up any confusion! Thanks!
Your conditions are quite complicated. If, by example, requirements would be to apply a fixed 10 rps rate for the group of 5 IP addresses, and the same 10 rps rate for every other IP, you could do this with a single limit_req zone:
limit_req_zone $users_key zone=users:10m rate=10r/s;
geo $users_key {
<ip1> unique_customer_id;
<ip2> unique_customer_id;
<ip3> unique_customer_id;
<ip4> unique_customer_id;
<ip5> unique_customer_id;
default $binary_remote_addr;
}
server {
...
location /users/ {
limit_req zone=users;
... proxy request to the backend
}
}
That unique_customer_id can be any string that won't be equal to any possible $binary_remote_addr value (which is a binary-packed string of 4 or 16 bytes length, so using something as simple as 1 will be enough; don't use a long id string to minimize the shared zone memory footprint).
However since your requirements are more complicated, including different rates for different users, we need to declare many different zones to achieve the result you want. The solution is based upon a fact that an empty zone key won't be accounted to limit requests with a single limit_req rule.
Here is the possible solution.
# zones definitions
limit_req_zone $unknown_customer zone=users_5:10m rate=5r/s;
limit_req_zone $customer_limited_group_1 zone=users_10:1m rate=10r/s;
limit_req_zone $customer_limited_group_2 zone=users_20:1m rate=20r/s;
limit_req_zone $unknown_customer zone=products_5:10m rate=5r/s;
limit_req_zone $customer_limited_group_1 zone=products_15:1m rate=15r/s;
limit_req_zone $customer_limited_group_2 zone=products_30:1m rate=30r/s;
# calculating keys
map $remote_addr $unknown_customer {
123.123.123.123 '';
45.45.45.45 '';
65.65.65.65 '';
# exclude any other IP for customers with different rates here, e.g.
# 124.124.124.124 '';
# 85.85.85.85 '';
default $binary_remote_addr;
}
map $remote_addr $customer_limited_group_1 {
123.123.123.123 1;
# here you can add another customers limited to 10/15 rps with another id, e.g.
# 124.124.124.124 2;
# the default value will be an empty string
}
map $remote_addr $customer_limited_group_2 {
# this will be a key for a different zone;
# value '1' does not interfere with the '1' from the previous map block in any way
45.45.45.45 1;
65.65.65.65 1;
# another customers limited to 20/30 rps can be added here with different ids, e.g.
# 85.85.85.85 2;
}
server {
...
location /users/ {
limit_req zone=users_5;
limit_req zone=users_10;
limit_req zone=users_20;
... proxy request to the backend
}
location /products/ {
limit_req zone=products_5;
limit_req zone=products_15;
limit_req zone=products_30;
... proxy request to the backend
}
}
Related
I would like to limit all the incoming traffic except for HEAD requests. We have implemented a rate limit using Nginx, it is limiting all the traffics currently. But I want to exclude the HEAD requests from the rate limit.
Here is the code snippet used for the rate limit
http {
...
limit_req_zone $binary_remote_addr zone=ratelimit:50m rate=200r/s;
limit_req_status 429
...
...
server {
limit_req zone=ratelimit burst=400 nodelay;
}
...
}
According to the limit_req_zone directive documentation:
Requests with an empty key value are not accounted.
So just made zone key an empty string in case of HEAD request method:
http {
...
map $request_method $ratelimit_key {
HEAD '';
default $binary_remote_addr;
}
limit_req_zone $ratelimit_key zone=ratelimit:50m rate=200r/s;
limit_req_status 429;
...
I have nginx rate-limiting working when using the following
limit_req_zone $binary_remote_addr zone=mylimit:20m rate=50r/m;
I now want to apply it to certain IPs so i've changed it to
geo $limit {
default 1;
1.2.3.4/32 0;
}
map $limit $mylimit {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $my_limit zone=mylimit:20m rate=50r/m;
Following the example here https://www.nginx.com/blog/rate-limiting-nginx/
But the rate limit is ignored even when coming from a different IP than the one in the config
This is using nginx version: nginx/1.14.0 (Ubuntu)
In the server block I have
limit_req zone=mylimit burst=15 nodelay;
which was working before
I have a server in nginx configured and have the following code to create my rate limit zone:
limit_req_zone $key zone=six_zone:10m rate=60r/m;
In my location, I use a module to serve the requests. This location supports GET, POST and DELETE methods. I am trying to rate limit only GET requests to that location. This is what I thought might work but it does not.
location /api/ {
if ($request_method = GET) {
limit_req zone=six_zone;
}
reqfwder;
}
Any help or pointers towards how I can approach this? Thanks.
Hope this helps,
In the http context of your NGINX configuration, add these lines:
http {
... # your nginx.conf here
# Maps ip address to $limit variable if request is of type POST
map $request_method $limit {
default "";
POST $binary_remote_addr;
}
# Creates 10mb zone in memory for storing binary ips
limit_req_zone $limit zone=my_zone:10m rate=1r/s;
}
**Rate limiting for the entire NGINX process:**
http {
... # your nginx.conf here
limit_req zone=global_zone;
}
REF: https://product.reverb.com/first-line-of-defense-blocking-bad-post-requests-using-nginx-rate-limiting-507f4c6eed7b
I'm evaluating nginx to act as rate limiter for a multi tenancy REST API system. I need to limit API calls by tenant-id.
For example i want to allow 100 r/s for tenant1 and only 50 r/s for tenant2.
It can be easily achived when there are differant urls like: "me.com/tenant1/api" and "me.com/tenant2/api" (with the location directive).
But, in my case the urls are the same for all tenants "me.com/api" (I can't change this).
To find the tenant-id I need to extract a JSON attribute from the Body of the request, and then check the DB for the real tenant-id.
Is it possible to limit_req with my requirements?
Thank for the help!
I decided to build another service getTenant for parsing the body and extracting the Tenant from the DB. This service is called internally by Nginx.
I'm not sure if that is the best nginx (/openresty) solution, but this is what i came up with:
limit_req_zone t1Limit zone=t1Zone:10m rate=200r/s;
limit_req_zone t2Limit zone=t2Zone:10m rate=90r/s;
server {
location /api{
content_by_lua_block {
ngx.req.read_body();
local reqBody = ngx.req.get_body_data()
local res = ngx.location.capture("/getTenant", {method=ngx.HTTP_POST,body=reqBody});
local tenantId= res.body;
if tenantId== "none" then
ngx.log(ngx.ERR, "Tenant not found!");
ngx.say(tenantId);
else
ngx.req.set_header("x_myTenantId", tenantId)
local res2 = ngx.location.capture("/" .. tenantId .."/doApi", {method=ngx.HTTP_POST,body=reqBody});
if res2.status == ngx.HTTP_OK then
ngx.say(res2.body);
ngx.exit(res2.status);
else
ngx.status = res2.status
ngx.exit(res2.status)
end
end;
}
}
location /getTenant {
internal; #this is not accessible from outside.
proxy_pass http://UpStream1/getCustomer;
proxy_set_header X-Original-URI $request_uri;
}
location /tenant1/doApi {
internal; #this is not accessible from outside.
# Proxy all requests to the AReqUpStream server group
proxy_pass http://UpStream2/doApi;
limit_req zone=tenant1Zone burst=25;
limit_req_log_level notice;
}
location /tenant2/doApi {
internal; #this is not accessible from outside.
# Proxy all requests to the AReqUpStream server group
proxy_pass http://UpStream2/doApi;
limit_req zone=tenant2Zone burst=10 ;#nodelay;
limit_req_status 409;
limit_req_log_level notice;
}
}
Basically, when me.com/api is called, a new subrequest is issued to service /getTenant. The response of that call is used to build another subrequest call to the /tenant[X]/doApi service. That way i can define locations per tenant and provide different rate_limis to each.
Comments on that are more than welcome!
We try to save nginx resources by limiting the number of requests per second:
http {
limit_req_zone $binary_remote_addr zone=gulag:10m rate=2r/s;
server
{
location / {
proxy_pass http://0.0.0.0:8181;
limit_req zone=gulag burst=40;
}
}
}
However, most employees in our company are also heavy users of our own website. Since everyone in the company appear to come from the same ip address were getting 503 errors because nginx thinks all the traffic is coming from one user. Can we add our ip as an exception to the requests per second limit?
Yes, you can. Just a quote from the documentation:
The key is any non-empty value of the specified variable (empty values are not accounted).
So you can achieve your goal by using geo and map modules like this:
geo $limited_net {
default 1;
10.1.0.0/16 0;
}
map $limited_net $addr_to_limit {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $addr_to_limit zone=gulag:10m rate=2r/s;