요청 제한 NGINX 및 NGINX Plus로 구현하기
가장 유용하지만 종종 잘못 이해되고 잘못 구성되는 NGINX의 기능 중 하나는 요청 제한 입니다. 이를 통해 사용자가 주어진 시간 동안 받을 수 있는 HTTP 요청의 양을 제한할 수 있습니다. 요청은 웹사이트의 홈페이지에 대한 GET 요청이나 로그인 양식의 POST 요청만큼 간단할 수 있습니다.
속도 제한은 무차별 암호 대입 공격(brute‑force password‑guessing attacks)을 늦추는 것과 같은 보안 목적으로 사용할 수 있습니다. 들어오는 요청 속도를 실제 사용자에게 일반적인 값으로 제한하고 (로깅을 통해) 대상 URL을 식별하여 DDoS 공격으로부터 보호할 수 있습니다. 보다 일반적으로 업스트림 응용 프로그램 서버가 동시에 너무 많은 사용자 요청에 압도되지 않도록 보호하는 데 사용됩니다.
이 포스트에서는 NGINX를 사용한 요청 제한의 기본 사항과 고급 구성에 대해 설명합니다. 속도 제한은 NGINX Plus에서 동일한 방식으로 작동합니다.
NGINX Plus는 “전역 요청 제한(global rate limiting)”을 지원합니다. 클러스터의 NGINX Plus 인스턴스는 요청이 도착하는 클러스터의 인스턴스에 관계없이 들어오는 요청에 일관된 요청 제한을 적용합니다. (클러스터의 상태 공유는 다른 NGINX Plus 기능에서도 사용할 수 있습니다.)
목차
1. NGINX 요청 제한 작동 방식
2. 기본 요청 제한 구성
3. 버스트 처리(Handling Bursts)
4. 지연 없는 대기열
5. 2단계 요청 제한
6. 요청 제한을 위한 고급 구성 예
6-1. 허용 목록(Allowlisting)
6-2. 한 Location에 여러 limit_req 지시문 포함
7. 관련 기능 구성
7-1. Logging
7-2. 클라이언트에게 전송된 오류 코드
7-3. 특정 location에 대한 모든 요청 거부
8. 결론
1. NGINX 요청 제한 작동 방식
NGINX 요청 제한은 대역폭이 제한될 때 버스트(Burst)를 처리하기 위해 통신 및 패킷 교환 컴퓨터 네트워크에서 널리 사용되는 누출 버킷 알고리즘을 사용합니다. 비유는 물이 위에서 부어지고 아래에서 새는 양동이와 같습니다. 물을 붓는 속도가 새는 속도를 초과하면 양동이가 넘칩니다. 요청 처리 측면에서 물은 클라이언트의 요청을 나타내고 버킷은 FIFO(선입 선출) 스케줄링 알고리즘에 따라 요청이 처리되기를 기다리는 대기열을 나타냅니다. 누수되는 물은 서버가 처리하기 위해 버퍼를 나가는 요청을 나타내고, 오버플로(Overflow)는 폐기되고 서비스되지 않는 요청을 나타냅니다.

