2025년 11월 7일 Cloudflare가 Trace 도구에서 각 요청의 정확한 캐시 키를 볼 수 있도록 개선했습니다. 커스텀 캐시 키 로직이 의도대로 동작하는지 확인하는 데 실무적으로 큰 이득입니다.
캐시 디버깅은 자체 서버 운영에서도 자주 병목입니다. “왜 이 요청은 히트가 안 되지?”라는 질문에 답하려면 캐시 키가 어떻게 계산되는지, 정확히 어떤 값이 저장되어 있는지 봐야 합니다.
이 글에서는 Nginx·Varnish에서 캐시 키·히트/미스를 디버깅하는 실무 절차를 정리합니다.
캐시 디버깅 3단계 접근
캐시 문제는 대체로 이 순서로 뜯어봅니다.
- 응답이 캐시되고 있는가 — 원본이 cacheable하게 응답하고 있는지.
- 캐시 키가 예상대로 계산되는가 — 같은 URL이 왜 여러 키로 나뉘거나 뭉치는지.
- 캐시가 서빙되고 있는가 — 저장은 되는데 왜 히트가 안 되는지.
각 단계마다 다른 도구·다른 로그가 필요합니다.
1단계 — 응답이 캐시 가능한가
원본 응답 헤더 확인
# 원본 서버 직접 (Nginx 프록시 우회)
curl -sI http://127.0.0.1:2368/ | grep -iE "^(cache-control|expires|vary|set-cookie|pragma)"
캐시를 막는 헤더.
Cache-Control: no-cache,no-store,private,must-revalidateSet-Cookie— 대부분 캐시 구현이 자동으로 캐시 회피Vary: *Pragma: no-cache(옛 방식이지만 여전히 자주 붙음)
원본이 이런 헤더를 붙이고 있으면 Nginx는 캐시하지 않습니다.
CMS·프레임워크의 기본값 함정
Ghost·WordPress·Django 같은 CMS·프레임워크는 세션 관리를 위해 자동으로 Set-Cookie·Cache-Control: private를 붙이는 경우가 흔합니다. 익명 사용자 페이지에도.
정적 콘텐츠·공개 페이지에 대해 CMS 설정에서 캐시 헤더를 명시적으로 덮어써야 합니다.
# Nginx 앞단에서 강제 오버라이드
location /public/ {
proxy_hide_header Set-Cookie;
proxy_hide_header Cache-Control;
add_header Cache-Control "public, max-age=3600";
proxy_pass http://backend;
proxy_cache mycache;
proxy_cache_valid 200 1h;
}
proxy_hide_header로 원본 헤더를 제거하고 add_header로 우리가 원하는 값을 넣는 패턴.
2단계 — 캐시 키 검사
Nginx의 캐시 키 로깅
Nginx는 기본적으로 캐시 키를 로그에 남기지 않습니다. 커스텀 log format으로 노출.
log_format cache '$remote_addr - $upstream_cache_status - '
'key=$upstream_cache_key '
'"$request" $status $body_bytes_sent';
access_log /var/log/nginx/cache.log cache;
문제는 Nginx는 $upstream_cache_key 변수를 노출하지 않음. 대신 계산된 키를 파일 시스템 경로에서 유추.
파일 시스템에서 캐시 파일 찾기
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:10m;
levels=1:2는 캐시 파일 경로가 /var/cache/nginx/<한글자>/<두글자>/<해시> 형태. 해시는 캐시 키의 MD5.
# 캐시 키 계산 (nginx의 기본 key = $scheme$proxy_host$request_uri)
echo -n "httpbackend/foo" | md5sum
# 결과가 abcdef1234...
# 실제 캐시 파일 위치
ls /var/cache/nginx/4/23/abcdef1234...
파일 내용을 열면 응답 헤더·본문이 들어 있습니다.
head -c 500 /var/cache/nginx/4/23/abcdef1234...
캐시 키 자체는 파일 첫 부분의 헤더에 저장돼 있음.
커스텀 캐시 키 디버깅
proxy_cache_key "$scheme$request_method$host$request_uri$lang";
# 디버그 응답 헤더로 노출
add_header X-Cache-Key "$scheme$request_method$host$request_uri$lang" always;
응답 헤더에 현재 요청의 캐시 키가 그대로 노출됨. 프로덕션에서는 절대 켜지 말고 개발·스테이징에서만.
3단계 — 히트/미스 상태 관측
$upstream_cache_status 값 목록
- MISS: 캐시에 없음, 오리진 호출.
- HIT: 캐시에서 응답.
- EXPIRED: 캐시 있으나 만료됨. 오리진 호출.
- STALE: 오리진 오류로 만료된 캐시를 그대로 서빙.
- UPDATING: 다른 요청이 오리진 갱신 중, 이 요청은 만료된 캐시 서빙(background_update).
- BYPASS:
proxy_cache_bypass조건 매칭, 캐시 우회. - REVALIDATED: 오리진에 조건부 요청(If-Modified-Since)해서 304 받고 캐시 신선도 갱신.
응답 헤더로 노출.
add_header X-Cache-Status $upstream_cache_status;
히트율 통계
Nginx access log에서 상태별 통계.
awk '/X-Cache-Status/ {status=$0} /GET/ {print status}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn
또는 커스텀 로그 포맷이면 더 간단.
awk '{print $3}' /var/log/nginx/cache.log | sort | uniq -c
히트율(HIT / 전체 요청)이 얼마인지 확인. 정상적으로 튜닝된 캐시는 정적 리소스 80%+, 동적 응답도 30%+ 히트가 나옵니다.
Varnish에서 캐시 디버깅
Varnish는 캐시 디버깅 도구가 훨씬 강력.
varnishlog — 실시간 요청·응답 로그
varnishlog -g request
특정 요청에 대해 어떤 VCL 서브루틴이 호출됐고 캐시 결과가 무엇인지 상세히 나옴.
varnishstat — 실시간 지표
varnishstat -1 | grep -E "cache_hit|cache_miss|cache_hitpass"
캐시 히트/미스 카운트.
varnishncsa — Apache Combined 로그 형식
varnishncsa -F '%{Varnish:hitmiss}x %l %u %t "%r" %s %b'
hit/miss가 표준 로그처럼 파싱 가능.
함정 목록
1) 트레일링 슬래시
/foo와 /foo/가 같은 응답을 반환하지만 캐시 키는 다름. 히트율 저하. 원인은 리다이렉트를 오리진이 처리하지 않고 앞단에서 그대로 오리진에 두 URL이 모두 감.
해결: Nginx에서 정규화 리다이렉트.
rewrite ^/(.*)/$ /$1 permanent;
2) 쿼리스트링 순서
/api?a=1&b=2와 /api?b=2&a=1은 다른 캐시 키. 캐시가 두 번 저장됨.
해결: 쿼리스트링 정렬 후 캐시 키에 사용.
-- lua-nginx로 정렬
init_by_lua_block {
require "resty.core"
}
3) 대소문자
/Foo와 /foo. 서버가 case-insensitive여도 캐시는 case-sensitive 저장.
해결: URL 소문자로 정규화하는 오리진 리다이렉트.
4) 응답 크기 상한
Nginx proxy_cache_path에 max_size를 안 정하면 디스크가 꽉 참. 정하면 LRU 삭제.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mycache:100m max_size=10g inactive=60m;
5) 캐시 프리로딩
새 콘텐츠 배포 후 첫 사용자가 오리진 지연을 겪음. 배포 직후 캐시 프리로드 스크립트로 주요 URL을 미리 요청.
for url in $(cat main-urls.txt); do
curl -so /dev/null https://example.com$url &
done
Cloudflare + 자체 Nginx 캐시 이중화
두 층이 있으면 문제 원인 파악이 어려워집니다.
- Cloudflare 대시보드에서
X-Cache-Status: HIT인지 확인. - 원본 Nginx에서
X-Cache-Status: HIT인지 확인 (Cloudflare로부터 온 요청은 대개 무의미하지만).
Cloudflare Trace 신기능이 이 이중화 디버깅에서 특히 유용. 어떤 캐시 키로 어느 계층에 저장돼 있는지 상세히 봄.
마무리
캐시 디버깅은 웹 성능 실무의 반쯤 되는 시간이 소요됩니다. 캐시 히트가 30%에서 80%로 오르면 오리진 부하가 극적으로 감소하고 응답 속도도 눈에 띄게 개선. 그만한 가치가 있는 작업.
Cloudflare가 캐시 키 검사 도구를 연 것은 반갑고, 자체 서버에도 같은 원칙(캐시 키 노출·상태 로깅·통계)을 적용하면 됩니다.
TCP-80.NET의 VPS·전용서버 고객 중 Nginx·Varnish 캐시 튜닝·디버깅이 필요하시면 @tcp80net 텔레그램으로 문의 주시면 됩니다.
출처 (Source)
이 글은 아래 Cloudflare 공식 changelog를 참고해 tcp-80.net 청중 관점에서 재구성·번역·확장했습니다. 자체 서버 캐시 디버깅 실무(Nginx $upstream_cache_status·proxy_cache_key·Varnish varnishlog·varnishstat·varnishncsa·함정 목록·프리로딩)는 원문에 없는 자체 추가 내용입니다.
- 원문 제목: Cache keys visible in Cloudflare Trace
- 원문 발행일: 2025-11-07
- 원문 URL: https://developers.cloudflare.com/changelog/post/2025-11-07-cache-keys-for-cloudflare-trace/
- 원문 저작자: Cloudflare, Inc.
- 본문 재구성·번역: TCP-80.NET (2025-11-07)
Cloudflare Trace UI·기능은 시점에 따라 다를 수 있으니 최신 정보는 원문 및 Cloudflare 대시보드에서 확인하시기 바랍니다.