보안 설정 하나가 블로그 캐시를 꺼 버린 사흘 대표 이미지
직접 운영 기록 · 웹 보안과 캐시

보안 설정 하나가 블로그 캐시를 꺼 버린 사흘: CSP nonce와 Cloudflare 캐시, 실측으로 본 대가관찰만 하는 보안 설정이 모든 방문을 원본 서버로 보냈다. 어떻게 찾았고, 무엇을 껐고, 둘 다 지키려면 무엇이 필요한지 숫자로 정리했다

2026년 10월 22일 오전, 이 블로그에 콘텐츠 보안 정책(CSP)을 단계적으로 적용하는 첫 단계로 ‘관찰 모드’를 켰다. 스크립트를 막지는 않고, 막아야 할 것이 있는지 기록만 하는 설정이다. 위험할 게 없어 보였다.

그런데 이 설정과 함께 사이트의 페이지 캐시가 통째로 꺼졌다. 그 뒤 사흘 동안 모든 방문이 Cloudflare를 그냥 지나쳐 원본 서버까지 갔다. 이 글은 그 사흘의 기록이다. 무엇이 캐시를 껐는지, 어떻게 알아챘는지, 응답 시간이 얼마나 달랐는지 실제로 잰 숫자로 적었다.

직접 운영 기록기록 기간 2026.10.22~10.26·읽는 시간 8분
바쁘다면 이 세 줄
  • 응답마다 새 일회용 값(nonce)을 넣는 CSP는 같은 페이지를 여러 사람에게 돌려쓰는 캐시와 맞지 않는다. 그래서 적용과 함께 모든 페이지의 캐시가 꺼졌다.
  • 10월 26일 밤 실측으로 캐시에서 나간 페이지는 대부분 0.1초 안팎, 원본 서버까지 간 페이지는 0.65~1.22초가 걸렸다. 그동안 관찰 모드가 모은 정보는 거의 없었다. 보고를 받을 수집기를 두지 않았기 때문이다.
  • 관찰 모드를 끄고 캐시를 되살렸다. 실제로 적용되는 기본 보안 헤더는 그대로 두었다. CSP와 캐시를 함께 쓰려면 nonce를 엣지에서 바꿔 넣거나, 해시 방식으로 가야 한다.

01CSP와 nonce, 쉽게 말하면

CSP(콘텐츠 보안 정책)는 웹페이지가 브라우저에게 보내는 안내문이다. “이 페이지에서는 이런 스크립트만 실행해도 된다”고 적어 둔다. 누군가 글이나 댓글에 몰래 스크립트를 끼워 넣어도, 안내문에 없는 스크립트라면 브라우저가 실행하지 않게 하는 장치다.

nonce는 그 안내문에 쓰는 일회용 암호다. 서버가 응답을 보낼 때마다 새 값을 하나 만들어 안내문에 적고, 페이지 안의 진짜 스크립트에도 같은 값을 붙인다. 브라우저는 값이 맞는 스크립트만 실행한다. 공연장 손목띠 색을 매일 바꾸는 것과 비슷하다. 오늘 색을 모르면 들어올 수 없다.

관찰 모드(Report-Only)는 이 안내문을 ‘경고만 하는’ 상태로 보내는 방법이다. 규칙에 어긋나는 스크립트가 있어도 막지 않고 기록만 남긴다. 실제로 막기 전에 사이트가 깨지지 않는지 확인하는 단계로 많이 쓴다.

02왜 캐시가 꺼졌나

문제는 “응답마다 새 값”이라는 부분이었다. Cloudflare 같은 캐시는 한 번 만든 페이지를 저장해 두고 다음 방문자에게 그대로 다시 준다. 그러면 모든 방문자가 같은 nonce를 받게 된다. 손목띠 색이 며칠째 그대로인 셈이다. 누군가 그 값을 알아내면, 끼워 넣은 스크립트에 같은 값을 붙여 검사를 통과할 수 있다.

