Nginx 캐싱: proxy_cache 완전 정복
Nginx의 프록시 캐시는 백엔드 서버의 응답을 디스크나 메모리에 저장해 동일한 요청이 반복될 때 백엔드 호출 없이 즉시 응답합니다. 올바르게 설정하면 응답 시간을 수십 배 단축하고 백엔드 서버 부하를 90% 이상 줄일 수 있습니다.
캐시 동작 원리
캐시 HIT 시 Nginx는 백엔드를 호출하지 않고 저장된 응답을 즉시 반환합니다. 캐시 MISS 또는 EXPIRED 시에만 백엔드에 요청을 보내고 응답을 저장합니다.
proxy_cache 기본 설정
1단계: 캐시 존(Cache Zone) 정의
# /etc/nginx/nginx.conf — http 블록에 추가
http {
# 캐시 존 정의
# levels=1:2 : 디렉토리 계층 구조 (파일 수 분산)
# keys_zone=my_cache:10m : 캐시 키 메모리 10MB (약 80,000개 항목)
# max_size=1g : 최대 캐시 디스크 크기
# inactive=60m : 60분간 접근 없으면 삭제
# use_temp_path=off : 임시 디렉토리 대신 직접 저장 (성능 향상)
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=my_cache:10m
max_size=1g
inactive=60m
use_temp_path=off;
}
2단계: server 블록에서 캐시 활성화
server {
listen 443 ssl;
server_name example.com;
location / {
proxy_pass http://backend;
# 캐시 존 이름 (위에서 정의한 이름)
proxy_cache my_cache;
# 캐시 키: 요청을 식별하는 고유 문자열
proxy_cache_key "$scheme$request_method$host$request_uri";
# 상태 코드별 캐시 유지 시간
proxy_cache_valid 200 302 10m; # 정상 응답: 10분
proxy_cache_valid 301 1h; # 영구 리다이렉트: 1시간
proxy_cache_valid any 1m; # 나머지 (404 등): 1분
# 응답 헤더에 캐시 상태 표시 (디버깅용)
add_header X-Cache-Status $upstream_cache_status;
}
}
캐시 상태 확인 ($upstream_cache_status)
| 값 | 의미 |
|---|---|
HIT | 캐시에서 응답 (백엔드 호출 없음) |
MISS | 캐시 없음 → 백엔드 호출 후 캐시 저장 |
EXPIRED | 캐시 만료 → 백엔드 재요청 |
BYPASS | 캐시 우회 (조건부 bypass 설정) |
STALE | 만료된 캐시 사용 (백엔드 오류 시) |
UPDATING | 캐시 갱신 중 → 이전 캐시 응답 |
REVALIDATED | 304 Not Modified → 기존 캐시 유효 확인 |
Cache-Control 헤더 연동
백엔드 애플리케이션이 반환하는 Cache-Control 헤더를 Nginx가 따를 수 있습니다.
location / {
proxy_pass http://backend;
proxy_cache my_cache;
# 백엔드의 Cache-Control: no-cache/no-store를 무시하고 강제 캐시 (주의!)
proxy_ignore_headers Cache-Control Expires;
# 또는 백엔드 Cache-Control을 존중 (기본 동작)
# proxy_cache_valid 200 10m; ← Cache-Control이 없을 때만 적용
}
Spring Boot에서 캐시 제어:
@GetMapping("/api/products")
public ResponseEntity<List<Product>> getProducts() {
List<Product> products = productService.findAll();
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES)) // Nginx가 10분 캐시
.body(products);
}
@GetMapping("/api/user/profile")
public ResponseEntity<User> getProfile() {
// 사용자 데이터는 캐시 금지
return ResponseEntity.ok()
.cacheControl(CacheControl.noStore())
.body(userService.getCurrentUser());
}
캐시 우회(Bypass) 설정
로그인한 사용자나 POST 요청은 캐시를 우회해야 합니다.
# 캐시 우회 조건 설정
map $request_method $bypass_cache {
POST 1;
PUT 1;
DELETE 1;
default 0;
}
server {
location /api/ {
proxy_pass http://backend;
proxy_cache my_cache;
# 조건에 따라 캐시 우회
proxy_cache_bypass $bypass_cache $cookie_session_id;
proxy_no_cache $bypass_cache $cookie_session_id;
# → POST 요청이거나 세션 쿠키가 있으면 캐시 우회
proxy_cache_valid 200 5m;
}
# 정적 파일은 장시간 캐시
location ~* \.(css|js|png|jpg|gif|ico|woff2)$ {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 7d; # 1주일
expires 7d;
add_header Cache-Control "public, immutable";
}
}
Stale-While-Revalidate (캐시 갱신 중 응답)
캐시가 만료되어 백엔드를 호출하는 동안에도 이전 캐시를 임시로 반환합니다.
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 10m;
# 10분 지나도 1시간까지는 이전 캐시 반환 (백엔드가 응답하는 동안)
proxy_cache_use_stale error timeout updating
http_500 http_502 http_503 http_504;
# 캐시 갱신을 백그라운드에서 처리 (요청자는 즉시 stale 응답 받음)
proxy_cache_background_update on;
# 동일 캐시 갱신 요청을 1개만 백엔드로 전달 (나머지는 대기)
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
}
캐시 무효화(Purge)
콘텐츠가 변경되었을 때 캐시를 즉시 삭제합니다.
방법 1: proxy_cache_purge 모듈 (Nginx Plus 또는 ngx_cache_purge)
# ngx_cache_purge 모듈 설치 필요
location ~ /purge(/.*) {
# 내부 IP에서만 허용
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
proxy_cache_purge my_cache "$scheme$request_method$host$1";
}
# 특정 URL 캐시 삭제
curl -X PURGE https://example.com/api/products
방법 2: 캐시 파일 직접 삭제
# 캐시 디렉토리 전체 삭제 (Nginx 재시작 불필요)
find /var/cache/nginx -type f -delete
# 특정 URL에 해당하는 캐시 파일 찾기
grep -r "example.com/api/products" /var/cache/nginx -l | xargs rm -f
방법 3: 버전 기반 URL (권장)
<!-- 파일명이나 쿼리스트링에 버전 포함 -->
<script src="/static/app.js?v=20240315"></script>
<link rel="stylesheet" href="/static/style.css?v=1.2.3">
캐시 성능 모니터링
# 캐시 디렉토리 크기 확인
du -sh /var/cache/nginx/
# HIT/MISS 비율 분석 (access.log에 X-Cache-Status 로깅 필요)
awk '{print $NF}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 12453 HIT
# 1230 MISS
# 89 EXPIRED
# 12 BYPASS
# 실시간 캐시 히트율
tail -f /var/log/nginx/access.log | grep -oP 'X-Cache-Status: \K\w+'
로그 포맷에 캐시 상태 추가:
log_format cache_log '$remote_addr - [$time_local] "$request" '
'$status $body_bytes_sent '
'"$upstream_cache_status" $request_time';
access_log /var/log/nginx/access.log cache_log;
캐시 설계 권장사항
| 리소스 종류 | 캐시 TTL | 비고 |
|---|---|---|
| 정적 파일 (css, js, img) | 7일~1년 | 버전 기반 URL과 함께 사용 |
| API 목록/검색 결과 | 1~10분 | 실시간성 낮은 데이터 |
| 사용자별 데이터 | 캐시 금지 | proxy_no_cache 설정 |
| POST/PUT/DELETE | 캐시 금지 | 자동으로 제외됨 |
| 헬스체크 엔드포인트 | 캐시 금지 | 실시간 상태 반영 필요 |