HTTP Cache-Control no-cache와 no-store 차이
HTTP 응답의 캐시를 제어할 때 no-cache와 no-store를 자주 사용한다.
이름만 보면 둘 다 캐시를 만들지 않는 설정처럼 보이지만 실제 기준은 다르다.
no-cache는 저장을 금지하지 않고 재사용 전에 원본 서버의 검증을 요구한다.no-store는 요청과 응답을 캐시에 저장하지 말라는 지시다.
두 지시자의 핵심 차이
응답의 Cache-Control: no-cache는 정상적인 캐시 조건을 만족하면 응답을 저장할 수 있게 둔다.
다만 저장한 응답으로 다음 요청을 바로 처리할 수 없고 원본 서버에 전달해 성공적으로 검증해야 한다.
Cache-Control: no-cache응답의 Cache-Control: no-store는 private cache와 shared cache 모두에 해당 요청과 응답을 저장하지 말라고 지시한다.
저장한 콘텐츠의 재검증이 아니라 저장 자체를 막는 설정이다.
Cache-Control: no-storeno-store는 브라우저의 private cache와 CDN이나 proxy의 shared cache 모두에 적용된다.
no-cache의 재검증 흐름
no-cache의 장점은 매번 최신 상태를 확인하면서 변경되지 않은 응답 본문을 다시 내려받지 않을 수 있다는 점이다.
이 흐름에는 ETag나 Last-Modified 같은 validator가 필요하다.
서버가 처음 응답할 때 ETag를 함께 보낼 수 있다.
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "post-42-v3"
Content-Type: application/json
{"id":42,"title":"Cache-Control 정리"}캐시는 응답을 보관할 수 있지만 다음 요청에서 그대로 재사용하지 않는다.
저장한 ETag를 If-None-Match에 넣어 원본 서버에 조건부 요청을 보낸다.
GET /api/posts/42 HTTP/1.1
Host: api.example.com
If-None-Match: "post-42-v3"콘텐츠가 바뀌지 않았다면 서버는 본문 없이 304 Not Modified로 응답할 수 있다.
HTTP/1.1 304 Not Modified
Cache-Control: no-cache
ETag: "post-42-v3"캐시는 검증에 성공한 기존 본문을 다시 사용한다.
본문 전송량은 줄지만 서버까지 검증 요청을 보내는 왕복 시간은 남는다.
validator가 없거나 서버가 조건부 요청을 지원하지 않으면 원본 서버가 매번 200 OK와 전체 본문을 보낼 수 있다.no-cache만 붙인다고 자동으로 304 응답이 만들어지는 것은 아니다.
304를 포함한 응답 코드의 역할은 HTTP 상태 코드 정리에서 이어서 볼 수 있다.
no-store의 저장 금지 범위
응답의 no-store는 캐시가 바로 전달 중인 요청과 응답의 어떤 부분도 의도적으로 비휘발성 저장소에 보관하지 못하게 한다.
로그인 과정에서 발급한 일회성 코드나 저장 자체를 피해야 하는 민감한 응답에 맞는 기준이다.
저장된 본문이 없으므로 다음 요청에서는 304 재검증으로 본문을 절약하는 흐름을 기대할 수 없다.
no-store는 보안 기능 전체를 대신하지 않는다.
악의적이거나 손상된 캐시가 지시를 무시할 수 있고 네트워크 도청이나 애플리케이션 로그 저장도 막지 못한다.
민감한 응답에는 HTTPS, 인증과 권한 검사, 안전한 로그 정책을 별도로 적용해야 한다.
기존 캐시 삭제에 대한 오해
서버가 새 응답에 no-store를 추가해도 같은 URL의 과거 응답은 자동으로 삭제되지 않으며, 아직 fresh하다면 새 header를 받을 기회도 없다.
이미 배포한 캐시를 즉시 없애려면 CDN의 purge 기능을 사용하거나 정적 asset 파일명에 content hash를 넣어 cache key를 바꿔야 한다.
no-store를 앞으로의 저장 정책과 과거 캐시의 삭제 명령으로 동시에 이해하면 배포 뒤 오래된 콘텐츠가 남는 원인을 찾기 어렵다.
HTTP 캐시 표준에는 중간 캐시의 특정 응답을 일괄 삭제하는 범용 Cache-Control 지시자가 없다.
요청과 응답 지시자의 방향
Cache-Control은 요청과 응답 양쪽에 들어갈 수 있다.
같은 이름이라도 누가 이후 동작을 지시하는지 구분해야 한다.
요청의 no-cache는 클라이언트가 저장된 응답을 원본 서버의 성공적인 검증 없이 사용하지 않기를 원한다는 뜻이다.
브라우저의 새로고침 과정에서 비슷한 요청 지시자를 볼 수 있다.
응답의 no-cache는 서버가 이 응답의 이후 재사용 조건을 캐시에 전달한다.
일반적인 웹 애플리케이션에서 캐시 정책을 정할 때는 서버가 보내는 응답 header가 중심이다.
요청의 no-store는 캐시가 해당 요청이나 그에 대한 응답을 저장하지 않도록 지시한다.
그러나 이미 저장된 응답을 지우는 명령은 아니며 no-store만으로 과거 응답의 재사용까지 금지하는 뜻도 아니다.
max-age와 must-revalidate의 차이
max-age=0은 응답을 저장할 수 있지만 즉시 stale 상태가 되게 한다.
반면 no-cache는 응답이 fresh로 계산될 수 있는 상황에서도 검증 없이 다른 요청에 사용하지 못하게 한다.
Cache-Control: max-age=0, must-revalidatemust-revalidate는 stale 응답을 원본 서버의 성공적인 검증 없이 재사용하지 못하게 한다.
따라서 max-age=0, must-revalidate 조합은 일반적인 목적에서 no-cache와 비슷한 결과를 만든다.
현재 환경이라면 의도가 더 직접적인 no-cache를 우선 사용할 수 있다.
no-store, no-cache, max-age=0, must-revalidate처럼 관련 지시자를 모두 붙이는 설정은 의미를 더 명확하게 만들지 않는다.
새 응답의 저장을 금지한 상태에서는 저장된 응답의 freshness와 재검증 규칙을 함께 지정할 실익이 없다.
private와 캐시 계층
private는 응답을 전혀 저장하지 말라는 뜻이 아니다.
shared cache의 저장은 금지하지만 브라우저 같은 private cache의 저장은 허용한다.
사용자별 HTML을 브라우저에 저장해도 되지만 매번 최신 상태를 확인해야 한다면 다음 조합을 사용할 수 있다.
Cache-Control: private, no-cache민감해서 브라우저에도 남기지 않아야 한다면 no-store가 더 맞다.
반대로 hash가 포함된 JavaScript나 CSS처럼 URL이 콘텐츠 버전을 나타내는 파일에는 두 지시자보다 긴 freshness가 어울린다.
Cache-Control: public, max-age=31536000, immutableHTTP 캐시와 framework의 데이터 캐시는 같은 계층이 아닐 수 있다.
예를 들어 Next.js의 서버 렌더링 캐시 기준은 Next.js 16 Cache Components와 use cache 정리처럼 framework 설정을 별도로 확인해야 한다.
선택 기준
응답을 저장해도 되고 사용할 때마다 최신 상태를 확인해야 한다면 no-cache를 선택한다.
ETag나 Last-Modified를 함께 제공하면 변경되지 않은 본문의 전송을 줄일 수 있다.
요청과 응답이 캐시에 남는 것 자체를 피해야 한다면 no-store를 선택한다.
다만 기존 캐시 삭제와 통신 보안까지 해결한다고 기대하지 않는다.
사용자별 응답을 브라우저에만 저장하려면 private를 먼저 기준으로 삼고 freshness와 재검증 정책을 조합한다.
모든 응답에 습관적으로 no-store를 붙이기보다 데이터의 민감도, 재사용 가능성, 최신성 요구를 나눠 결정하는 편이 낫다.
참고 자료


























