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 전략별 권장 설정
| 엔드포인트 | zone | rate | burst | 비고 |
|---|---|---|---|---|
| 일반 페이지 | general | 30r/s | 50 | CDN 뒤라면 더 높게 |
| API | api_limit | 10r/s | 20 | 서비스 특성에 맞게 |
| 로그인 | login_limit | 5r/m | 3 | Brute Force 방어 |
| 검색 | search_limit | 5r/s | 10 | DB 부하 고려 |
| 파일 다운로드 | download | 2r/s | 5 | 대역폭과 함께 제한 |