-
Cache-Control: no-cache인데 왜 캐시에 저장될까Programming 2026. 9. 20. 10:53728x90반응형

저장해 둔 내용을 다시 확인한 뒤 쓰는 캐시 정책을 비유한 AI 생성 이미지입니다. 캐시 정책은 저장을 허용할지, 재사용 전에 확인할지, 누구와 공유할지의 세 질문으로 나눠 정하세요.
Cache-Control: no-cache를 붙였는데 브라우저에 응답이 남아 있습니다. 이름만 보면 설정이 무시된 것 같지만, 저장 자체를 막고 싶었다면 선택한 지시어가 목적과 달랐을 수 있습니다. no-cache의 핵심은 저장 금지가 아니라 확인 없는 재사용 금지입니다.
이 차이를 알아야 “항상 최신 내용을 보여 주세요”와 “이 응답을 HTTP 캐시에 남기지 마세요”를 다르게 구현할 수 있습니다. 아래는 2026년 9월 13일 HTTP 명세와 MDN 기준의 설명입니다. 헤더와 시간은 설계 예시이며 실제 서비스의 캐시를 측정하거나 삭제하지 않았습니다.
no-cache는 저장한 응답을 확인하고 쓰게 합니다
서버가 인자 없는
Cache-Control: no-cache를 응답에 넣으면 HTTP 캐시는 저장한 응답을 다른 요청에 쓰기 전에 성공적으로 검증해야 합니다. 저장된 본문을 무조건 버린 뒤 매번 전체 파일을 내려받으라는 뜻은 아닙니다. RFC 9111의 no-cache서버가 ETag 같은 검증자를 제공한다면 클라이언트는 자신이 가진 버전을 조건부 요청으로 확인할 수 있습니다. 서버가 같은 버전이라고 판단하면 304 Not Modified로 본문 전송을 생략하고, 클라이언트는 저장한 본문을 사용할 수 있습니다. 내용이 바뀌었다면 새 본문을 받아야 합니다. ETag를 이용한 버전 확인
따라서 Network에 304가 보이거나 디스크에 캐시가 남아 있다는 사실은 no-cache의 실패 증거가 아닙니다. 이 정책이 요구하는 것은 새 HTTP 요청에서 검증 절차를 거쳤는지입니다.
또한 검증 요청은 네트워크 왕복을 없애지 않습니다. 본문을 다시 내려받는 비용은 줄일 수 있지만 서버에 확인하는 시간이 남습니다. 응답 크기가 작고 서버의 검증 처리가 비싸다면 전송량 감소가 곧 같은 비율의 지연 감소라고 말할 수 없습니다.
no-store와 private은 다른 질문에 답합니다
Cache-Control: no-store는 해당 교환의 정보를 HTTP 캐시에 저장하거나 다른 요청에 재사용하지 말라는 지시입니다. 개인 캐시와 공유 캐시에 모두 적용됩니다. 하지만 이 헤더가 악의적인 저장을 막거나 모든 개인정보 보호를 보장하는 장치는 아닙니다. RFC 9111의 no-storeprivate는 공유 캐시에 응답을 저장하지 못하게 합니다. 브라우저 같은 개인 캐시에 저장하는 것까지 금지하지는 않습니다. 사용자의 이름이나 개인 설정이 포함된 응답을 여러 사람이 함께 쓰는 CDN 캐시에 넣어서는 안 되는 상황과 관련이 있습니다. 개인 캐시와 공유 캐시이 세 지시어는 강도만 다른 한 줄짜리 스위치가 아닙니다. no-cache는 재사용 전 확인, no-store는 저장 제한, private은 공유 범위를 다룹니다. 그래서 브라우저 저장은 허용하되 다른 사용자와 공유하지 않고 매번 검증하려면 다음처럼 두 목적을 함께 적을 수 있습니다.
Cache-Control: private, no-cache반대로 저장 자체를 허용하지 않는 것이 요구사항이라면 no-store를 검토합니다. no-cache, max-age=0, must-revalidate 등을 이유 없이 모두 붙여야 더 안전한 정책이 되는 것은 아닙니다. 어떤 요구 때문에 각 지시어를 넣었는지 설명할 수 있어야 합니다.
첫 번째 예시: 내용이 바뀌면 주소도 바뀌는 정적 파일
가상의 사이트가
/assets/app.a1b2c3.js를 제공합니다. 파일 내용이 바뀌면 기존 주소를 덮어쓰지 않고 새로운 해시가 붙은 주소를 만든다는 배포 규칙이 있다고 하겠습니다. 사용자별 정보가 없는 공개 파일입니다.이 조건에서는 다음과 같은 정책을 검토할 수 있습니다. 1년이라는 값은 예시일 뿐 모든 서비스에 대한 권장 보관 기간은 아닙니다.
Cache-Control: public, max-age=31536000, immutablemax-age는 HTTP 캐시가 응답을 신선한 상태로 판단할 수 있는 수명을 초 단위로 정합니다. immutable은 그 신선한 기간에 해당 표현이 바뀌지 않는다는 뜻입니다. URL에 버전이나 해시를 넣고 내용 변경 때 주소를 바꾸는 방식과 함께 쓰입니다. 버전별 정적 리소스 정책
이 정책을 쓰려면 공개 파일의 같은 URL에 다른 내용을 덮어쓰지 않아야 합니다. 새 배포에서는 새 파일 주소를 만들고 HTML의 참조도 그 주소로 바꿉니다. 그러면 새 HTML을 받은 브라우저는 새 주소를 요청하므로, 이전 주소에 저장된 응답을 새 파일 대신 사용하는 문제를 피할 수 있습니다.
이 규칙을 지키지 않고 app.a1b2c3.js의 내용만 덮어쓰면 사용자는 오래된 파일을 계속 사용할 수 있습니다. immutable이 새 파일을 찾아주는 기능은 아닙니다. 오래 저장해도 되는 이유를 헤더가 만들어 주는 것이 아니라 배포 방식이 먼저 뒷받침해야 합니다.
파일이 1년 동안 반드시 디스크에 남는다는 뜻도 아닙니다. max-age는 재사용 판단의 수명이지 저장 공간 보장 기간이 아닙니다. 캐시가 항목을 잃었거나 새 주소를 처음 요청하면 다시 받아야 합니다.
두 번째 예시: 최신 파일 주소를 알려 주는 HTML
같은 사이트의
/index.html은 주소를 유지하면서 새 JavaScript 파일 이름을 알려 준다고 하겠습니다. 이 HTML까지 오래된 채로 재사용하면 새 파일을 만들어도 사용자가 그 주소를 알지 못할 수 있습니다.브라우저가 저장한 HTML을 활용하되 새 HTTP 요청에서는 변경 여부를 확인하도록 다음을 검토할 수 있습니다. ETag 값은 서버가 실제 버전에 맞게 생성해야 하며 아래 문자열은 설명용입니다.
Cache-Control: no-cache ETag: "page-revision-7"처음에는 본문과 검증자를 저장합니다. 다음 요청에서 같은 버전인지 확인하고, 같으면 저장된 HTML을 사용하며 다르면 새 HTML과 파일 주소를 받습니다. 이 예시에서 no-cache의 목적은 정적 파일과 달리 주소를 유지하는 진입 문서의 변경을 확인하는 것입니다. HTML과 정적 리소스의 캐시 구분
서버가 검증자를 잘못 고정해 두면 내용이 바뀌어도 같다고 답할 수 있습니다. 그러므로 헤더 이름이 있다는 것만 확인하지 않고 실제 내용 변경과 검증자 변경을 함께 테스트해야 합니다. ETag를 넣는 작업과 올바른 조건부 응답을 만드는 작업은 연결돼 있습니다.
이 정책은 열린 화면을 서버가 자동으로 갱신하게 만들지는 않습니다. 새 HTTP 요청의 캐시 동작을 정할 뿐입니다. 화면의 자동 갱신 주기나 사용자가 이미 보고 있는 데이터의 갱신은 앱의 별도 동작입니다. 신선도와 화면 갱신의 구분
세 번째 예시: 사용자별 응답은 공유 여부부터 봅니다
/me/preferences가 사용자별 화면 설정을 반환한다고 하겠습니다. 요구사항이 “브라우저 저장은 허용하지만 다른 사용자와 공유하면 안 되고, 재사용 전 변경을 확인해야 한다”면 private, no-cache가 후보입니다.반면 응답에 담긴 정보와 서비스의 보관 정책 때문에 HTTP 캐시 저장 자체를 허용하지 않기로 했다면 no-store를 검토합니다. 같은 로그인 API라도 응답 내용과 요구사항이 다르면 선택이 달라집니다. URL에 /me가 있다는 사실만으로 모든 계층이 정책을 알아서 정해 주지는 않습니다.
가상의 사용자 A와 B로 확인한다면 두 계정의 비밀값을 기록할 필요는 없습니다. 구별 가능한 비민감 테스트 설정을 준비하고, A의 응답이 B에게 재사용되지 않는지와 각 응답의 실제 Cache-Control을 확인합니다. 이 글에서는 계정이나 서버를 만들지 않았으며 이 절차는 검증 설계입니다.
private을 접근 제어로 오해해서도 안 됩니다. 서버는 요청자의 인증과 권한을 따로 확인해야 합니다. 캐시에 저장할 수 있는 주체를 제한하는 것과 누가 API를 호출할 수 있는지를 제한하는 것은 다릅니다.
헤더를 바꿨는데 이전 응답이 보이는 이유도 있습니다
서버의 새 응답에 no-store를 넣었다고 이미 저장된 모든 응답이 원격으로 삭제되지는 않습니다. 기존 캐시가 아직 재사용 가능하다고 판단해 서버에 요청하지 않으면 새 정책을 받을 기회도 없습니다. 이미 저장된 응답과 정책 변경
따라서 “방금 서버 설정을 고쳤으니 모든 브라우저와 CDN이 즉시 새 정책을 적용했다”는 결론은 성립하지 않습니다. 어떤 URL에 어떤 이전 정책이 적용됐는지, 관리하는 CDN에 별도 무효화 수단이 있는지, 정적 파일은 새 주소로 배포할 수 있는지 나눠 봐야 합니다.
또 하나는 다른 종류의 저장소입니다. 서비스 워커 등이 사용하는 Cache API는 HTTP 캐시 헤더를 자동으로 따르지 않습니다. 앱 코드가 응답을 Cache 객체에 넣고 다시 꺼내는 정책은 별도로 관리해야 합니다. no-store 하나로 이 코드의 저장 동작까지 모두 멈췄다고 판단하면 안 됩니다. Cache API의 헤더 처리
뒤로 가기로 복원한 화면도 새 HTTP 요청과 같지 않을 수 있습니다. 브라우저의 방문 기록 복원이나 bfcache는 별도 동작이므로 no-cache를 붙였다는 이유만으로 매번 서버 검증이 일어났다고 단정하지 않습니다. 방문 기록 탐색의 예외
검증은 의도한 사용 흐름에서 마칩니다
정책을 바꾼 뒤에는 응답 헤더와 요청 흐름을 함께 봅니다. 첫 방문, 같은 주소의 재요청, 실제 내용 변경 뒤 재요청을 구분하고, 개인화 응답이라면 다른 사용자에게 섞이지 않는지도 확인합니다.
개발자 도구에서 캐시를 꺼 둔 상태만으로 확인하면 일반 사용자의 재사용 동작을 놓칠 수 있습니다. 반대로 캐시를 무작정 모두 삭제하면 어떤 이전 정책이 문제였는지 비교하기 어렵습니다. 먼저 요청 주소, 기존 헤더, 현재 헤더와 응답 출처를 기록합니다.
no-cache인데 저장된 응답이 보이는 것은 이상하지 않습니다. 저장을 허용하면서 검증 후 재사용하도록 의도했다면 정상적인 설계입니다. 정말 필요한 것이 저장 금지인지, 최신성 확인인지, 사용자 간 공유 금지인지부터 정한 뒤 그 요구와 실제 요청 결과가 일치하는지 확인하면 됩니다.
728x90반응형'Programming' 카테고리의 다른 글
뒤로 가기했더니 예전 화면이 그대로 나타나는 이유: bfcache (0) 2026.09.21 304 Not Modified는 오류일까: ETag로 갱신 여부 읽기 (1) 2026.09.20 로그인 쿠키가 있는데 다른 사이트 요청에는 안 붙는 이유 (0) 2026.09.20 curl은 되는데 브라우저만 CORS 오류가 나는 이유 (0) 2026.09.20 UPDATE 전후 값을 한 번에 받고 싶다면: PostgreSQL 18 RETURNING (0) 2026.09.20