2. 기본 요청 제한 구성
요청 제한은 다음 예와 같이 두 가지 주요 지시문, limit_req_zone 및 limit_req로 구성됩니다.
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /login/ {
limit_req zone=mylimit;
proxy_pass http://my_upstream;
}
}
limit_req_zone 지시문은 요청 제한을 위한 매개변수를 정의하는 반면 limit_req는 나타나는 컨텍스트 내에서 요청 제한을 활성화합니다(예에서 /login/에 대한 모든 요청에 대해).
limit_req_zone 지시문은 일반적으로 http 블록에 정의되어 여러 컨텍스트에서 사용할 수 있습니다. 다음 세 가지 매개변수를 사용합니다.
- Key – 제한이 적용되는 요청 특성을 정의합니다. 이 예에서는 클라이언트 IP 주소의 이진 표현을 보유하는 NGINX 변수 $binary_remote_addr입니다. 이것은 우리가 각각의 고유한 IP 주소를 세 번째 매개변수에 의해 정의된 요청 비율로 제한한다는 것을 의미합니다. (이 변수는 클라이언트 IP 주소 $remote_addr의 문자열 표현보다 공간을 덜 차지하기 때문에 사용하고 있습니다.)
- Zone – 각 IP 주소의 상태와 요청 제한 URL에 액세스한 빈도를 저장하는 데 사용되는 공유 메모리 영역을 정의합니다. 정보를 공유 메모리에 보관한다는 것은 NGINX 작업자 프로세스 간에 정보를 공유할 수 있음을 의미합니다. 정의는 zone= 키워드로 식별되는 영역 이름과 콜론 다음에 오는 크기의 두 부분으로 구성됩니다. 약 16,000개의 IP 주소에 대한 상태 정보는 1메가바이트가 걸리므로 우리 영역은 약 160,000개의 주소를 저장할 수 있습니다. NGINX가 새 항목을 추가해야 할 때 스토리지가 고갈되면 가장 오래된 항목을 제거합니다. 사용 가능한 공간이 여전히 새 레코드를 수용하기에 충분하지 않은 경우 NGINX는 상태 코드 503(서비스를 일시적으로 사용할 수 없음)을 반환합니다. 또한 메모리가 고갈되는 것을 방지하기 위해 NGINX는 새 항목을 생성할 때마다 이전 60초 동안 사용되지 않은 항목을 최대 2개까지 제거합니다.NGINX가 새 항목을 추가해야 할 때 스토리지가 고갈되면 가장 오래된 항목을 제거합니다. 사용 가능한 공간이 여전히 새 레코드를 수용하기에 충분하지 않은 경우 NGINX는 상태 코드 503(서비스를 일시적으로 사용할 수 없음)을 반환합니다. 또한 메모리가 고갈되는 것을 방지하기 위해 NGINX는 새 항목을 생성할 때마다 이전 60초 동안 사용되지 않은 항목을 최대 2개까지 제거합니다.
- Rate – 최대 요청 비율을 설정합니다. 예에서 속도는 초당 10개 요청을 초과할 수 없습니다. NGINX는 실제로 밀리초 단위로 요청을 추적하므로 이 제한은 100밀리초(ms)마다 요청 1개에 해당합니다. 버스트를 허용하지 않기 때문에(다음 섹션 참조) 이는 이전에 허용된 요청보다 100ms 이내에 도착한 요청이 거부되었음을 의미합니다.
limit_req_zone 지시문은 요청 제한 및 공유 메모리 영역에 대한 매개변수를 설정하지만 실제로 요청 속도를 제한하지는 않습니다. 이를 위해서는 limit_req 지시문을 포함하여 특정 위치 또는 server 블록에 제한을 적용해야 합니다. 이 예에서는 /login/에 대한 요청 속도를 제한하고 있습니다.
따라서 이제 각 고유 IP 주소는 /login/에 대해 초당 10개의 요청으로 제한됩니다. 더 정확하게는 이전 URL의 100ms 이내에 해당 URL을 요청할 수 없습니다.
3. 버스트 처리(Handling Bursts)
서로 100ms 이내에 2개의 요청을 받으면 어떻게 될까요? 두 번째 요청의 경우 NGINX는 상태 코드 503을 클라이언트에 반환합니다. 애플리케이션은 본질적으로 폭주하는 경향이 있기 때문에 이것은 아마도 우리가 원하는 것이 아닐 것입니다. 대신 우리는 초과 요청을 버퍼링하고 적시에 서비스하기를 원합니다. 여기에서 이 업데이트된 구성에서와 같이 limit_req에 burst 매개변수를 사용합니다.
location /login/ {
limit_req zone=mylimit burst=20;
proxy_pass http://my_upstream;
}
burst 매개변수는 클라이언트가 영역에 지정된 속도를 초과하여 만들 수 있는 요청 수를 정의합니다(샘플 mylimit 영역의 경우 요청 제한은 초당 10개 요청 또는 100ms마다 1개). 이전 요청이 대기열에 들어간 후 100ms보다 빨리 도착한 요청은 여기에서 대기열 크기를 20으로 설정합니다.
즉, 주어진 IP 주소에서 동시에 21개의 요청이 도착하면 NGINX는 첫 번째 요청을 즉시 업스트림 서버 그룹으로 전달하고 나머지 20개를 대기열에 넣습니다. 그런 다음 100ms마다 대기열에 있는 요청을 전달하고 들어오는 요청으로 인해 대기열에 있는 요청 수가 20을 초과하는 경우에만 503을 클라이언트에 반환합니다.
4. 지연 없는 대기열
burst가 있는 구성은 트래픽 흐름이 원활하게 이루어지지만 사이트가 느려질 수 있으므로 실용적이지 않습니다. 이 예에서 대기열의 20번째 패킷은 전달되기 위해 2초를 기다리며, 이 시점에서 이에 대한 응답은 클라이언트에게 더 이상 유용하지 않을 수 있습니다. 이 상황을 해결하려면 burst 매개변수와 함께 nodelay 매개변수를 추가하십시오.
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://my_upstream;
}
nodelay 매개변수를 사용하면 NGINX는 여전히 burst 매개변수에 따라 대기열에 슬롯을 할당하고 구성된 요청 제한을 부과하지만 대기 중인 요청의 전달에 간격을 두지 않습니다. 대신, 요청이 “너무 빨리” 도착하면 NGINX는 대기열에 사용 가능한 슬롯이 있는 한 즉시 요청을 전달합니다. 해당 슬롯을 “사용됨”으로 표시하고 적절한 시간이 경과할 때까지(이 예에서는 100ms 후) 다른 요청에서 사용할 수 있도록 해제하지 않습니다.
이전과 마찬가지로 20개 슬롯 대기열이 비어 있고 주어진 IP 주소에서 21개의 요청이 동시에 도착한다고 가정합니다. NGINX는 21개의 모든 요청을 즉시 전달하고 대기열의 20개 슬롯을 사용된 것으로 표시한 다음 100ms마다 1개의 슬롯을 해제합니다. (대신 25개의 요청이 있는 경우 NGINX는 그 중 21개를 즉시 전달하고 20개의 슬롯을 사용된 것으로 표시하고 상태 503인 4개의 요청을 거부합니다.)
이제 첫 번째 요청 세트가 전달된 후 101ms 후에 다른 20개의 요청이 동시에 도착한다고 가정합니다. 대기열에서 1개의 슬롯만 해제되었으므로 NGINX는 1개의 요청을 전달하고 상태가 503인 다른 19개를 거부합니다. 대신 20개의 새 요청이 도착하기 전에 501ms가 경과하면 5개의 슬롯이 사용 가능하므로 NGINX는 즉시 5개의 요청을 전달하고 15개를 거부합니다.
효과는 초당 10개 요청의 비율 제한과 같습니다. nodelay 옵션은 요청 사이에 허용된 간격을 제한하지 않고 요청 제한을 적용하려는 경우에 유용합니다.
참고: 대부분의 배포에서는 limit_req 지시문에 burst 및 nodelay 매개변수를 포함하는 것이 좋습니다.
5. 2단계 요청 제한
NGINX Plus R17 또는 NGINX 오픈소스 1.15.7 이상의 버전을 사용하면 일반적인 웹 브라우저 요청 패턴을 수용할 수 있도록 요청 burst를 허용하도록 NGINX를 구성한 다음 추가 과도한 요청이 거부되는 지점까지 추가 초과 요청을 제한할 수 있습니다. 2단계 요청 제한은 limit_req 지시문에 대한 지연 매개변수로 활성화됩니다.
2단계 요청 제한을 설명하기 위해 여기에서는 초당 5개 요청(r/s)의 요청 제한을 적용하여 웹 사이트를 보호하도록 NGINX를 구성합니다. 웹 사이트에는 일반적으로 페이지당 4-6개의 리소스가 있으며 12개를 넘지 않습니다. 구성은 최대 12개의 요청 burst를 허용하며 그 중 처음 8개는 지연 없이 처리됩니다. 5r/s 제한을 적용하기 위해 8번의 과도한 요청 후에 지연이 추가됩니다. 12번의 초과 요청 후에는 추가 요청이 거부됩니다.
limit_req_zone $binary_remote_addr zone=ip:10m rate=5r/s;
server {
listen 80;
location / {
limit_req zone=ip burst=12 delay=8;
proxy_pass http://website;
}
}
지연 매개변수는 버스트 크기 내에서 정의된 요청 제한을 준수하기 위해 과도한 요청이 제한(지연)되는 지점을 정의합니다. 이 구성을 사용하여 8 r/s에서 연속적인 요청 스트림을 만드는 클라이언트는 다음 동작을 경험합니다.