그래서 적용 방식은 캐시를 아예 끄는 쪽을 택했다. 모든 페이지가 “저장하지 말라”는 응답 헤더(Cache-Control: private, no-store, CDN-Cache-Control: no-store)를 보냈고, Cloudflare에도 이미지·CSS 같은 정적 파일을 뺀 모든 주소의 캐시를 끄는 규칙이 추가됐다. 적용 기록에도 “원본 서버 요청량과 응답 시간이 늘 수 있다”는 경고가 적혀 있었다. 경고는 있었지만, 그 비용을 얼마 동안 감수할지는 정해 두지 않았다.

핵심

응답마다 바뀌는 nonce와 같은 응답을 돌려쓰는 캐시는 원리상 부딪힌다. 둘 중 하나를 포기하거나, 둘을 이어 주는 장치가 따로 필요하다.

03어떻게 알아챘나

이 블로그에는 3시간마다 돌면서 페이지를 캐시에 미리 채워 두는 자동 점검이 있다. 보고서에는 매번 이런 줄이 찍힌다. “페이지 94개 (이미 캐시됨 0, 이번에 채움 94, 오류 0)”. 캐시가 살아 있다면 ‘이미 캐시됨’이 대부분이어야 하는데, 0이 찍히고 있었다.

그 숫자를 바로 보지 못한 이유가 있다. 그 무렵 이 점검은 다른 이유의 오경보 때문에 매번 실패로 끝나고 있었다. 실패 메일이 계속 오다 보니 보고서 안의 숫자 한 줄까지 읽는 사람이 없었다. 10월 24일 밤, 점검 도구를 고치던 AI 에이전트가 보고서를 끝까지 읽고 “페이지가 캐시되지 않는다”고 짚어 냈다. 응답 헤더를 직접 확인해 보니 홈, 글, 소개 페이지 모두 cf-cache-status: DYNAMIC, 즉 캐시를 거치지 않고 있었다.

원인을 거슬러 올라가자 10월 22일의 CSP 관찰 모드 적용 기록이 나왔다. ‘저장하지 말라’는 응답 헤더와 Cloudflare 규칙 하나가 모든 페이지의 캐시를 끄고 있었다.

04대가를 재 보니

캐시를 되살린 뒤인 10월 26일 밤, 토론토에서 같은 페이지를 두 방식으로 세 번씩 불러 첫 바이트가 도착하는 시간을 쟀다. 하나는 캐시에서 받는 일반 방문, 하나는 캐시를 일부러 건너뛰어 원본 서버까지 가는 요청이다.

페이지 (압축 크기)캐시에서 받을 때원본 서버까지 갈 때
홈 (약 104KB)0.05~0.08초0.72~1.22초
클로드 듀엣 글 (약 160KB)0.07~0.22초0.65~0.78초
하네스 글 (약 168KB)0.07~0.22초0.65~0.69초

캐시가 비어 있던 글을 처음 불렀을 때는 1.8초가 걸렸다. 문제가 생기기 전인 10월 22일 오전에 쟀을 때는 캐시를 거치지 않은 요청이 1.6~6.7초까지 걸렸다. 원본 서버가 붐빌 때는 훨씬 느려진다는 뜻이다.

시간만의 문제도 아니다. 캐시가 꺼져 있으면 방문 한 번마다 압축 기준 100~170KB가 원본 서버에서 나간다. 이 블로그의 호스팅은 하루 트래픽 한도가 있는 요금제라, 방문이 몰리면 그만큼 한도에 빨리 닿는다. 단순 계산으로 1GB면 페이지를 6천~1만 번 보낼 수 있는 양이다. 검색 로봇과 미리보기 요청까지 더하면 실제 여유는 더 작다.

반대로 사흘 동안 관찰 모드가 알려 준 것은 거의 없었다. 관찰 결과를 받아 모아 둘 수집 주소를 정하지 않았기 때문이다. 위반 기록은 각 방문자 브라우저의 개발자 도구에만 잠깐 떴다 사라졌다.

05무엇을 끄고, 무엇을 남겼나

