하루 16만 요청, 무료 플랜 사이트가 죽었다 — Cloudflare Error 1027 대응기
안녕하세요, 천상나타입니다.
어느 날 제 무료 도구 사이트가 통째로 안 열리는 일이 생겼어요. 서버가 죽은 게 아니라, Cloudflare Workers 무료 플랜의 일일 요청 한도를 넘어버린 거였습니다. 화면에 뜬 건 Error 1027. 오늘은 한도가 왜 터졌는지 파고들어서 실제로 고친 과정을 정리해봤습니다.
Error 1027이 뭔데 사이트가 통째로 죽나
Cloudflare Workers 무료 플랜은 하루 100,000 요청까지만 허용됩니다. 이걸 넘으면 다음 리셋(UTC 0시, 우리 시간으로 오전 9시)까지 방문자 전원이 에러 화면만 보게 돼요.
제 사이트는 Workers + 정적 에셋 구성이라 별도 오리진 서버가 없습니다. 캐시가 있어도 우회할 곳이 없어서 한도 초과 = 완전한 다운이에요. 호스팅비 0원의 대가치고는 꽤 아픈 순간이었습니다.
대시보드를 열어보니 상황은 이랬어요.

호출 165,000건. 한도의 1.65배였고, CPU 시간도 중앙값 52ms로 한도(10ms)를 5배 넘겼습니다. 참고로 무료 플랜은 CPU 초과가 잦으면 Error 1102로 또 죽어요. 두 개 다 빨간불이었습니다.
이상한 점 — 사람이 온 게 아니었다
여기서 의문이 생겼습니다. 제 사이트에 하루 16만 명이 오는 게 아니거든요.
대시보드 수치를 뜯어보니 이상한 비율이 보였어요. 페이지 요청은 165k인데, 브라우저가 실제로 받아간 에셋(JS·CSS·이미지) 요청은 하루 3,400건뿐이었습니다.
사람이 페이지 하나를 열면 JS·CSS 파일을 수십 개씩 받아갑니다. 비율로 따지면 페이지 1건당 에셋 수십 건이 나와야 정상인데, 165k 대 3.4k면 뭔가가 JS를 안 받고 HTML만 긁어갔다는 뜻이에요. 크롤러였습니다.
그리고 때마침 제가 5개 언어로 페이지를 1,574개까지 늘려놓은 직후였어요. 사이트맵도 새로 등록했고요. 봇 입장에서는 갓 깐 뷔페가 열린 셈이죠.
"자산 요청 165k"의 함정
대시보드에 "자산 요청 165k"가 찍혀있어서 처음엔 정적 파일 때문인 줄 알았어요. 그래서 _next/static 파일들을 R2(별도 스토리지)로 옮기는 안까지 검토했습니다.
그런데 파보니 이 수치의 대부분은 Worker 내부의 캐시 읽기였어요. 제 구조에서 페이지 요청이 오면 Worker가 떠서, 캐시해둔 프리렌더 HTML을 에셋 바인딩에서 꺼내 응답합니다. 그 내부 읽기가 "자산 요청"으로 같이 잡히는 거죠.

정리하면 이렇습니다.
- 진짜 정적 파일 (
_next/static, 이미지) — 에셋이 직접 응답. 무료·무제한. Worker도 안 거침 - 모든 페이지 HTML/RSC 요청 — Worker 호출 1건 차감. 캐시가 있어도 마찬가지
즉 R2로 파일을 옮겨봤자 한도에 잡히는 페이지 요청은 그대로라는 결론이 나왔어요. 검토하다가 기각한 안이라 따로 적어둡니다 — 같은 착각을 하기 쉬울 것 같아서요.
CPU 52ms의 범인들
캐시된 HTML을 읽는 건 5ms면 끝납니다. 중앙값이 52ms라는 건 무거운 작업이 끼어있다는 뜻이었어요.

범인은 셋이었습니다.
1. 동적 OG 이미지. 미리보기 썸네일을 요청할 때마다 Worker에서 PNG를 렌더링하고 있었어요. 이미지 렌더링은 10ms를 훌쩍 넘는 무거운 작업인데, 사이트맵과 RSS에 이 주소가 노출돼 있어서 봇이 계속 가져가고 있었습니다. CPU 초과의 주범.
2. Link 프리페치. Next.js의 <Link>는 화면에 보이기만 해도 다음 페이지를 미리 받아둡니다. 카드 30개짜리 목록을 스크롤하면 사람 하나가 RSC 요청을 수십 건 만들어요.
3. 열린 동적 파라미터. /calc/연봉/[숫자] 같은 라우트에 목록 밖의 값을 넣어도 페이지가 생성됐는데, 캐시에 없으니 매번 SSR이 돌았습니다. 봇이 임의 파라미터로 때려 넣으면 무한정 SSR이 생기는 구조였어요.
취한 조치들
핵심은 하나였습니다. Worker까지 도달하는 요청 자체를 줄이자.

