Multiple limit_req_zone with geo and map in nginx - nginx
I need help in defining rate limiting in nginx usging map and geo modules.
I've defined 4 cases with. Each of these cases should have a different rate limit set :
geo $limited_ip {
default 0;
1.1.1.1/24 1;
2.2.2.2/24 2;
3.3.3.3/24 3;
}
I use map module to transfer the ip value of the client to a variable :
map $limited_ip $limited_ip_key {
0 '';
1 $binary_remote_addr;
2 $binary_remote_addr;
3 $binary_remote_addr;
}
Now, I set 4 limit zones. The last zone is for testing :
limit_req_zone $limited_ip_key zone=zone0:10m rate=100r/m;
limit_req_zone $limited_ip_key zone=zone1:10m rate=200r/m;
limit_req_zone $limited_ip_key zone=zone2:10m rate=500r/m;
limit_req_zone $limited_ip_key zone=zone3:10m rate=1r/m;
Finally, I apply the limits in the server{} block :
limit_req zone=zone0 burst=10 nodelay;
limit_req zone=zone1 burst=10 nodelay;
limit_req zone=zone2 burst=10 nodelay;
limit_req zone=zone3 burst=1 nodelay;
The configuration test if ok, I reload nginx, ok too. Using apache bench tool (ab) to hammer the nginx server, it looks like zone3 always match for any source IP. Why do IP from other masks defines by geo module matche zone3 ?
Log :
*2757 limiting requests, excess: 1.697 by zone "zone3", client: 3.3.3.3, server: my.domain.com, request: "HEAD / HTTP/1.0", host: "my.domain.com"
*29449 limiting requests, excess: 1.958 by zone "zone3", client: 2.2.2.2, server: my.domain.com, request: "HEAD / HTTP/2.0", host: "my.domain.com"
All results I found are about 2 zones defined, I can't find examples with more zones. Maybe it's not possible to do it this way ?
Thank you
I was searching for the same configuration today and could not find an example with more than 2 zones too. So here is what in theory should work (I have not yet tried it out) :
geo $limited_ip {
default 0;
1.1.1.1/24 1;
2.2.2.2/24 2;
3.3.3.3/24 3;
}
map $limited_ip $limited_ip_key0 {
0 $binary_remote_addr;
1 '';
2 '';
3 '';
}
map $limited_ip $limited_ip_key1 {
0 '';
1 $binary_remote_addr;
2 '';
3 '';
}
map $limited_ip $limited_ip_key2 {
0 '';
1 '';
2 $binary_remote_addr;
3 '';
}
map $limited_ip $limited_ip_key3 {
0 '';
1 '';
2 '';
3 $binary_remote_addr;
}
limit_req_zone $limited_ip_key0 zone=zone0:10m rate=100r/m;
limit_req_zone $limited_ip_key1 zone=zone1:10m rate=200r/m;
limit_req_zone $limited_ip_key2 zone=zone2:10m rate=500r/m;
limit_req_zone $limited_ip_key3 zone=zone3:10m rate=1r/m;
limit_req zone=zone0 burst=10 nodelay;
limit_req zone=zone1 burst=10 nodelay;
limit_req zone=zone2 burst=10 nodelay;
limit_req zone=zone3 burst=1 nodelay;
Explanation:
a) if IP is none of the 3 defined then
$limited_ip_key0 = $binary_remote_addr
$limited_ip_key1 = ''
$limited_ip_key2 = ''
$limited_ip_key3 = ''
thus the zone0 will be matched only, and the rate limit of 100r/m will be applied
b) if IP is 1.1.1.1/24
$limited_ip_key0 = ''
$limited_ip_key1 = $binary_remote_addr
$limited_ip_key2 = ''
$limited_ip_key3 = ''
thus the zone1 will be matched only, and the rate limit of 200r/m will be applied
c) if IP is 2.2.2.2/24
$limited_ip_key0 = ''
$limited_ip_key1 = ''
$limited_ip_key2 = $binary_remote_addr
$limited_ip_key3 = ''
thus the zone2 will be matched only, and the rate limit of 500r/m will be applied
d) if IP is 3.3.3.3/24
$limited_ip_key0 = ''
$limited_ip_key1 = ''
$limited_ip_key2 = ''
$limited_ip_key3 = $binary_remote_addr
thus the zone3 will be matched only, and the rate limit of 1r/m will be applied
Here is a php script to automatically create the required zones so you can be able to administer more zones and ips easily
<?php
list($mapString, $zonesString, $zonesArray) = createZones(
"myendpoint",
array(
"default"=>array(
"zoneSize"=>"10m",
"rate"=>"10r/s",
"burst"=>"100",
"options"=>""
),
"zone1"=>array(
"ips"=>array(
"11.11.11.11/32",
"1.1.1.1/24"
),
"zoneSize"=>"10m",
"rate"=>"10r/s",
"burst"=>"100",
"options"=>""
),
"zone2"=>array(
"ips"=>array(
"22.22.22.22/32",
"2.2.2.2/24"
),
"zoneSize"=>"10m",
"rate"=>"20r/s",
"burst"=>"100",
"options"=>"nodelay"
),
"zone3"=>array(
"ips"=>array(
"33.33.33.33/32",
"3.3.3.3/24"
),
"zoneSize"=>"10m",
"rate"=>"30r/s",
"burst"=>"100",
"options"=>""
),
)
);
echo "# Define the ips and maps\n$mapString\n#Define the zones\n$zonesString\n\n";
echo "\t# limit directives to be placed inside the location section\n";
foreach ($zonesArray as $zoneName=>$zoneString) {
echo "\t$zoneString\n";
}
function createZones($endpointPrefix,$zones) {
$mapString0='geo $'.$endpointPrefix.' {'."\n\t\tdefault\t0;";
$mapString1='';
$zonesString='';
$zonesArray=array();
$zoneNum=0;
foreach ($zones as $zoneName=>$params) {
$zoneName=strtolower($zoneName);
if ($zoneName!='default') {
$zoneNum++;
foreach($params['ips'] as $ip) {
$mapString0.="\n\t\t$ip\t$zoneNum;";
}
}
}
$mapString0.="\n}\n";
$zoneNumTotal=$zoneNum;
// now that we now the total zones we can create the maps
$zoneNum=0;
foreach ($zones as $zoneName=>$params) {
$zoneName=strtolower($zoneName);
$mapString1.='map $'.$endpointPrefix.' $'.$endpointPrefix.'_key_'.$zoneName.' {';
for($zoneNumTemp=0;$zoneNumTemp<=$zoneNumTotal;$zoneNumTemp++) {
if ($zoneNum==$zoneNumTemp) {
$mapString1.="\n\t\t$zoneNumTemp\t\$binary_remote_addr;";
} else {
$mapString1.="\n\t\t$zoneNumTemp\t'';";
}
}
$mapString1.="\n}\n\n";
$zoneNum++;
}
// now create the actual zones string
foreach ($zones as $zoneName=>$params) {
$zoneName=strtolower($zoneName);
$zonesString.="\n";
if ( isset( $params['ips']) ) {
foreach ($params['ips'] as $ip) {
$zonesString .= "# $ip\n";
}
}
$zonesString.='limit_req_zone $'.$endpointPrefix.'_key_'.$zoneName.' zone='.$endpointPrefix.'_'.$zoneName.':'.$params['zoneSize'].' rate='.$params['rate'].';';
$zonesString.="\n";
}
// now create the limits that should be applied inside the location sections
foreach ($zones as $zoneName=>$params) {
$zoneName=strtolower($zoneName);
$zonesArray[$zoneName]='limit_req zone='.$endpointPrefix.'_'.$zoneName;
if ($params['burst']) {
$zonesArray[$zoneName].=' burst='.$params['burst'];
}
if ($params['options']) {
$zonesArray[$zoneName].=' '.$params['options'];
}
}
return array($mapString0."\n".$mapString1, $zonesString,$zonesArray);
}
When executed the above script produces:
# Define the ips and maps
geo $myendpoint {
default 0;
11.11.11.11/32 1;
1.1.1.1/24 1;
22.22.22.22/32 2;
2.2.2.2/24 2;
33.33.33.33/32 3;
3.3.3.3/24 3;
}
map $myendpoint $myendpoint_key_default {
0 $binary_remote_addr;
1 '';
2 '';
3 '';
}
map $myendpoint $myendpoint_key_zone1 {
0 '';
1 $binary_remote_addr;
2 '';
3 '';
}
map $myendpoint $myendpoint_key_zone2 {
0 '';
1 '';
2 $binary_remote_addr;
3 '';
}
map $myendpoint $myendpoint_key_zone3 {
0 '';
1 '';
2 '';
3 $binary_remote_addr;
}
#Define the zones
limit_req_zone $myendpoint_key_default zone=myendpoint_default:10m rate=10r/s;
# 11.11.11.11/32
# 1.1.1.1/24
limit_req_zone $myendpoint_key_zone1 zone=myendpoint_zone1:10m rate=10r/s;
# 22.22.22.22/32
# 2.2.2.2/24
limit_req_zone $myendpoint_key_zone2 zone=myendpoint_zone2:10m rate=20r/s;
# 33.33.33.33/32
# 3.3.3.3/24
limit_req_zone $myendpoint_key_zone3 zone=myendpoint_zone3:10m rate=30r/s;
# limit directives to be placed inside the location section
limit_req zone=myendpoint_default burst=100
limit_req zone=myendpoint_zone1 burst=100
limit_req zone=myendpoint_zone2 burst=100 nodelay
limit_req zone=myendpoint_zone3 burst=100
Related
prometheus-nginxlog-exporter dosn't show correct statistics
i've just started to work with prometheus-nginxlog-exporter https://github.com/martin-helmich/prometheus-nginxlog-exporter my nginx.conf has only one change log_format custom '$remote_addr - $remote_user [$time_local] $request_method "$request_uri " $status'; access_log /var/log/nginx/access.log custom; I have two simple site server { listen 82; server_name localhost; access_log /var/log/nginx/access_default_site.log custom; location / { root /usr/share/nginx/html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } upstream prom { server 127.0.0.1:9090; keepalive 15; } server { listen 80; location / { auth_basic "Restricted Access"; auth_basic_user_file /etc/nginx/htpasswd.prom; proxy_pass http://prom; proxy_redirect off; proxy_buffering off; proxy_http_version 1.1; proxy_set_header Connection "Keep-Alive"; proxy_set_header Proxy-Connection "Keep-Alive"; } } etc/prometheus-nginxlog-exporter.hcl listen { port = 4040 } namespace "nginx" { source = { files = [ "/var/log/nginx/access.log" ] } metrics_override = { prefix = "allnginx" } namespace_label = "vhost" format = "$remote_addr - $remote_user [$time_local] $request_method \"$request_uri\" $status" labels { app = "prod" } } namespace "default" { source = { files = [ "/var/log/nginx/access_default_site.log" ] } metrics_override = { prefix = "allnginx" } namespace_label = "vhost" format = "$remote_addr - $remote_user [$time_local] $request_method \"$request_uri\" $status" labels { app = "test" } } if check prometheus metrics curl http://localhost:4040/metrics output is # HELP allnginx_http_response_count_total Amount of processed HTTP requests # TYPE allnginx_http_response_count_total counter allnginx_http_response_count_total{app="prod",method="",status="200",vhost="nginx"} 28 allnginx_http_response_count_total{app="prod",method="",status="302",vhost="nginx"} 7 allnginx_http_response_count_total{app="prod",method="",status="400",vhost="nginx"} 30 # HELP allnginx_parse_errors_total Total number of log file lines that could not be parsed # TYPE allnginx_parse_errors_total counter allnginx_parse_errors_total{vhost="default"} 0 allnginx_parse_errors_total{vhost="nginx"} 0 allnginx_http_response_count_total{app="prod",method="",status="200",vhost="nginx"} 28 Why is method empty? How to configure it correct? How to get statistics about requests to location e.g. how many requests to "/", "/api"
as mentions in documentations https://github.com/martin-helmich/prometheus-nginxlog-exporter#custom-labels-pass-through we should you relabel my working config is listen { port = 4040 } namespace "nginx" { source = { files = [ "/var/log/nginx/access.log" ] } metrics_override = { prefix = "allnginx" } namespace_label = "vhost" format = "$remote_addr - $remote_user [$time_local] $request_method \"$request_uri\" $status" relabel "method" { from = "request_method" } relabel "request_uri" { from = "request_uri" } labels { app = "prod" } } namespace "default" { source = { files = [ "/var/log/nginx/access_default_site.log" ] } metrics_override = { prefix = "allnginx" } namespace_label = "vhost" format = "$remote_addr - $remote_user [$time_local] $request_method \"$request_uri\" $status" relabel "method" { from = "request_method" } relabel "request_uri" { from = "request_uri" } labels { app = "test" } } in metrics we can see somthing like this allnginx_http_response_count_total{app="prod", instance="localhost:4040", job="nginx", method="DELETE", request_uri="/api/dashboards/uid/GLEY_3g7k", status="200", vhost="nginx"} 1 allnginx_http_response_count_total{app="prod", instance="localhost:4040", job="nginx", method="GET", request_uri="/", status="302", vhost="nginx"} 1 allnginx_http_response_count_total{app="prod", instance="localhost:4040", job="nginx", method="GET", request_uri="/", status="401", vhost="nginx"} 2 allnginx_http_response_count_total{app="prod", instance="localhost:4040", job="nginx", method="GET", request_uri="/api/alerts/states-for-dashboard?dashboardId=2", status="200", vhost="nginx"} 1 allnginx_http_response_count_total{app="prod", instance="localhost:4040", job="nginx", method="GET", request_uri="/api/alerts/states-for-dashboard?dashboardId=3", status="200", vhost="nginx"}
nginx + upstreams and switch upstreams by arg from url
I searched many forums , found many similar topics, but none works for me( I have this configuration: upstream 8083 { server 127.0.0.1:8083; } upstream 8084 { server 127.0.0.1:8084; } split_clients "upstream${remote_addr}" $default { 50% 8083; 50% 8084; } map $arg_upstream $upstream { default $default; "8083" "8083"; "8084" "8084"; } location / { if ($arg_upstream = "8083") { proxy_pass http://8083; break; } if ($arg_upstream = "8084") { proxy_pass http://8084; break; } proxy_pass http://$default; } But after going by url site/?upstream=8084 I have no switching to 8084 upstream. If I test my config by changing to: if ($arg_upstream = "8083") { return 200 "upstream 8083" } if ($arg_upstream = "8084") { return 200 "upstream 8084" } I see text perfectly like needed! Where am I going wrong? Thanks!
this is answer to my question, it was decided by map 'http_cookies': upstream default { ip_hash; server 127.0.0.1:8083; server 127.0.0.1:8084; } upstream 8083 { server 127.0.0.1:8083; } upstream 8084 { server 127.0.0.1:8084; } map $arg_upstream $upstream { '8083' '8083'; '8084' '8084'; default 'default'; } map $http_cookie $upstream_cookie { default ''; "~*upstream=(?<variable>[^;]+)" "$1"; } and location part: location / { if ($upstream_cookie) { set $upstream $upstream_cookie; } if ($arg_upstream) { add_header Set-Cookie upstream=$arg_upstream always; } proxy_pass http://$upstream; }
What is the priority level of multi prefix match in nginx
location ^~ /upload/images/ { return 200; } location ^~ /admin/ { return 300; } When I visit http://ip/admin/upload/images/pic.jpg , it return 300. How could I do to make http://ip/admin/upload/images/pic.jpg and http://ip/upload/images/pic.jpg and http://ip/anyother/upload/images/pic.jpg return 200
Using variables in body_filter_by_lua_block
I am new to nginx, and I need to return altered request and from my understanding it is made in body_filter_by_lua_block scope by setting ngx.arg[1]. This is a simple base example of my usecase: server { listen 80; server_name localhost; location / { body_filter_by_lua_block { ngx.arg[1] = '{ "Subject": "someval" }' ngx.arg[2] = true } } } My question is, how can "someval" be turned into a variable, i.e: server { listen 80; server_name localhost; location / { set $someval "hello"; body_filter_by_lua_block { ngx.arg[1] = '{ "Subject": "$someval" }' ngx.arg[2] = true } } }
Your nginx variable is available for get and set in the lua block with ngx.var.someval.
content-type related nginx expires header
Is it possible to set a content-type dependent expires header in nginx? I'm quite new to nginx and tried the following: location ~ /files\.php$ { ... if ($content_type = "text/css") { add_header X-TEST1 123; expires 7d; } if ($content_type = "image/png") { add_header X-TEST2 123; expires 30d; } if ($content_type = "application/javascript") { add_header X-TEST3 123; expires 1d; } #testing if ($content_type != "text/css") { add_header X-TEST4 abc; } #testing if ($content_type = text/css) { add_header X-TEST5 123; } } But the only header added is "X-TEST4" on all requests. I know about the other solution using file extension: location ~* \.(ico|css|js|gif|jp?g|png)\?[0-9]+$ But it's not applicable for my application.
I think the code should be in location / { } instead of location ~ /files\.php$ { }...
Have you tried $content_type ~= application/javascript Also, make sure to read this: http://wiki.nginx.org/IfIsEvil
If you have the lua module installed you could do something similar like this: server { location / {...} header_filter_by_lua_block { local cct = {} -- # cached content types cct["text/css"] = true cct["application/javascript"] = true cct["application/x-javascript"] = true cct["text/javascript"] = true cct["image/jpeg"] = true cct["image/png"] = true cct["application/vnd.ms-fontobject"] = true cct["application/font-woff"] = true cct["application/x-font-truetype"] = true cct["image/svg+xml"] = true cct["application/x-font-opentype"] = true if cct[ngx.header.content_type] ~= nil and ngx.header["expires"] == nil then local now = os.time() local expires = os.date("%a, %d-%b-%Y %H:%I:%S GMT", now+604800) -- # one week in seconds ngx.header["expires"] = expires end } } Responses with the specified content-type will now get an expires-header. When the response already has an expires-header the lua block will not touch the header.