처음 8개 요청(지연 값)은 NGINX Plus에서 지연 없이 프록시됩니다. 다음 4개 요청(burst – delay)은 5r/s의 정의된 속도를 초과하지 않도록 지연됩니다. 다음 3개 요청은 총 burst 크기를 초과했기 때문에 거부되었습니다. 후속 요청이 지연됩니다.
6. 요청 제한 을 위한 고급 구성 예
기본 요청 제한을 다른 NGINX 기능과 결합하여 보다 미묘한 트래픽 제한을 구현할 수 있습니다.
6-1. 허용 목록(Allowlisting)
이 예는 “허용 목록(Allowlisting)”에 없는 사람의 요청에 대해 요청 제한을 적용하는 방법을 보여줍니다.
geo $limit {
default 1;
10.0.0.0/8 0;
192.168.0.0/24 0;
}
map $limit $limit_key {
0 "";
1 $binary_remote_addr;
}
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
server {
location / {
limit_req zone=req_zone burst=10 nodelay;
# ...
}
}
이 예에서는 geo 및 map 지시문을 모두 사용합니다. geo 블록은 허용 목록(allowlist)의 IP 주소에 대해 0에서 $limit 값을 할당하고 다른 모든 주소에 대해 1을 할당합니다. 그런 다음 맵(map)을 사용하여 다음과 같이 해당 값(value)을 키(key)로 변환합니다.
- $limit가 0이면 $limit_key는 빈 문자열로 설정됩니다.
- $limit가 1이면 $limit_key는 바이너리 형식의 클라이언트 IP 주소로 설정됩니다.
두 가지를 합치면 $limit_key는 허용 목록(allowlist)에 있는 IP 주소의 경우 빈 문자열로 설정되고 그렇지 않은 경우 클라이언트의 IP 주소로 설정됩니다. limit_req_zone 디렉토리(키)에 대한 첫 번째 매개변수가 빈 문자열이면 제한이 적용되지 않으므로 허용 목록에 있는 IP 주소(10.0.0.0/8 및 192.168.0.0/24 서브넷)는 제한되지 않습니다. 다른 모든 IP 주소는 초당 5개의 요청으로 제한됩니다.
limit_req 지시문은 / location에 제한을 적용하고 전달 지연 없이 구성된 제한을 초과하여 최대 10개의 패킷 burst를 허용합니다.
6-2. 한 Location에 여러 limit_req 지시문 포함
한 Location에 여러 limit_req 지시문을 포함할 수 있습니다. 지정된 요청과 일치하는 모든 제한이 적용됩니다. 즉, 가장 제한적인 제한이 사용됩니다. 예를 들어, 둘 이상의 지시문이 지연을 부과하는 경우 가장 긴 지연이 사용됩니다. 마찬가지로, 다른 지시문이 허용하더라도 요청이 지시의 영향인 경우에는 요청이 거부됩니다.
이전 예를 확장하여 허용 목록의 IP 주소에 요청 제한을 적용할 수 있습니다.
http {
# ...
limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;
server {
# ...
location / {
limit_req zone=req_zone burst=10 nodelay;
limit_req zone=req_zone_wl burst=20 nodelay;
# ...
}
}
}
허용 목록의 IP 주소는 첫 번째 속도 제한(req_zone)과 일치하지 않지만 두 번째(req_zone_wl)와 일치하므로 초당 15개 요청으로 제한됩니다. 허용 목록에 없는 IP 주소는 두 속도 제한 모두와 일치하므로 더 제한적인 제한이 적용됩니다(초당 5개 요청).
7. 관련 기능 구성
7-1. Logging
기본적으로 NGINX는 다음 예와 같이 요청 제한으로 인해 지연되거나 삭제된 요청을 기록합니다.
2022/10/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginxstore.com, request: "GET / HTTP/1.0", host: "nginxstore.com"
로그 항목의 필드는 다음과 같습니다.
- 2022/10/13 04:20:00 – 로그 항목이 작성된 날짜 및 시간
- [error] – 심각도 수준
- 120315#0 – # 기호로 구분된 NGINX 작업자의 프로세스 ID 및 스레드 ID
- *32086 – 속도가 제한된 프록시 연결의 ID
- limiting requests – 로그 항목이 속도 제한을 기록함을 나타내는 표시기
- excess – 이 요청이 나타내는 구성된 속도를 초과하는 밀리초당 요청 수
- zone – 부과된 속도 제한을 정의하는 영역
- client – 요청하는 클라이언트의 IP 주소
- server – 서버의 IP 주소 또는 호스트 이름
- request – 클라이언트가 만든 실제 HTTP 요청
- host – 호스트 HTTP 헤더의 값
기본적으로 NGINX는 위의 예에서 [error]로 표시된 것처럼 오류 수준에서 거부된 요청을 기록합니다. (지연된 요청은 한 단계 낮은 수준에서 기록하므로 기본적으로 경고합니다.) 기록 수준을 변경하려면 limit_req_log_level 지시문을 사용합니다. 여기에서 경고 수준에서 기록하는 거부된 요청을 설정합니다.
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_log_level warn;
proxy_pass http://my_upstream;
}
7-2. 클라이언트에게 전송된 오류 코드
기본적으로 NGINX는 클라이언트가 속도 제한을 초과할 때 상태 코드 503(서비스를 일시적으로 사용할 수 없음)으로 응답합니다. limit_req_status 지시문을 사용하여 다른 상태 코드(이 예에서는 444)를 설정합니다.
location /login/ {
limit_req zone=mylimit burst=20 nodelay;
limit_req_status 444;
}
7-3. 특정 location에 대한 모든 요청 거부
특정 URL에 대한 모든 요청을 제한하는 대신 거부하려면 해당 URL에 대한 location 블록을 구성하고 모두 거부 지시문을 포함하세요.
location /foo.php {
deny all;
}
8. 결론
우리는 HTTP 요청의 다른 location에 대한 요청 제한(rate limting) 설정, burst 및 nodelay 매개변수와 같은 속도 제한에 대한 추가 기능 구성을 포함하여 NGINX 및 NGINX Plus가 제공하는 속도 제한의 많은 기능을 다뤘습니다. 또한 허용 목록 및 거부 목록에 있는 클라이언트 IP 주소에 대해 서로 다른 제한을 적용하기 위한 고급 구성을 다루었고 거부 및 지연된 요청을 기록하는 방법을 설명했습니다.
NGINX Plus의 요청 제한을 직접 사용해 보십시오. 지금 무료 30일 평가판을 시작하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.
댓글을 달려면 로그인해야 합니다.