10월 25일 오후, 두 가지만 껐다. 관찰 모드 CSP를 넣던 워드프레스 스니펫을 비활성화했고, Cloudflare의 HTML 캐시 우회 규칙을 비활성화했다. 둘 다 지우지 않고 꺼 두기만 해서, 언제든 다시 켤 수 있다.

남긴 것도 분명히 했다. 실제로 적용되는 기본 CSP(페이지를 다른 사이트 안에 넣지 못하게 하는 규칙, 폼을 다른 주소로 보내지 못하게 하는 규칙 등)와 다른 보안 헤더는 그대로다. 관찰 모드를 위해 손본 스니펫 두 개도 워드프레스 표준 함수를 쓰고 있어서 그대로 두었다.

끈 뒤에는 이렇게 확인했다.

  • 원본 서버가 더 이상 “저장하지 말라” 헤더를 보내지 않는다.
  • 같은 페이지를 두 번 부르면 두 번째부터 캐시에서 나간다.
  • 사이트맵에 있는 공개 주소 63개를 한 번씩 불러 캐시를 채웠다. 문제가 생기기 전의 오래된 사본이 남아 있는 주소는 없었다.
  • 로그인한 관리자 화면과 검색 결과 페이지는 지금도 캐시하지 않는다.
  • 광고와 방문 분석 코드는 그대로 페이지에 있다.

06CSP와 캐시, 둘 다 지키려면

보안을 포기하자는 이야기가 아니다. 방법은 있다. 다만 이 블로그는 아직 아래 어느 쪽도 적용하지 않았다.

  • 엣지에서 nonce 바꿔 넣기. 캐시에는 자리표시용 값으로 저장하고, 방문자에게 나가기 직전에 Cloudflare Workers 같은 엣지 코드가 응답마다 새 값으로 바꿔 넣는다. 캐시와 일회용 값이 함께 산다. 대신 엣지 코드를 관리해야 한다.
  • 해시 방식. 허용할 인라인 스크립트의 내용으로 만든 지문(해시)을 CSP에 적는다. 내용이 같으면 지문도 같아서 캐시해도 된다. 대신 스크립트를 고칠 때마다 지문도 고쳐야 한다.
  • 관찰부터 다시 할 때는 수집기와 기간을 먼저. 보고를 받을 주소를 정하고, 관찰 기간을 정하고, 그동안의 비용을 감수할지 미리 정한다.

07배운 것 5가지

  1. 보안 헤더를 바꾼 날에는 캐시 상태를 본다. 응답 헤더의 cf-cache-status 한 줄이면 된다. HIT가 아니라 DYNAMIC이면 캐시가 일하지 않는 것이다.
  2. 관찰 모드도 공짜가 아니다. 막지 않는다는 말이 비용이 없다는 뜻은 아니었다.
  3. 수집기 없는 관찰은 관찰이 아니다. 결과를 모을 곳이 없으면 비용만 남는다.
  4. 보고서의 숫자 한 줄을 읽는다. “이미 캐시됨 0”은 사흘 동안 매번 찍혀 있었다. 실패 알림이 반복되면 이런 줄이 가장 먼저 묻힌다.
  5. 되돌리는 방법을 먼저 적어 둔다. 이번 적용 기록에는 무엇을 끄면 원래대로 돌아가는지가 적혀 있었고, 덕분에 두 군데만 끄고 바로 복구할 수 있었다.

같은 달 운영하며 겪은 다른 사고들은 AI 에이전트에게 블로그 운영을 맡긴 한 달에, 점검 도구가 오경보를 낸 이야기는 자동 사실 검사기 한 달 기록에 정리했다.

이 글의 근거와 범위

이 글은 이 블로그의 운영 기록, CSP 적용 기록, 자동 점검 보고서, 그리고 10월 26일 밤(토론토 시간) 직접 잰 응답 시간을 바탕으로 썼다. 측정은 한 지역에서 페이지마다 세 번씩 한 작은 표본이라, 다른 지역이나 다른 시간대에서는 숫자가 달라질 수 있다. 보안상 실제 정책 전문과 서버 설정의 세부 내용은 싣지 않았다.

단단한 인생 / Dandan Life · 개인 AI 기술 블로그 / Independent AI technology journal