Cloudflare Pages에서 삭제한 파일이 배포별 URL에 남아있음 — 404를 확인해도 사라지지 않았던 현상
요약
Cloudflare Pages에서 파일을 삭제하고 재배포했음에도 불구하고, 과거 배포 시 생성된 해시 기반의 URL(Preview Deployment)을 통해 이전 원고 내용이 여전히 접근 가능한 문제가 발생했습니다. 이는 Cloudflare가 모든 배포를 '당시의 완전한 사본'으로 보존하기 때문에 발생하는 정상적인 동작입니다.
핵심 포인트
- Cloudflare Pages는 각 배포 시점의 내용을 해시 URL로 영구 보존합니다.
- 실제 최신 URL은 항상 가장 최근 배포만 반영하며, 과거 배포 내용은 별도의 해시 URL을 통해 접근 가능합니다.
- 파일 삭제 후 재배포를 해도 이전 배포 기록(해시 URL)이 남아있어 내용 유출 위험이 있습니다.
AI(Claude Code)에게 블로그 운영을 맡기고 있습니다. 그 과정 중에 공개 전 원고를 임시적으로 제 사이트에 올리는 단계가 있습니다.
파일을 사용한 후에는 삭제하고, 다시 배포합니다. 그리고 나서 실제 URL에서 404 에러가 뜨는 것까지 확인했습니다. 매뉴얼에도 그렇게 적혀 있었고, 매번 그대로 진행했습니다.
2026년 10월 12일, 그래도 삭제했다고 생각했던 원고가 다른 URL에서 아직 읽을 수 있다는 것을 알게 되었습니다. Cloudflare Pages는 배포할 때마다 고유한 URL을 발행합니다. 이 URL은 나중에 새로운 배포를 하더라도 당시의 내용을 계속 반환합니다.
이 글은 무엇이 남아 있었는지, 어떻게 확인했고, 어떻게 수정했는지에 대한 기록입니다.
어떤 작업을 했는가
note(블로그 서비스)의 아티클을 예약 게시하는 작업을 브라우저 조작으로 AI에게 맡기고 있습니다. note 편집 화면에 본문을 입력할 때, 본문의 HTML을 브라우저 측 스크립트에서 가져와야 했습니다. 이 전달 과정에 제 정적 사이트(Cloudflare Pages)를 사용했습니다.
절차는 다음과 같습니다.
- 본문을 HTML로 만들고,
public/transfer/<랜덤 16자리>.html
에 배치합니다 -
wrangler pages deploy
로 배포합니다 - note 편집 화면에서 해당 URL을
fetch
하여 본문을 붙여넣습니다 - 붙여넣기가 끝나면,
public/transfer/
파일을 삭제하고, 다시 배포합니다 - 실제 URL에서 해당 파일이 404가 되는 것을 확인합니다.
파일명은 무작위이며 어디에서도 링크되지 않습니다. 5단계의 확인도 합니다. 이렇게 해서 '공개 전 원고는 외부에서 읽을 수 없다'고 생각했습니다.
무엇이 남아 있었나
10월 12일 자정 무렵, 3개의 아티클을 예약한 후, 절차대로 배포용 3개 파일을 삭제하고 재배포했습니다. 실제 URL에서는 3개 모두 404였습니다.
그 후에 문득 wrangler pages deployment list를 열어보았습니다. 배포 목록에는 1건당 https://<해시>.<프로젝트명>.pages.dev라는 URL이 나열되어 있었습니다.
이 중, 배포용 파일을 배치했을 때 생성된 URL로 같은 경로를 열어보았습니다.
curl -sL -o /tmp/t.out -w "%{http_code}\n" \
"https://<해시>.<프로젝트명>.pages.dev/transfer/<파일명>.html"
# → 200
...
200이 반환되었고, 내용은 공개 전 원고의 본문 그 자체였습니다. 실제 URL에서는 삭제되어 있었지만, 당시 배포를 가리키는 URL에서는 당시 파일이 그대로 배포되고 있었습니다.
이는 사양대로의 동작이다
Cloudflare 공식 문서(프리뷰 배포 페이지)에는 이렇게 적혀 있습니다.
These are atomic and may always be visited in the future.
(출처: https://developers.cloudflare.com/pages/configuration/preview-deployments/, 2026-10-12 확인)
배포는 하나씩 독립된 '당시의 완전한 사본'이며, 나중에 언제든지 방문할 수 있다는 의미입니다. 롤백(이전 배포로 되돌리는 기능)이 가능한 것도 오래된 배포가 내용과 함께 남아있기 때문입니다.
문서의 이 문구는 프리뷰 배포에 대한 설명입니다. 다만, 제 프로젝트는 전부 main 브랜치로부터 프로덕션(Production) 배포를 했고, 목록을 보니 프로덕션 배포에도 1건씩 해시가 붙은 URL이 할당되어 있었습니다. 실제로 200이 반환된 것도 프로덕션 배포의 URL이었습니다.
즉,
- 실제 URL(
<프로젝트명>.pages.dev)
는 항상 최신 배포를 가리킵니다 -
해시가 붙은 URL은 생성된 시점의 배포를 가리킨 채 움직이지 않습니다
라는 것입니다. 삭제하고 재배포해도 변하는 것은 전자뿐이었습니다.
얼마나 많이 남아있는지 계산했다
먼저, 손으로 확인할 수 있는 범위 전부를 확인했습니다. wrangler pages deployment list --json로 목록을 가져와 각 배포 URL에 이번 3개 파일의 경로를 문의합니다.
import json, urllib.request
deps = json.load(open("/tmp/deps.json")) # wrangler pages deployment list --json 의 출력
names = ["<파일1>", "<파일2>", "<파일3>"]
...
list
의 목록에 나온 배포는 25건이었습니다(이것이 전체인지 여부는 확인하지 못했습니다). 3개의 파일이 200을 반환한 것은 그중 단 한 건이었습니다. 파일을 배치한 후 삭제하기 전 사이에 만들어진 배포입니다. 그보다 이전과 이후의 배포에서는 세 개 모두 404였습니다.
파일이 남아 있는 경우는 '배치하는 동안에 만들어진 배포'로 한정된다는 것입니다. 생각해보면 당연하지만, 개수를 세어 확인할 때까지는 '모든 배포에 남아있는 것이 아닐까' 하고 걱정했습니다.
확인하지 못한 것들
이 배포 방식은 10월 8일과 9일에도 사용했습니다. 그때 배치했던 파일 이름은 어디에도 기록되어 있지 않았습니다. 임의의 이름이었기 때문에, 이름이 모르면 문의할 수가 없습니다.
그 시기의 배포에도 당시 원고가 남아있다고 보고 있습니다. 다만, 이것은 추측이며 확인된 바는 없습니다.
하나의 배포에 어떤 파일이 들어있는지 목록으로 출력하는 방법은 이번에는 찾을 수 없었습니다. wrangler pages deployment
의 서브 커맨드는 list
・create
・tail
・delete
의 4가지이며(wrangler 4.149.0 의 --help로 확인), 내용 목록을 출력하는 것은 없습니다.
왜 알아차리지 못했는가
절차의 5번, '운영 URL에서 404가 되는 것을 확인'하는 과정은 매번 했습니다. 확인 절차 자체는 있었지만, 확인할 장소가 단 한 곳뿐이었습니다.
전에 같은 배포용 파일이 사라지지 않았던 사고를 일으킨 적이 있습니다. 쉘 변수가 공백으로 분리되지 않아 삭제 명령어가 파일을 하나도 지우지 않은 경우입니다. 그때는 이 404 확인을 통해 알아차릴 수 있었습니다. 404 확인은 확실히 도움이 되었습니다.
다만, 404 확인이 말해줄 수 있는 것은 '운영 URL에서 사라졌다'는 것뿐입니다. '어디에서도 읽을 수 없다'는 것까지는 말해주지 못합니다. 저는 이 두 가지를 같은 것으로 취급하고 있었습니다.
사소한 부분에서, 두 가지가 걸렸다
1. .html이 없는 URL은 308로 리디렉션된다
운영 URL에서 확인했을 때, 3개 파일 중 하나는 404가 아니라 308을 반환했습니다. /foo.html
으로의 접근이 /foo
으로 전송되고 있었습니다. 전송되는 곳까지 추적하지 않으면 실제 결과를 알 수 없습니다.
curl -sL -o /dev/null -w "%{\http_code}\n" "https://.../transfer/<파일이름>.html"
-L을 붙여 전송을 추적하면, 그 파일은 404였습니다. 308은 '사라지지 않았다'는 의미가 아니라, 전송 도중에 있었다는 것뿐입니다. 반대로, 해시가 붙은 URL에서 원고를 찾았을 때도 처음에 반환된 것은 308이었고, 추적한 곳이 200이었습니다. 전송을 추적하지 않고 숫자만 보면, 어느 쪽으로든 오독할 수 있습니다.
2. 재배포 직후에는 오래된 사본이 돌아올 때가 있다
재배포 직후에 확인했을 때는 다른 하나가 200을 반환했고, 내용물도 원고 그대로였습니다(운영 URL입니다). 조금 시간을 두고 다시 문의하자 404가 되었습니다. 배포망에 오래된 사본이 잠시 남아있었던 것이라고 생각합니다(추측입니다).
따라서 지금은 전부 404가 될 때까지, 10초 간격으로 다시 문의하는 방식으로 하고 있습니다.
for i in $(seq 1 30); do
n=0
for u in "/transfer/a.html" "/transfer/a"; do
...
URL 끝에 ?v=$RANDOM을 붙이는 것은, 로컬이나 중간 사본을 피하기 위함입니다.
어떻게 고쳤는가
공개 사이트를 경유시키는 것을 그만두었다
가장 확실한 방법은, 삭제할 필요가 있는 것을 애초에 공개 장소에 두지 않는 것이었습니다.
본문 전달에 공개 사이트를 사용했던 것은, 브라우저 측 스크립트가 본문을 가져갈 곳이 필요했기 때문입니다. 그런데 이번 3개의 본문을 HTML로 만들자, 각각 3.9KB~4.9KB였습니다. 이 정도 크기라면, 브라우저에서 실행하는 스크립트 안에 본문을 문자열로 직접 삽입하여 전달할 수 있습니다.
// 지금까지: 공개 사이트에 둔 파일을 가져옴
const html = await (await fetch('https://<프로젝트명>.pages.dev/transfer/<파일이름>.html')).text();
// 다음부터: 스크립트에 본문을 직접 삽입함 (공개 사이트를 거치지 않음)
...
다음 예약부터는 이 방식으로 전환하기로 결정했습니다. 이 글을 쓴 시점에서는 아직 이 방식으로 하나도 붙인 적이 없습니다. 잘 안 되었을 경우에는 그 점도 쓰겠습니다.
넣지 않으면, 잊어버리는 것도, 배포에 남는 걱정도 없습니다.
오래된 배포를 지우는 방법은 있다
이미 남아 있는 것은 배포 단위로 지울 수 있습니다. wrangler에 pages deployment delete가 있습니다.
npx wrangler pages deployment delete <배포의 ID> --project-name <프로젝트명>
공식 명령어 레퍼런스에서는 현재 별칭이 붙어 있는 배포(운영으로 배포되는 것)를 지우려면 --force가 필요하다고 되어 있습니다 (출처: https://developers.cloudflare.com/workers/wrangler/commands/pages/, 2026-10-12 확인).
다만, 지워진 배포는 더 이상 롤백할 수 없습니다. 저는 어떤 배포를 지울지 운영 판단으로 나누어 처리하기로 했고, 이번에는 아직 지우지 않았습니다.
프리뷰를 접근 제한으로 보호하는 방법도 있다
Cloudflare에는 프로젝트 설정에서 'Enable access policy'를 활성화하면 프리뷰 배포에 인증을 걸 수 있는 기능이 있습니다. 다만, 문서에는 '프리뷰 배포만 보호한다'고 되어 있습니다. 저처럼 운영 배포의 해시 URL에 남아있는 경우도 보호되는지는 확인하지 못했습니다.
다른 사용 사례에서도 일어날 수 있는 일
이번에 남아있던 것은 며칠 후에 공개할 예정인 제 원고였습니다. 개인 정보나 인증 정보는 들어있지 않습니다.
하지만 같은 메커니즘은, 공개 디렉토리에 들어간 모든 것에 적용됩니다. 예를 들어, API 키가 들어간 설정 파일을 public/에 그대로 넣은 채 배포해 버렸다고 가정해 봅시다. 알아차리고 지우고 재배포하여 운영 URL에서 404를 확인하더라도, 그때의 배포 URL에는 남아 있습니다.
이 경우 필요한 것은 파일을 지우는 것보다 먼저, 키 자체를 무효화하고 다시 만드는 것이라고 생각합니다. '지웠다'가 아니라 '더 이상 사용할 수 없다' 상태로 만들지 않으면, 남은 사본에서 읽혔을 때 막을 수 없습니다.
가져갈 수 있는 형태로 만들기
이번부터 정한 것은 네 가지입니다.
- 지울 전제인 것은 공개 디렉토리에 두지 않는다. 두지 않으면, 남지 않습니다. 404 확인은 운영 URL뿐만 아니라, 그 파일을 둔 동안의 배포 URL에서도 진행****확인은 리다이렉트를 따라 (
curl -L
), 전부가 404가 될 때까지 반복한다** 공개하면 안 되는 것을 내보냈다면, 지우기보다 먼저 무효화한다**
AI에게 부탁할 때는 이 형태로 전달하고 있습니다.
공개 전의 글이나 설정값은, 일시적으로라도 공개 사이트 디렉토리에 두지 마세요.
인계가 필요하다면, 실행하는 스크립트에 직접 삽입하세요.
만약 공개 사이트에 둔 것을 지울 경우에는, 운영 URL뿐만 아니라,
...
마지막 문장을 넣은 것은, 이번에 제가 10월 8일과 9일 분을 확인할 수 없었던 것처럼, 확인할 수 없는 장소는 남습니다. 그것을 '사라졌습니다'라는 한 마디로 정리하게 하는 것이 중요했습니다.
AI(Claude Code)에게 개발과 운영을 맡긴 기록을 비용 0원으로 계속하고 있습니다. 읽는 방법 안내는 note의 요약에 두고 있습니다.
Discussion
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기