How to do Nginx:
rate limiting behind Cloudflare
on docker
docker-compose.yml
:
services:
n1:
image: nginx:alpine
volumes:
- ./n1.conf:/etc/nginx/conf.d/default.conf
ports:
- 8080:80
n2:
image: nginx:alpine
volumes:
- ./n2.conf:/etc/nginx/conf.d/default.conf
s:
build: .
command: perl server.pl
init: true
n1.conf
:
server {
location / {
proxy_pass http://n2;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
n2.conf
:
limit_req_zone $binary_remote_addr zone=one:10m rate=12r/m;
server {
real_ip_header X-Forwarded-For;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
real_ip_recursive on;
location /unlim {
return 200 "hello world (nginx)";
}
location / {
limit_req zone=one burst=5;
proxy_pass http://s;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # nginx < 1.9.7, it will add the overriden $remote_addr to X-Forwarded-For
proxy_set_header X-Forwarded-For "$http_x_forwarded_for, $realip_remote_addr"; # nginx >= 1.9.7
}
}
Dockerfile
:
FROM alpine:3.20
RUN apk add perl perl-http-daemon
COPY server.pl server.pl
server.pl
:
use strict;
use warnings;
use HTTP::Daemon;
$| = 1;
my $d = HTTP::Daemon->new(LocalPort => 80) or die;
while (my $c = $d->accept) {
while (my $r = $c->get_request) {
print $r->as_string;
$c->send_response(200, undef, [
'Content-Type' => 'text/html; charset=utf-8',
], "hello world (perl)\n");
}
$c->close;
}
2 /
requests from different end user IPs (both requests are served immediately):
$ curl -sSv localhost:8080; docker compose exec n1 curl -sSv localhost
s-1 | GET / HTTP/1.0
s-1 | Connection: close
s-1 | Accept: */*
s-1 | Host: s
s-1 | User-Agent: curl/8.10.1
s-1 | X-Forwarded-For: 192.168.0.1, 192.168.0.3
s-1 |
n2-1 | 192.168.0.1 - - [04/Oct/2024:03:32:39 +0000] "GET / HTTP/1.0" 200 19 "-" "curl/8.10.1" "192.168.0.1"
n1-1 | 192.168.0.1 - - [04/Oct/2024:03:32:39 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/8.10.1" "-"
s-1 | GET / HTTP/1.0
s-1 | Connection: close
s-1 | Accept: */*
s-1 | Host: s
s-1 | User-Agent: curl/7.88.1
s-1 | X-Forwarded-For: 127.0.0.1, 192.168.0.3
s-1 |
n2-1 | 127.0.0.1 - - [04/Oct/2024:03:32:39 +0000] "GET / HTTP/1.0" 200 19 "-" "curl/7.88.1" "127.0.0.1"
n1-1 | 127.0.0.1 - - [04/Oct/2024:03:32:39 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/7.88.1" "-"
2 /
requests from the same end user IP (the second request blocks):
$ curl -sSv localhost:8080; curl -sSv localhost:8080
s-1 | GET / HTTP/1.0
s-1 | Connection: close
s-1 | Accept: */*
s-1 | Host: s
s-1 | User-Agent: curl/8.10.1
s-1 | X-Forwarded-For: 192.168.0.1, 192.168.0.3
s-1 |
n2-1 | 192.168.0.1 - - [04/Oct/2024:03:32:58 +0000] "GET / HTTP/1.0" 200 19 "-" "curl/8.10.1" "192.168.0.1"
n1-1 | 192.168.0.1 - - [04/Oct/2024:03:32:58 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/8.10.1" "-"
n2-1 | 2024/10/04 03:32:58 [warn] 31#31: *7 delaying request, excess: 0.999, by zone "one", client: 192.168.0.1, server: , request: "GET / HTTP/1.0", host: "n2"
s-1 | GET / HTTP/1.0
s-1 | Connection: close
s-1 | Accept: */*
s-1 | Host: s
s-1 | User-Agent: curl/8.10.1
s-1 | X-Forwarded-For: 192.168.0.1, 192.168.0.3
s-1 |
n2-1 | 192.168.0.1 - - [04/Oct/2024:03:33:03 +0000] "GET / HTTP/1.0" 200 19 "-" "curl/8.10.1" "192.168.0.1"
n1-1 | 192.168.0.1 - - [04/Oct/2024:03:33:03 +0000] "GET / HTTP/1.1" 200 19 "-" "curl/8.10.1" "-"
2 /unlim
requests:
$ curl -sSv localhost:8080/unlim; curl -sSv localhost:8080/unlim
n1-1 | 192.168.0.1 - - [04/Oct/2024:03:33:14 +0000] "GET /unlim HTTP/1.1" 200 12 "-" "curl/8.10.1" "-"
n2-1 | 192.168.0.1 - - [04/Oct/2024:03:33:14 +0000] "GET /unlim HTTP/1.0" 200 12 "-" "curl/8.10.1" "192.168.0.1"
n2-1 | 192.168.0.1 - - [04/Oct/2024:03:33:14 +0000] "GET /unlim HTTP/1.0" 200 12 "-" "curl/8.10.1" "192.168.0.1"
n1-1 | 192.168.0.1 - - [04/Oct/2024:03:33:14 +0000] "GET /unlim HTTP/1.1" 200 12 "-" "curl/8.10.1" "-"
this logic can be applied to non-docker nginx also.