Developer Tools

HTTP 캐싱 가이드: 추가 인프라 없이 웹사이트 속도 높이기

Cache-Control 헤더, ETag, CDN 캐싱 전략, 브라우저 캐시 동작 방식을 완벽히 익혀 사이트 로딩 속도를 획기적으로 개선하고 서버 비용을 절감하세요.

7분 읽기

데이터 센터의 서버 랙

캐싱은 그 어떤 성능 최적화보다 효과가 뛰어납니다. 캐시된 응답은 마이크로초 단위로 제공되고, 비용이 들지 않으며, 서버 처리가 전혀 필요 없습니다. 그러나 대부분의 애플리케이션은 지나치게 공격적으로 캐시하거나(오래된 콘텐츠 제공), 아예 캐시하지 않아(대역폭과 컴퓨팅 자원 낭비) 문제가 됩니다. HTTP 캐싱을 제대로 이해하면 버그의 원인이 아닌 강력한 무기로 활용할 수 있습니다.

HTTP 캐싱의 동작 원리

브라우저나 CDN이 응답을 받으면, 헤더를 확인해 캐시 여부와 캐시 기간을 결정합니다. 동일한 리소스에 대한 다음 요청 시에는 서버에 전혀 접근하지 않고 캐시된 사본을 바로 제공할 수 있습니다.

캐시 생명 주기는 두 단계로 구성됩니다:

  1. 신선도(Freshness) — 캐시된 사본이 아직 유효한가? Cache-Control: max-age 또는 Expires로 결정됩니다.
  2. 유효성 검사(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

이렇게 하면 gzipbr 응답에 대해 별도의 사본이 캐시됩니다. 주요 활용 사례:

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를 사용해 응답 헤더를 검사하고 캐싱 설정이 올바르게 작동하는지 확인하세요:

  1. 요청을 보내고 Cache-Control, ETag, Last-Modified 헤더를 확인합니다
  2. 동일한 요청을 다시 보내 — Age 헤더(캐시된 이후 초)와 X-Cache: HIT를 확인합니다
  3. 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 캐싱은 재방문 사용자에게 즉각적인 속도감을 제공하고, 대역폭 비용을 절감하며, 서버 부하를 줄입니다 — 추가 인프라 없이도 말입니다.