Cloudflare 프록시를 켰더니 오히려 느려졌다: 한국 ISP와 Free Plan의 함정


VM IP가 그대로 노출되어 있었다
커뮤니티(fullstackfamily.com)를 GCP 서울 리전 VM 하나로 운영하고 있습니다. nginx, Spring Boot, Next.js, MySQL을 Docker Compose로 묶어서 돌리는 구조입니다.
이전 작업에서 DNS를 Cloudflare로 옮기고, 이미지 저장소를 R2로 마이그레이션했는데요. 정작 웹사이트(www)와 API(api) 트래픽은 Cloudflare를 거치지 않고 VM으로 직접 들어오고 있었습니다.
[당시 상태] 브라우저 ─────────────────────────▶ VM (34.64.156.69) ├─ nginx Cloudflare는 DNS만 중계 ├─ backend (회색 구름 = DNS-only) ├─ frontend └─ mysql dig www.fullstackfamily.com → 34.64.156.69 VM의 실제 IP가 그대로 노출!
SSL 인증서도 시한폭탄이었습니다. Let's Encrypt 와일드카드 인증서의 DNS-01 챌린지를 Google Cloud DNS로 처리하게 설정해뒀는데, DNS는 이미 Cloudflare로 옮긴 상태거든요. 다음 갱신 때 TXT 레코드를 못 만들어서 실패할 게 뻔했습니다.
Let's Encrypt 인증서 (만료: 2026-05-16) └─ 갱신 방식: certbot + dns-google └─ DNS는 이미 Cloudflare에 있음 └─ 다음 갱신 → TXT 레코드 생성 불가 → 실패
IP 노출이랑 인증서 갱신 실패, 두 가지를 한번에 해결하기로 했습니다.
Cloudflare 프록시를 전면에 세우자
Cloudflare Free Plan으로 할 수 있는 것들:
- 모든 트래픽을 Cloudflare 프록시 경유 (오렌지 클라우드) → VM IP 숨김, DDoS 방어
- Origin Certificate로 SSL 전환 → 15년 유효, 갱신이 필요 없음
- nginx에서 실제 클라이언트 IP 복원 →
CF-Connecting-IP헤더 활용
[목표] 브라우저 ──HTTPS──▶ Cloudflare ──HTTPS──▶ VM (IP 비공개) ├─ DDoS 방어 ├─ nginx ├─ CDN 캐싱 ├─ backend └─ SSL 종료 └─ frontend
전부 무료입니다. 바로 작업에 들어갔습니다.
실행: 순서를 틀리면 사이트가 죽는다
1단계: nginx에 Cloudflare Real IP 설정
Cloudflare 프록시를 켜면 nginx 로그에 모든 요청이 Cloudflare IP에서 온 걸로 찍힙니다. Rate Limiting도 먹통이 되고요.
# cloudflare-realip.conf set_real_ip_from 173.245.48.0/20; set_real_ip_from 103.21.244.0/22; # ... (총 15개 IPv4 + 7개 IPv6) real_ip_header CF-Connecting-IP;
프록시를 켜기 전에 이걸 먼저 배포해야 합니다.
2단계: SSL 모드를 Full (Strict)로
이것도 프록시보다 먼저. 순서 틀리면 사이트가 죽습니다.
Flexible (기본값): Cloudflare ──HTTP──▶ nginx ──301──▶ HTTPS → 무한 리다이렉트! 사이트 다운! Full (Strict): Cloudflare ──HTTPS──▶ nginx → 정상 동작
nginx에 80→443 리다이렉트가 걸려있어서, Flexible 모드면 Cloudflare→nginx→Cloudflare→nginx 무한 루프에 빠집니다. Cloudflare 대시보드에서 "전체(엄격)"을 먼저 선택한 뒤 다음 단계로 넘어갔습니다.
3단계: DNS 프록시 활성화
Cloudflare API로 www, api, @ 레코드의 프록시를 켰습니다.
도메인 이전 이후 ─────────────────────────────────────────────────── www.fullstackfamily.com DNS-only → Proxied api.fullstackfamily.com DNS-only → Proxied fullstackfamily.com DNS-only → Proxied image.fullstackfamily.com Proxied 유지 storage.fullstackfamily.com Proxied 유지
dig www.fullstackfamily.com 결과가 34.64.156.69에서 172.67.213.18로 바뀌었습니다. VM IP 숨김 성공.
4단계: Origin Certificate 설치
Cloudflare 대시보드에서 15년짜리 Origin Certificate를 발급받아 VM에 넣었습니다.
# Let's Encrypt → Origin Certificate ssl_certificate /etc/cloudflare/ssl/origin-cert.pem; ssl_certificate_key /etc/cloudflare/ssl/origin-key.pem;
certbot 타이머 비활성화, Docker 볼륨 마운트 변경. 여기까지는 잘 됐습니다.
Lighthouse 점수가 55점에서 24점으로 떨어졌다
Cloudflare를 전면 배치하고 Lighthouse를 돌렸는데, 성능 점수가 55점에서 24점이 나왔습니다.
[Lighthouse 결과] 항목 이전 이후 ────────────────────────────────── FCP ~1.0s 1.2s LCP ~5s 10.9s ← 2배 느려짐 TBT ~3s 5.4s Speed Index ~5s 8.6s 성능 점수 55 24 ← 반토막
체감으로도 확실히 느려진 게 느껴졌습니다.
서울에서 시애틀을 거쳐 서울로
응답 헤더의 cf-ray 값을 확인했습니다.
cf-ray: 9cec0582de4adee5-SEA ^^^ 시애틀!
서울에서 접속하는데 Cloudflare 시애틀(SEA) PoP을 경유하고 있었습니다. 서울(ICN)이 아니라 태평양 건너편.
[트래픽 경로] 직접 연결: 서울 ──▶ 서울 (TTFB ~0.2s) CF 경유: 서울 ──▶ 시애틀 ──▶ 서울 ──▶ 시애틀 ──▶ 서울 (TTFB ~0.9s)
TTFB가 0.2초에서 0.9초로, 4.5배 느려졌습니다. 정적 파일 20개짜리 페이지면 이 지연이 겹쳐서 체감은 더 심하고요.
한국에서는 원래 이렇다
Cloudflare 커뮤니티를 뒤져보니 한국에서 꽤 유명한 문제였습니다.
[한국 ISP별 Cloudflare Free Plan 라우팅] ISP 라우팅 PoP 서울과의 거리 ───────────────────────────────────────────────── KT LAX (로스앤젤레스) ~9,500km SK HKG (홍콩) ~2,100km LG U+ SJC/SEA (미국 서부) ~8,700km ICN(서울) PoP = Enterprise 플랜에서만 보장
한국 ISP들의 상호접속료가 세계 최고 수준이라, Cloudflare가 Free/Pro 플랜에서는 한국 PoP을 쓰지 않습니다. 한국 통신사들이 Cloudflare와 피어링을 안 하니까, 트래픽이 미국이나 홍콩을 빙 돌아옵니다.
설정으로 해결할 수 있는 문제가 아니었습니다.
| 방법 | 비용 | 효과 |
|---|---|---|
| Argo Smart Routing | $5/월~ | ISP 피어링 문제까지는 못 고침 |
| Pro Plan | $20/월 | 한국 PoP 보장 안 됨 |
| Business Plan | $200/월 | 될 수도 있지만 개인 프로젝트에는 과함 |
| Enterprise | 문의 | 확실하지만 역시 과함 |
하이브리드 구조로 방향 전환
IP 숨기고 DDoS 방어하는 것보다, 사이트가 느려지는 게 더 큰 문제였습니다. 속도가 반토막 나면 보안 이점이 의미가 없으니까요.
결정: www, api는 직접 연결로 되돌리고, 이미지/스토리지만 Cloudflare 유지
[최종 구조] www/api (직접 연결): 브라우저 ──HTTPS──▶ VM(서울) 직접 SSL: Let's Encrypt + certbot-dns-cloudflare 이미지/스토리지 (Cloudflare Worker/R2): 브라우저 ──▶ Cloudflare Worker ──▶ R2 (VM을 안 거치니까 PoP 문제와 무관) DNS 관리: Cloudflare (무료)
복원 작업
1. DNS 프록시 OFF
www.fullstackfamily.com Proxied → DNS-only (회색 구름) api.fullstackfamily.com Proxied → DNS-only fullstackfamily.com Proxied → DNS-only image.fullstackfamily.com Proxied 유지 (Worker/R2) storage.fullstackfamily.com Proxied 유지 (Worker/R2)
2. SSL: Origin Certificate → Let's Encrypt
Origin Certificate는 Cloudflare 프록시가 꺼지면 브라우저가 신뢰하지 않습니다. Let's Encrypt로 돌아가야 하는데, 이번에는 certbot-dns-cloudflare 플러그인을 씁니다.
이전: certbot + dns-google → Google Cloud DNS (DNS 이전으로 불가) 현재: certbot + dns-cloudflare → Cloudflare DNS (정상 동작)
# Cloudflare API 토큰 설정 cat > /etc/cloudflare/cloudflare.ini << EOF dns_cloudflare_api_token = {your-token} EOF chmod 600 /etc/cloudflare/cloudflare.ini # 인증서 발급 certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials /etc/cloudflare/cloudflare.ini \ -d fullstackfamily.com \ -d '*.fullstackfamily.com'
3. nginx 인증서 경로 복원
ssl_certificate /etc/letsencrypt/live/fullstackfamily.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/fullstackfamily.com/privkey.pem;
4. certbot 자동 갱신 다시 켜기
systemctl enable certbot.timer systemctl start certbot.timer
갱신 후 nginx가 자동으로 reload되게 hook도 추가했습니다.
결과
[TTFB 비교] 직접 연결 (이전): ~0.2s Cloudflare 경유: ~0.9s (4.5배 느림) 직접 연결 (복원 후): ~0.2s (원복) Lighthouse: 55 → 24(CF 경유) → 54(복원)
| 항목 | 직접 연결 | CF 프록시 | 복원 후 |
|---|---|---|---|
| TTFB | 0.2s | 0.9s | 0.2s |
| Lighthouse | 55점 | 24점 | 54점 |
| VM IP 노출 | O | X | O |
최종 아키텍처
www/api (직접 연결): 브라우저(서울) ──HTTPS──▶ VM(서울) ├─ nginx (Let's Encrypt SSL) ├─ backend:8080 ├─ frontend:3000 └─ mysql:3306 이미지/스토리지 (Cloudflare): 브라우저 ──▶ image.fullstackfamily.com ──▶ CF Worker ──▶ R2 브라우저 ──▶ storage.fullstackfamily.com ──▶ R2 DNS: Cloudflare (무료) SSL: Let's Encrypt + certbot-dns-cloudflare (자동 갱신)
인증서 세 가지를 다 써본 후기
이번 작업 하나에서 세 가지 인증서 전략을 경험했습니다.
| 항목 | LE + dns-google | CF Origin Certificate | LE + dns-cloudflare |
|---|---|---|---|
| 유효기간 | 90일 | 15년 | 90일 |
| 갱신 | certbot 자동 | 불필요 | certbot 자동 |
| DNS 의존성 | Google Cloud DNS | 없음 | Cloudflare DNS |
| CF 프록시 필수 | 아니오 | 예 | 아니오 |
| 브라우저 직접 신뢰 | 예 | 아니오 | 예 |
| 현재 상태 | DNS 이전으로 사용 불가 | 프록시 OFF라 사용 불가 | 사용 중 |
Origin Certificate가 가장 편합니다 (15년 유효, 갱신 없음). 근데 Cloudflare 프록시를 반드시 켜둬야 하고, 한국에서 그 프록시가 성능을 깎으니까 쓸 수가 없었습니다.
배운 것들
Cloudflare Free Plan은 한국에서 CDN이 아니다
한국에서 Cloudflare Free Plan 프록시를 켜면 트래픽이 미국 서부를 경유합니다. CDN은커녕 지연만 추가됩니다. Cloudflare 설정 문제가 아니라 한국 ISP와 Cloudflare 사이의 피어링 구조 문제이기 때문에 설정으로는 해결이 안 됩니다.
한국 사용자가 대부분인 서비스라면, DNS 관리나 Worker/R2 같은 엣지 서비스만 쓰는 게 현실적입니다.
전환 순서가 핵심이다
SSL 모드가 Flexible인 채로 프록시를 켜면 무한 리다이렉트. Origin Certificate 상태에서 프록시를 끄면 인증서 경고. 인프라 작업은 전환 순서를 잘못 잡으면 바로 장애가 납니다.
프록시 켤 때: SSL 모드 Full (Strict) 먼저 → 프록시 ON 프록시 끌 때: Let's Encrypt 먼저 설치 → 프록시 OFF
측정을 안 했으면 몰랐다
Lighthouse를 안 돌렸으면 "Cloudflare 붙였으니 빨라졌겠지"라고 넘어갔을 겁니다. cf-ray 헤더의 PoP 코드를 확인하지 않았으면 시애틀을 경유하고 있다는 사실도 몰랐을 거고요. 인프라 바꾸고 나면 반드시 측정을 해야 합니다.
변경 파일
인프라 설정: infra/vm/nginx/conf.d/cloudflare-realip.conf (신규 → 유지) infra/vm/nginx/conf.d/fullstackfamily.conf (인증서 경로 복원) infra/vm/docker-compose.prod.yml (볼륨 마운트 복원) scripts/ssh-tunnel.sh (gcloud SSH로 변경) VM 설정: /etc/cloudflare/cloudflare.ini (certbot용 CF API 토큰) /etc/letsencrypt/renewal-hooks/deploy/ (갱신 후 nginx reload) 백엔드/프론트엔드 코드 변경: 없음






댓글
댓글을 작성하려면 이 필요합니다.