NGINX Rate Limit 에서 특정 IP를 Rate Limit 제외하는 방법
NGINX Rate Limit 을 사용하다 보면 테스트를 위해 Rate Limit에 지정되어있는 값보다 많은 요청을 하게될 수도있습니다. 테스트를 위해 Rate Limit을 해제하는 것보단 이 포스트의 방법을 사용하여 특정 IP의 Rate Limit을 제외하여 Rate Limit을 보다 유연하게 사용할 수 있습니다.
목차
1. ngx_http_limit_req_module
2. ngx_http_map_module
3. 특정 IP Rate Limit 제외 구현
3-1. Rate Limit 활성화
3-2. map을 이용한 IP 구별
4. 결론
1. ngx_http_limit_req_module
NGINX에서 요청의 속도를 제어하는데 사용되는 모듈입니다. 요청의 속도를 제한하여 서버의 자원 남용을 방지하고 DoS 공격에 대한 일부 방어를 제공합니다.
사용 예로는 다음과 같습니다.
limit_req_zone을 통해 요청을 제한할 Key와 zone(key에 대한 상태를 유지하는 공유메모리), 요청을 제한할 속도를 지정합니다.
limit_req를 통해 limit_req_zone에서 지정한 공유 메모리와 요청의 최대 버스트(최대 5개의 요청까지는 추가적으로 허용.) 크기를 지정합니다.
http {
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
...
server {
...
location / {
limit_req zone=one burst=5 nodelay;
}
2. ngx_http_map_module
NGINX에서 사용하는 모듈 중 하나로, 다양한 조건에 따라 값을 매핑하는 데 사용됩니다. 즉, 하나의 입력 값에 따라 다른 출력 값을 설정할 수 있도록 도와줍니다. 이 모듈을 통해 복잡한 조건부 로직을 간결하게 처리할 수 있습니다.
map $http_host $name {
hostnames;
default 0;
example.com 1;
*.example.com 1;
example.org 2;
*.example.org 2;
.example.net 3;
wap.* 4;
}
map $http_user_agent $mobile {
default 0;
"~Opera Mini" 1;
}
3. 특정 IP Rate Limit 제외 구현
3-1. Rate Limit 활성화
특정 IP Rate Limit 제외를 구현하기 전에 Rate Limit을 활성화 합니다.
# Rate Limit zone을 지정합니다.
limit_req_zone $remote_addr zone=limit:10m rate=1r/s;
# 현재 Rate Limit의 상태를 확인하기 위해 로그 포멧을 지정하였습니다.
log_format limited '{\n'
'\t "time" : $remote_user [$time_local]\n'
'\t "ip" : $remote_addr\n'
'\t "limit_status" : $limit_req_status \n'
'}\n';
server {
# 요청에 최대치에 도달할 시에 보여줄 http status code를 지정해주었습니다,
# 429 too many request
limit_req_status 429;
# 위에서 지정한 zone을 지정한 후 delay 없이 요청을 제한합니다.
limit_req zone=limit;
listen 80 default_server;
server_name localhost;
# Rate Limit 상태를 확인하기 위한 log를 지정합니다.
access_log /var/log/nginx/limited.log limited;
location / {
proxy_pass http://192.168.201.27:80;
}
}
위와 같이 구성하게 된다면 초당 1번의 요청만 받을 수 있고 초당 1번의 요청을 넘기게 된다면 아래와 같이 429 Too Many Requests가 나오게됩니다.

아래와 같이 허용된 요청은 PASSED Rate Limit 요청을 초과한 상태는 REJECTED로 나오게 됩니다.
# /var/log/nginx/limited.log
{
"time" : - [07/Oct/2024:06:38:38 +0000]
"ip" : 192.168.201.74
"limit_status" : PASSED
}
{
"time" : - [07/Oct/2024:06:38:38 +0000]
"ip" : 192.168.201.74
"limit_status" : REJECTED
}
{
"time" : - [07/Oct/2024:06:38:40 +0000]
"ip" : 192.168.201.74
"limit_status" : PASSED
}
{
"time" : - [07/Oct/2024:06:38:40 +0000]
"ip" : 192.168.201.74
"limit_status" : REJECTED
}
3-2. map을 이용한 IP 구별
아래와 같이 Rate Limit의 키를 map으로 만든 변수로 지정하여 사용합니다.
1 limit_req_zone $limit_key zone=limit:10m rate=1r/s;
2
3 map $remote_addr $limit_key {
4 default $remote_addr;
5 192.168.201.74 "";
6 }
7
8 log_format limited '{\n'
9 '\t "time" : $remote_user [$time_local]\n'
10 '\t "ip" : $remote_addr\n'
11 '\t "limit_status" : $limit_req_status \n'
12 '\t "limit_key" : $limit_key \n'
13 '}\n';
14
15 server {
16 limit_req_status 429;
17
18 limit_req zone=limit nodelay;
19 listen 80 default_server;
20 server_name localhost;
21
22 #access_log /var/log/nginx/host.access.log main;
23
24 access_log /var/log/nginx/limited.log limited;
25
26 location / {
27 proxy_pass http://192.168.201.27:80;
28 proxy_set_header X-Forwarded-For $remote_addr;
29 }
30
31 #error_page 404 /404.html;
32
33 # redirect server error pages to the static page /50x.html
34 #
35 error_page 500 502 503 504 /50x.html;
36 location = /50x.html {
37 root /usr/share/nginx/html;
38 }
39 }
1 : key를 map에 지정한 값을 사용합니다.
3~6 : remote_addr 값에따라 IP가 192.168.201.74일 때 rate limit의 key값을 공백으로 지정하여 Rate Limit에 제외되게 설정합니다. 192.168.201.74를 제외한 IP는 IP별 Rate Limit에 적용될 수 있도록 remote_addr을 key로 지정합니다.
12 : Rate Limit의 key가 무엇으로 잡혀있는지 로깅을 통해 확인할 수 있도록 지정하였습니다.
위와 같이 구성하게 된다면 map 지정된 특정 IP는 Rate Limit key를 공백으로 들어가기 때문에 Rate Limit이 걸리지 않게됩니다. (Rate Limit Key에 빈값이 들어가게 되면 Rate Limit이 적용되지 않습니다.)
지정된 IP로 들어가게되면 지정된 요청수에 넘기게 되어도 아래와 같이 프록시된 컨텐츠를 보여주게 됩니다.
# 192.168.201.74
$ curl 192.168.201.28
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ curl 192.168.201.28
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
로그를 확인해 보면 아래와 같이 나오게됩니다.
허용된 IP로 접속시 Rate Limit에 걸리지 않게 되고
limit_status(Rate Limit 상태)는 – 빈값으로 나오게되고
limit_key는 아래와 같이 공백으로 나오게됩니다.
# /var/log/nginx/limited.log
{
"time" : - [07/Oct/2024:07:03:48 +0000]
"ip" : 192.168.201.74
"limit_status" : -
"limit_key" :
}
{
"time" : - [07/Oct/2024:07:03:48 +0000]
"ip" : 192.168.201.74
"limit_status" : -
"limit_key" :
}
{
"time" : - [07/Oct/2024:07:03:49 +0000]
"ip" : 192.168.201.74
"limit_status" : -
"limit_key" :
}
{
"time" : - [07/Oct/2024:07:03:49 +0000]
"ip" : 192.168.201.74
"limit_status" : -
"limit_key" :
}
허용되지 않은 IP로 접속 시
$ curl 192.168.201.28
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
$ curl 192.168.201.28
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>nginx/1.25.5</center>
</body>
</html>
위와 같이 요청 제한에 걸리게 됩니다.
로그를 보게되면 아래와 같이 나옵니다.
허용되지 않은 IP이기 때문에 limit_status가 제대로 PASSED와 REJECTED가 나오는 것을 볼 수 있고 map에 지정된 IP가 아닌 경우 remote_addr를 따라가기 때문에 limit_key가 IP로 적혀있는 것을 볼 수 있습니다.
# 192.168.201.28
# /var/log/nginx/limited.log
{
"time" : - [07/Oct/2024:07:09:59 +0000]
"ip" : 192.168.201.27
"limit_status" : PASSED
"limit_key" : 192.168.201.27
}
{
"time" : - [07/Oct/2024:07:09:59 +0000]
"ip" : 192.168.201.27
"limit_status" : REJECTED
"limit_key" : 192.168.201.27
}
{
"time" : - [07/Oct/2024:07:09:59 +0000]
"ip" : 192.168.201.27
"limit_status" : PASSED
"limit_key" : 192.168.201.27
}
{
"time" : - [07/Oct/2024:07:10:00 +0000]
"ip" : 192.168.201.27
"limit_status" : REJECTED
"limit_key" : 192.168.201.27
}
{
"time" : - [07/Oct/2024:07:10:00 +0000]
"ip" : 192.168.201.27
"limit_status" : REJECTED
"limit_key" : 192.168.201.27
}
4. 결론
테스트 중에 Rate Limit을 해제하지 않고도 특정 IP에 대해 예외 처리를 통해 유연하게 관리하는 방법을 활용하는 것이 더 안전하고 효율적입니다. 이 방법을 사용하면 시스템의 안정성을 유지하면서도 테스트 목적으로 제한을 초과한 요청을 처리할 수 있습니다.
Rate Limit을 완전히 해제하는 것은 예상치 못한 과부하를 초래할 수 있으므로, 특정 IP에 대해 제한을 완화하는 방식이 더 적절합니다.
NGINX Plus를 직접 사용해 보시려면 30일 무료 평가판을 신청하거나 NGINX STORE에 연락하여 논의하십시오.
댓글을 달려면 로그인해야 합니다.