Cloudflare 무료 플랜과 한국 엣지: TTFB 1초의 원인 분해
데모 사이트 하나가 느렸습니다. 첫 바이트까지 1초가 걸렸습니다. 대시보드를 여는 화면이라 무겁긴 한데, 그래도 1초는 이상했습니다. 앱을 뜯어볼 생각으로 시작했다가, 결국 앱은 한 줄도 안 고쳤습니다.
앱은 0.1초였습니다
먼저 오리진 서버에서 직접 재봤습니다. 같은 페이지를 내부에서 부르면 이랬습니다.
TTFB 0.081s / 0.131s / 0.110s0.1초 언저리입니다. 공개 주소로는 1.0초가 나오는데 말이죠. 앱이 느린 게 아니라 오는 길이 느린 것이었습니다.
공개 경로를 구간별로 쪼개보니 이렇게 나왔습니다.
DNS 0.003s
TCP 0.177s
TLS 0.349s
TTFB 0.998s여기서 두 덩어리가 보입니다. 연결(TCP+TLS)에 0.35초, 그다음 응답이 돌아오기까지 0.65초입니다. 그리고 그 0.65초 중 실제 렌더링은 0.1초뿐이니, 나머지 0.55초는 순수하게 이동 시간입니다.
1초 중 0.9초가 네트워크였습니다.
응답 헤더에 답이 있었습니다
Cloudflare 를 통과하는 요청에는 cf-ray 헤더가 붙고, 끝에 어느 데이터센터를 거쳤는지가 적힙니다.
cf-ray: ...-LAXLAX. 로스앤젤레스입니다. 한국에서 접속했는데 미국 서부를 찍고 다시 한국의 오리진으로 온 겁니다. 태평양을 두 번 건넌 셈이니 0.55초가 설명됩니다.
혹시 이 호스트만 그런가 싶어 프록시를 거치는 다른 주소들도 확인했습니다.
호스트 A TTFB 1.00s LAX
호스트 B TTFB 0.94s LAX
호스트 C TTFB 0.96s LAX
호스트 D TTFB 0.73s LAX
호스트 E TTFB 0.49s LAX전부 LAX 였습니다. 특정 앱 문제가 아니라 계정 전체에 걸린 문제였습니다.
여기서 두 번째로 틀렸습니다
저는 이걸 플랜을 올리면 풀리는 문제라고 생각했습니다. 유료로 바꾸면 가까운 엣지를 쓰게 해주겠지, 하고요.
찾아보니 아니었습니다. 같은 증상 신고가 Cloudflare 커뮤니티에 계속 올라와 있는데, 무료뿐 아니라 Pro 와 Business 에서도 한국 엣지가 활성화되지 않습니다. 흥미로운 건 같은 회선에서 cloudflare.com 자체는 서울(ICN)로 간다는 점입니다. 망 문제가 아니라 플랜별 정책입니다.
한국은 국내 피어링 비용이 비싸기로 유명한 시장이라 짐작 가는 바는 있지만, 공식적인 설명은 못 찾았습니다.
어쨌든 결론은 분명했습니다. 터널로 노출하는 한, 돈을 더 내도 이 경로는 안 바뀝니다.
그래서 서울에 대문을 하나 세우기로 했습니다
선택지를 늘어놓고 봤습니다.
| 방식 | 비용 | 효과 |
|---|---|---|
| 오리진을 직접 노출 | 0원 | 최대. 대신 회선 조건과 노출면 문제 |
| 서울 VPS 를 앞에 세우고 터널로 잇기 | 월 $6 | 국내 경로만 사용 |
| Cloudflare Argo | 월 $5 + 전송량 | 중간 구간만 개선, 접속 0.35초는 잔존 |
| 앱까지 통째로 클라우드 이전 | 월 $12 이상 | 위보다 나을 게 없음 |
마지막 줄이 조금 의외였습니다. 처음엔 앱을 통째로 옮기는 게 정석이라고 생각했는데, 계산해 보니 아니었습니다. 렌더링이 이미 0.1초이고 DB 가 앱 바로 옆에 있는 상황이라, 앱만 옮기면 DB 를 부를 때마다 국내 왕복이 쿼리 수만큼 곱해집니다. 대문만 세우면 HTTP 왕복 한 번만 국내를 지납니다. 쿼리를 여러 번 타는 대시보드일수록 대문 쪽이 유리합니다.
참고로 리전 간 거리도 재봤습니다. 한국에서 AWS 각 리전의 퍼블릭 엔드포인트에 TCP 연결을 걸어본 값입니다.
| 리전 | 중앙값 |
|---|---|
| 서울 | 19.9ms |
| 도쿄 | 49.7ms |
| 싱가포르 | 80.2ms |
측정 회선의 라스트마일이 15ms 정도였으니, 서울에 있는 서버 기준으로는 대략 서울 2ms, 도쿄 30ms, 싱가포르 65ms 입니다. 관리형 DB 를 고를 때도 이 표가 그대로 쓰입니다.
공짜 서버를 고르려다 말았습니다
$6 도 아끼고 싶어서 무료 티어를 먼저 봤습니다. 4 OCPU, 24GB RAM, 월 10TB 전송을 0원에 주는 곳이 있습니다. 스펙만 보면 압도적입니다.
약관을 읽다가 멈췄습니다. 유휴 인스턴스 회수 정책이 있었습니다.
7일간
CPU 사용률(95 백분위) < 20%
네트워크 사용률 < 20%
메모리 사용률 < 20%리버스 프록시가 정확히 이 프로필입니다. TLS 를 종료하고 바이트를 넘기는 데 쓰는 CPU 는 사실상 0% 입니다. 고객에게 보여줄 데모의 정문이 유휴로 판정돼 조용히 회수되면, 그게 첫인상이 됩니다.
무료 티어는 부하가 꾸준하고 죽어도 되는 곳에 어울립니다. 대문은 그 반대 성격이라 맞지 않았습니다. 월 $6 을 내기로 했습니다.
콘솔은 안 판다고 했는데 API 는 팔았습니다
플랜을 고르는데 콘솔이 계속 이렇게 말했습니다.
This plan is unavailable in this location.서울을 다시 선택해도, 다른 계열로 바꿔도 같았습니다. 마침 그 사업자는 공개 API 로 리전별 재고를 조회할 수 있어서 직접 물어봤습니다.
서울 현재 재고: 87개 플랜
...-1gb-amd 재고 있음
...-1gb-intel 재고 있음있다고 나왔습니다. 그래서 API 로 그대로 생성 요청을 보냈더니 아무 문제 없이 만들어졌습니다. 콘솔 쪽 표시 문제였던 것 같습니다.
이런 경우가 종종 있습니다. 웹 UI 가 막을 때 API 를 한 번 찔러보면 답이 다를 수 있습니다.
WireGuard 를 준비했다가 SSH 터널로 갔습니다
대문과 오리진을 잇는 건 WireGuard 로 할 생각이었습니다. 양쪽 설정 파일까지 미리 써뒀습니다.
그런데 막상 올리려니 몇 가지가 걸렸습니다. wg-quick 은 네트워크 인터페이스를 새로 만들기 때문에 오리진 쪽에서 관리자 권한이 필요합니다. 상시 실행도 시스템 레벨 서비스로 등록해야 합니다. 커널에 모듈이 없는 환경이면 사용자 공간 구현을 따로 설치해야 하는 것도 일이었습니다. 대문 하나 세우자고 오리진의 권한 설정과 부팅 구성을 건드리는 게 과하다는 생각이 들었습니다.
방향을 바꿔서 리버스 SSH 터널로 갔습니다.
오리진에서 실행:
ssh -N -R <포트>:127.0.0.1:<포트> tunnel@<대문서버>오리진이 대문 쪽으로 나가서 연결을 유지하면, 대문의 127.0.0.1 에 오리진의 서비스가 나타납니다. 방향이 밖에서 안으로가 아니라 안에서 밖으로라, 오리진에 인바운드 포트를 열 필요가 없습니다. 고정 IP 도 필요 없습니다. 터널 방식의 장점을 그대로 가져가면서 root 권한만 빠집니다.
대문 쪽 계정도 최소 권한으로 잡았습니다. 셸을 못 쓰게 하고 포트 포워딩만 허용했습니다.
no-pty,no-agent-forwarding,no-X11-forwarding,command="/bin/false" ssh-ed25519 ...상시 실행은 사용자 권한으로 도는 서비스 관리자에 맡겼습니다. 프로세스가 죽으면 자동으로 다시 띄우도록 해두면 회선이 끊겨도 알아서 붙습니다. 여기도 관리자 권한이 필요 없습니다.
대신 포기한 것도 있습니다. WireGuard 는 인터페이스 하나를 올리면 대역 전체가 통하는데, SSH 터널은 포트마다 -R 을 하나씩 걸어야 합니다. 호스트가 늘면 그만큼 줄도 늘어납니다. 지금 규모에서는 그게 부담이 아니라서 단순한 쪽을 골랐습니다. 서비스가 수십 개가 되면 다시 WireGuard 를 볼 것 같습니다.
앞단은 Caddy 를 썼습니다. 인증서를 알아서 받고 갱신하기 때문입니다. 설정은 이게 전부입니다.
example.duolabs.co.kr {
reverse_proxy 127.0.0.1:<포트>
}호스트를 추가할 때 블록만 늘리면 되고 인증서를 따로 관리하지 않아도 됩니다. 익숙한 nginx 로 같은 걸 하려면 server 블록 25줄에 certbot 설치와 갱신 크론이 붙습니다. 도구를 하나 더 늘리는 비용은 분명히 있지만, 호스트가 여러 개가 될 예정이라 이쪽을 골랐습니다.
터널을 세우고 잰 값
DNS 를 바꾸기 전에 대문에서 오리진 서비스를 먼저 불러봤습니다.
대문 → 터널 → 오리진 서비스 200, TTFB 0.075s / 0.087s오리진 내부에서 직접 부를 때가 0.1초 언저리였으니, 터널이 더한 비용은 사실상 없습니다. 국내 구간이라 그렇습니다. TCP 위에 TCP 를 얹는 구조라 오버헤드를 걱정했는데, 이 거리에서는 드러나지 않았습니다.
여기까지 보고 방문자 기준으로는 0.15초쯤 나오겠다고 예상했습니다.
DNS 를 바꾸자마자 인증서에서 막혔습니다
레코드를 대문 쪽으로 돌리고 바로 열어봤더니 이 화면이 떴습니다.
ERR_SSL_PROTOCOL_ERROR로그를 보니 원인이 분명했습니다. Caddy 는 서비스가 뜨는 순간 인증서를 받으러 갑니다. 그때는 아직 DNS 를 안 바꿨을 때라 이렇게 실패해 있었습니다.
DNS problem: NXDOMAIN looking up A for ...문제는 실패한 뒤 재시도까지 300초를 기다린다는 점입니다. DNS 를 고쳐도 그 대기가 남아 있어서, 가만히 두면 5분 동안 접속이 안 됩니다. 서비스를 재시작해 대기를 초기화하니 바로 발급됐습니다.
certificate obtained successfully순서를 반대로 했으면 안 겪었을 일입니다. DNS 를 먼저 바꾸고 그다음에 앞단을 올렸으면 첫 시도에 통과했을 겁니다. 무중단이 필요하면 DNS 검증 방식으로 인증서를 미리 받아두는 방법도 있습니다.
결과
전환 전 TCP 0.177s | TLS 0.349s | TTFB 0.998s | cf-ray: ...-LAX
전환 후 TCP 0.024s | TLS 0.058s | TTFB 0.134s | cf-ray 없음| 구간 | 전 | 후 |
|---|---|---|
| TCP 연결 | 0.177s | 0.024s |
| TLS 완료 | 0.349s | 0.058s |
| 첫 바이트 | 0.998s | 0.134s |
1초가 0.13초가 됐습니다. 예상한 0.15초보다 조금 더 나왔고, cf-ray 헤더가 사라진 것으로 우회도 확인했습니다. 측정은 모두 국내 유선 회선에서 했습니다.
참고로 같은 도메인에서 서울 리전의 관리형 플랫폼에 배포된 다른 사이트가 0.19초입니다. 대문을 세운 쪽이 그보다 빠르게 나왔는데, 그쪽은 요청마다 함수를 깨우는 구조이고 이쪽은 상시 떠 있는 프로세스라 그 차이일 겁니다. 표본이 적어서 단정하지는 않겠습니다.
앱은 한 줄도 안 고쳤습니다.
남은 문제
이 구성은 속도만 해결합니다. 오리진이 죽으면 대문이 살아 있어도 502 입니다. 가용성은 그대로 남아 있고, 그건 다른 방법으로 풀어야 합니다.
그리고 Cloudflare 를 앞에서 걷어냈으니 거기서 무료로 받던 DDoS 완화도 함께 사라집니다. 다만 확인해 보니 무료 플랜에서 받던 건 관리형 룰셋의 부분집합이었고, 전체 룰셋은 원래 상위 플랜에서만 제공됩니다. 잃는 것이 생각보다 적었습니다. 대문 쪽에서 레이트리밋과 방화벽으로 메우고, 보호가 더 중요한 호스트는 프록시를 유지하기로 했습니다. 전부 옮길 필요는 없고 DNS 레코드 단위로 정하면 됩니다.
측정부터 하길 잘했다고 생각합니다. 앱을 먼저 뜯었으면 0.1초짜리를 붙들고 며칠을 보냈을 겁니다.
함께 읽기
- ingress 와 egress: 터널 설정과 청구서에서 만난 두 방향하루에 두 번 이 단어들을 만났습니다. 오전에는 터널 설정 화면에서 ingress 를 봤고, 오후에는 데이터베이스 청구 항목에서 egress 를 봤습니다. 몇 년째 둘 다 쓰고 있었는데, 그날에서야 같은 축의 양쪽이라는 게 눈에 들어왔습니다.
- Vercel Web Analytics 요금과 설치: Enable 버튼만으로는 안 되는 이유회사 홈페이지의 방문자 수를 Vercel 대시보드에서 보려고 Web Analytics를 켰습니다. Enable을 누르고 잠시 기다렸는데 지표가 0이었습니다. 스크립트를 넣어야 한다는 걸 그때 알았고, 넣고 배포한 뒤에도 한 번 더 헷갈렸습니다. 그 과정을 정리했습니다.
- 클라우드와 DevOps 핵심 개념 지도: 인프라에서 배포와 관측성까지클라우드와 DevOps를 제품 목록으로 익히면 비슷한 기능을 가리키는 이름이 계속 늘어납니다. 가상 머신, 컨테이너, 서버리스, CI/CD와 Kubernetes는 같은 층의 대안이 아닙니다. 어떤 것은 실행 환경이고, 어떤 것은 배포 흐름이며, 어떤 것은 원하는 상태를 유지하는 제어 시스템입니다.
- S3와 R2를 다시 비교해보니, 저장비보다 CDN 경로가 더 중요했습니다이 글의 가격과 제품 조건은 2026년 8월 4일 공식 문서 기준입니다.
- 배포하면 CSS가 404: stale-while-revalidate 함정"모바일에서 사이트가 이상해요."