HTTP 캐싱 가이드: 추가 인프라 없이 웹사이트 속도 높이기
Cache-Control 헤더, ETag, CDN 캐싱 전략, 브라우저 캐시 동작 방식을 완벽히 익혀 사이트 로딩 속도를 획기적으로 개선하고 서버 비용을 절감하세요.
캐싱은 그 어떤 성능 최적화보다 효과가 뛰어납니다. 캐시된 응답은 마이크로초 단위로 제공되고, 비용이 들지 않으며, 서버 처리가 전혀 필요 없습니다. 그러나 대부분의 애플리케이션은 지나치게 공격적으로 캐시하거나(오래된 콘텐츠 제공), 아예 캐시하지 않아(대역폭과 컴퓨팅 자원 낭비) 문제가 됩니다. HTTP 캐싱을 제대로 이해하면 버그의 원인이 아닌 강력한 무기로 활용할 수 있습니다.
HTTP 캐싱의 동작 원리
브라우저나 CDN이 응답을 받으면, 헤더를 확인해 캐시 여부와 캐시 기간을 결정합니다. 동일한 리소스에 대한 다음 요청 시에는 서버에 전혀 접근하지 않고 캐시된 사본을 바로 제공할 수 있습니다.
캐시 생명 주기는 두 단계로 구성됩니다:
- 신선도(Freshness) — 캐시된 사본이 아직 유효한가?
Cache-Control: max-age또는Expires로 결정됩니다. - 유효성 검사(Validation) — 만료된 경우, 서버에 콘텐츠 변경 여부를 확인할 수 있는가?
ETag또는Last-Modified로 결정됩니다.
Cache-Control: 핵심 캐싱 지시어
Cache-Control은 가장 강력한 캐싱 헤더입니다. 쉼표로 구분된 지시어 목록으로 구성됩니다:
Cache-Control: public, max-age=31536000, immutable
주요 지시어
| 지시어 | 의미 |
|---|---|
public |
모든 캐시(브라우저, CDN, 프록시)에 저장 가능 |
private |
최종 사용자의 브라우저만 캐시 가능 (CDN 불가) |
no-cache |
사용 전 항상 서버에서 재검증 필요 ("캐시 안 함"이 아님) |
no-store |
절대 캐시하지 않음 — 민감한 데이터용 |
max-age=N |
N초 동안 캐시 |
s-maxage=N |
CDN 전용 최대 수명 (공유 캐시에서 max-age 대체) |
immutable |
콘텐츠가 절대 변경되지 않음 — 재검증 완전 생략 |
must-revalidate |
만료 시 제공 전 반드시 재검증 |
stale-while-revalidate=N |
백그라운드에서 새 버전을 가져오는 동안 N초간 오래된 버전 제공 |
⚠️
no-cache는 "캐시하지 않음"을 의미하지 않습니다. "캐시하되, 항상 유효한지 확인하라"는 의미입니다. 진정으로 캐시를 원하지 않는다면no-store를 사용하세요.
리소스 유형별 캐싱 전략
리소스마다 다른 전략이 필요합니다:
콘텐츠 해시를 적용한 정적 자산 (CSS, JS, 이미지)
Cache-Control: public, max-age=31536000, immutable
빌드 도구가 파일명에 해시를 추가하는 경우(main.a3f9b2c.js), 콘텐츠가 변경되면 URL도 바뀝니다. 영구 캐시하세요 — 새 버전은 새 URL을 갖게 됩니다.
HTML 페이지
Cache-Control: no-cache
또는 짧은 TTL 사용:
Cache-Control: public, max-age=60, stale-while-revalidate=3600
HTML은 자주 변경되고 해시된 자산에 연결됩니다. 짧게 캐시하거나 강제 재검증을 설정하세요.
API 응답
# 공개 데이터 (예: 상품 카탈로그)
Cache-Control: public, max-age=300, stale-while-revalidate=600
# 사용자별 데이터
Cache-Control: private, max-age=60
# 실시간 데이터 (주가, 라이브 스코어)
Cache-Control: no-store
민감한 데이터 (인증, 결제)
Cache-Control: no-store
절대 캐시하지 마세요. 예외 없습니다.
ETag와 조건부 요청
캐시된 응답이 만료되면, 브라우저는 단순히 버리는 것이 아니라 서버에 여전히 유효한지 확인합니다. 이것이 재검증입니다.
ETag
ETag는 응답 콘텐츠의 지문(fingerprint)입니다:
# 서버 응답:
ETag: "a3f9b2c8d4e1"
# 브라우저의 다음 요청:
If-None-Match: "a3f9b2c8d4e1"
# 변경되지 않은 경우, 서버 응답:
HTTP/1.1 304 Not Modified
(본문 없음 — 대역폭 절약)
# 변경된 경우, 서버 응답:
HTTP/1.1 200 OK
ETag: "b7c2d4e9a1f3"
(전체 새 응답)
304 Not Modified 응답에는 본문이 없고 헤더만 있습니다. 콘텐츠를 다시 다운로드하는 데 필요한 모든 대역폭을 절약할 수 있습니다.
Last-Modified
해시 대신 타임스탬프를 사용하는 유사한 방식입니다:
Last-Modified: Tue, 01 Apr 2026 10:00:00 GMT
# 브라우저 전송:
If-Modified-Since: Tue, 01 Apr 2026 10:00:00 GMT
ETag가 더 신뢰할 수 있습니다 (로드 밸런싱 서버에서 타임스탬프가 정확하지 않을 수 있음).
Vary 헤더: 요청 변형별 캐시
Vary 헤더는 어떤 요청 헤더가 응답에 영향을 미치는지 캐시에 알려줍니다:
Vary: Accept-Encoding
이렇게 하면 gzip과 br 응답에 대해 별도의 사본이 캐시됩니다. 주요 활용 사례:
Vary: Accept-Encoding # 압축/비압축 별도 캐시
Vary: Accept-Language # 언어별 별도 캐시
Vary: Accept # JSON vs HTML 응답 별도 캐시
⚠️
Vary: Cookie또는Vary: Authorization은 사실상 CDN 캐싱을 비활성화합니다 — CDN은 사용자별 응답을 캐시할 수 없습니다.
stale-while-revalidate: 백그라운드 갱신
가장 유용한 최신 캐싱 패턴 중 하나입니다:
Cache-Control: max-age=60, stale-while-revalidate=600
- 처음 60초 동안은 캐시에서 즉시 제공 (신선 상태)
- 60~660초 사이의 요청: 오래된 사본을 즉시 제공하면서 백그라운드에서 새 버전을 가져옴
- 660초 이후: 제공 전 반드시 재검증 필요
사용자는 항상 빠른 응답을 받습니다. 누구도 네트워크 왕복을 기다리지 않아도 되면서 캐시는 최신 상태를 유지합니다.
CDN 캐싱 고려사항
CDN(Cloudflare, CloudFront, Fastly)은 Cache-Control 헤더를 따르지만, 자체적인 복잡성을 추가합니다:
-
**
s-maxage**를 사용하면 CDN과 브라우저에 서로 다른 TTL을 설정할 수 있습니다:Cache-Control: public, max-age=60, s-maxage=86400브라우저는 1분, CDN은 24시간 캐시합니다.
-
캐시 퍼지(Cache purging) — 배포 시 업데이트된 자산의 CDN 캐시를 퍼지하세요. 대부분의 CDN은 API 기반 퍼지를 제공합니다.
-
캐시 키(Cache keys) — CDN은 URL + Vary 헤더를 기준으로 캐시합니다. 쿼리 스트링은 보통 캐시 키에 포함됩니다.
캐시 동작 테스트
API Request Builder를 사용해 응답 헤더를 검사하고 캐싱 설정이 올바르게 작동하는지 확인하세요:
- 요청을 보내고
Cache-Control,ETag,Last-Modified헤더를 확인합니다 - 동일한 요청을 다시 보내 —
Age헤더(캐시된 이후 초)와X-Cache: HIT를 확인합니다 CF-Cache-Status(Cloudflare) 또는X-Cache(CloudFront)를 확인해 CDN 캐싱을 검증합니다
Chrome DevTools → Network 탭 → 리소스 클릭 → Headers 탭에서 응답의 from disk cache 또는 from memory cache 항목을 확인하세요.
Nginx 캐싱 설정
일관된 동작을 위해 Nginx 수준에서 캐싱 헤더를 설정하세요:
# 정적 자산 — 영구 캐시
location ~* \.(js|css|woff2|png|jpg|webp|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML — 항상 재검증
location ~* \.html$ {
add_header Cache-Control "no-cache";
}
# API — stale-while-revalidate를 활용한 짧은 캐시
location /api/ {
add_header Cache-Control "public, max-age=60, stale-while-revalidate=600";
}
Nginx Config Generator를 사용해 사용 사례에 맞는 완전하고 최적화된 Nginx 설정을 생성하세요.
캐싱 체크리스트
- 정적 자산(CSS/JS)에 콘텐츠 해시 파일명 +
max-age=31536000, immutable적용 - HTML은
no-cache또는 매우 짧은max-age로 제공 - API 응답은 업데이트 빈도에 따라 캐시
- 민감한 데이터는
no-store사용 - 조건부 요청을 위한 ETag 또는
Last-Modified활성화 - 적절한 API 엔드포인트에
stale-while-revalidate적용 - CDN 캐싱 헤더 검증 및 테스트 완료
올바른 HTTP 캐싱은 재방문 사용자에게 즉각적인 속도감을 제공하고, 대역폭 비용을 절감하며, 서버 부하를 줄입니다 — 추가 인프라 없이도 말입니다.