코드 없이 대시보드에서 막은 것
- Bot Fight Mode + "AI 스크래퍼·크롤러 차단" 켜기
- WAF 무료 규칙 5개로 과도한 봇 UA 차단 — Bytespider, GPTBot, CCBot 같은 애들
차단된 요청은 Worker까지 오지도 않고 호출 수에도 안 잡힙니다. 다만 Googlebot, Yeti 같은 검색엔진은 건드리지 않았어요. SEO가 목적인 사이트에서 검색봇을 막으면 본전도 못 찾으니까요.
코드로 줄인 것
- OG 이미지를 빌드할 때 PNG로 미리 만들어 정적 파일로 교체 — Worker에서 이미지를 렌더링하는 코드를 통째로 지웠습니다
<Link>전체에prefetch={false}— 76곳 일괄 적용- 동적 라우트 30개에
dynamicParams = false— 목록 외 파라미터는 이제 404 robots.txt에/api/와 커뮤니티 글쓰기·수정 경로 Disallow, Bing·Yandex용 Crawl-delay
보너스 삽질 — 배포 명령이 틀려있었다
조사하다가 더 황당한 걸 발견했어요. 제 배포 스크립트가 wrangler deploy를 직접 호출하고 있었는데, 이 방식은 프리렌더 캐시를 에셋에 심는 단계(populateCache)를 건너뜁니다.
이 상태로 배포하면 캐시가 없어서 모든 페이지가 매 요청 SSR돼요. 로컬에서 재어보니 캐시 있으면 5ms, 없으면 210ms. 제가 올려둔 "무료 최적화"가 실제로는 최악의 상태로 배포되고 있었던 거죠. 배포 명령을 opennextjs-cloudflare deploy로 바꾸는 것만으로도 CPU가 확 줄었습니다.
결과와 한계
조치 후 호출 수는 안정권으로 내려왔고, CPU 중앙값도 10ms 언저리로 돌아왔습니다. 무엇보다 봇 차단 요청은 과금에 안 잡힌다는 걸 안 게 컸어요.
다만 근본적인 한계는 남습니다. 페이지 요청이 전부 Worker를 거치는 구조 자체는 못 바꿨거든요. 트래픽이 더 커지면 선택지는 둘뿐입니다 — HTML을 정적 에셋으로 직접 서빙하도록 구조를 뜯어고치거나, 월 $5짜리 유료 플랜으로 넘어가거나(1천만 요청/월). 저는 일단 무료로 버티는 쪽입니다.
정리
- Workers Free는 일 10만 요청 — 넘으면 리셋까지 사이트 전체가 1027
- "자산 요청" 수치는 내부 캐시 읽기가 섞여 있으니 그대로 믿지 말 것
- 봇 차단(Bot Fight Mode·WAF)은 Worker 호출 수에 잡히지 않아 효과가 가장 큼
- 동적 이미지 생성·프리페치·열린 동적 파라미터는 무료 플랜에서 독
- 배포 도구가 캐시를 심고 있는지도 꼭 확인 — 저는 안 그랬어요
무료 플랜은 한도가 빡빡한 대신, 한도 안에서 버티는 설계를 강제합니다. 덕분에 내 사이트 요청이 어디서 오고 어디로 새는지 끝까지 파보게 됐어요. 트래픽 적은 개인 사이트를 공짜로 굴리는 분들께 참고가 됐으면 합니다.
'IT > Server' 카테고리의 다른 글
| 사람 없이 구글 API를 쓰는 법 — 서비스 계정과 OAuth 이해하기 (0) | 2026.11.14 |
|---|---|
| Bitbucket 전용 SSH Key 생성 & 설정 방법 (기존 키 유지) (0) | 2026.01.10 |
| 왜 Forward Proxy / Reverse Proxy라고 부를까? (0) | 2025.12.25 |
| HTTPS 프록시의 핵심, CONNECT 메서드란? (0) | 2025.12.25 |
| HTTP 메서드 정리: GET, POST 말고도 뭐가 있을까? (0) | 2025.12.25 |