본문으로 건너뛰기

Rate Limiting: 요청 속도 제한

Rate Limiting은 단위 시간당 처리할 요청 수를 제한해 DDoS, 무차별 대입 공격(Brute Force), 웹 스크래핑을 방어하는 핵심 보안 기능입니다.


Nginx Rate Limiting

Nginx는 limit_req(요청 속도)와 limit_conn(동시 연결 수) 두 가지 제한을 제공합니다.

limit_req_zone — 요청 속도 제한

# /etc/nginx/nginx.conf — http 블록

http {
# 요청 속도 제한 존 정의
# key: $binary_remote_addr = 클라이언트 IP (이진 형태, 메모리 효율적)
# zone=api_limit:10m = 존 이름:메모리 크기 (10MB ≈ 160,000개 IP)
# rate=10r/s = 초당 10 요청 허용

limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m; # 분당 5회
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;

# 한도 초과 시 반환 상태 코드 (기본: 503)
limit_req_status 429; # Too Many Requests
limit_conn_status 429;
}
server {
# 일반 페이지: IP당 초당 30 요청
location / {
limit_req zone=general burst=50 nodelay;
proxy_pass http://backend;
}

# API: IP당 초당 10 요청, 버스트 20 허용
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://backend;
}

# 로그인: IP당 분당 5회
location /api/auth/login {
limit_req zone=login_limit burst=3 nodelay;
proxy_pass http://backend;
}
}

burst와 nodelay 이해:

rate=10r/s  burst=20  nodelay 설정일 때:
─────────────────────────────────────────
순간 최대 20개 요청을 즉시 처리 (nodelay)
이후 초당 10개 속도로 처리
burst를 넘으면 429 반환

nodelay 없을 때: burst 내 요청을 rate 속도로 지연 처리 (큐잉)

limit_conn — 동시 연결 수 제한

http {
# 동시 연결 수 제한 존
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}

server {
location / {
# IP당 최대 10개 동시 연결
limit_conn conn_limit 10;
proxy_pass http://backend;
}

# 파일 다운로드: 동시 연결 + 대역폭 제한
location /downloads/ {
limit_conn conn_limit 3;
limit_rate 1m; # 연결당 1MB/s로 대역폭 제한
limit_rate_after 10m; # 처음 10MB는 무제한, 이후 제한
alias /var/www/downloads/;
}
}

화이트리스트 IP는 Rate Limit 제외

http {
# 내부망은 rate limit 제외
geo $limit {
default $binary_remote_addr;
127.0.0.1 ""; # 빈 문자열 → rate limit 적용 안 됨
10.0.0.0/8 "";
192.168.0.0/16 "";
}

limit_req_zone $limit zone=api_limit:10m rate=10r/s;
}

Apache Rate Limiting

mod_ratelimit

sudo a2enmod ratelimit
<Location "/api/">
# 전송 속도 제한 (바이트/초)
SetOutputFilter RATE_LIMIT
SetEnv rate-limit 400 # 400KB/s
</Location>

mod_evasive — HTTP Flood 방어

sudo apt install libapache2-mod-evasive
sudo a2enmod evasive
# /etc/apache2/mods-available/evasive.conf

<IfModule mod_evasive20.c>
# 동일 페이지 요청 임계값: 2초 내 같은 URI 요청 수
DOSPageCount 5
DOSSiteCount 50

# 임계값 초과 시 블랙리스트 유지 시간 (초)
DOSPageInterval 2
DOSSiteInterval 1
DOSBlockingPeriod 10

# 차단 시 이메일 알림
DOSEmailNotify admin@example.com

# 화이트리스트 IP
DOSWhitelist 127.0.0.1
DOSWhitelist 10.0.0.*

# 로그 디렉토리
DOSLogDir /var/log/mod_evasive
</IfModule>

Spring Boot에서 Rate Limiting

Nginx Rate Limiting은 모든 요청에 적용되지만, 애플리케이션 레벨에서도 추가로 구현할 수 있습니다.

// Bucket4j 라이브러리 활용 (의존성 추가 필요)
// implementation 'com.bucket4j:bucket4j-core:8.x.x'

@RestController
@RequestMapping("/api")
public class ApiController {

private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();

private Bucket getBucket(String ip) {
return buckets.computeIfAbsent(ip, k ->
Bucket.builder()
.addLimit(Bandwidth.classic(10, Refill.greedy(10, Duration.ofSeconds(1))))
.build()
);
}

@GetMapping("/products")
public ResponseEntity<?> getProducts(HttpServletRequest request) {
String ip = request.getRemoteAddr();
Bucket bucket = getBucket(ip);

if (bucket.tryConsume(1)) {
return ResponseEntity.ok(productService.findAll());
}

return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS)
.header("X-Rate-Limit-Retry-After-Seconds", "1")
.body("Too many requests");
}
}

Rate Limit 응답 커스터마이징

# 429 응답에 Retry-After 헤더 추가
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

server {
location /api/ {
limit_req zone=api_limit burst=20 nodelay;

# 429 응답 커스터마이징
error_page 429 @rate_limited;
}

location @rate_limited {
add_header Retry-After 1 always;
add_header Content-Type "application/json" always;
return 429 '{"error":"Too Many Requests","retryAfter":1}';
}
}

Rate Limiting 모니터링

# 429 에러 빈도 확인
grep " 429 " /var/log/nginx/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

# Rate limit 로그 활성화
http {
log_format rate_log '$remote_addr [$time_local] "$request" $status '
'limit_req=$limit_req_status';
# $limit_req_status: PASSED, DELAYED, REJECTED, DELAYED_DRY_RUN, REJECTED_DRY_RUN

access_log /var/log/nginx/rate.log rate_log;
}

# 실시간 차단된 IP 모니터링
tail -f /var/log/nginx/access.log | grep " 429 "

Rate Limit 전략별 권장 설정

엔드포인트zonerateburst비고
일반 페이지general30r/s50CDN 뒤라면 더 높게
APIapi_limit10r/s20서비스 특성에 맞게
로그인login_limit5r/m3Brute Force 방어
검색search_limit5r/s10DB 부하 고려
파일 다운로드download2r/s5대역폭과 함께 제한