본문으로 건너뛰기

Nginx 캐싱: proxy_cache 완전 정복

Nginx의 프록시 캐시는 백엔드 서버의 응답을 디스크나 메모리에 저장해 동일한 요청이 반복될 때 백엔드 호출 없이 즉시 응답합니다. 올바르게 설정하면 응답 시간을 수십 배 단축하고 백엔드 서버 부하를 90% 이상 줄일 수 있습니다.


캐시 동작 원리

Nginx Caching Flow

캐시 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캐시 갱신 중 → 이전 캐시 응답
REVALIDATED304 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캐시 금지자동으로 제외됨
헬스체크 엔드포인트캐시 금지실시간 상태 반영 필요