Cloudflare Tunnel로 Vercel을 연결하면 안 되는 이유
Vercel 배포는 성공했습니다. 프로젝트명.vercel.app 으로 접속하면 잘 뜹니다.
그런데 제 도메인 shop.example.co.kr 을 연결했더니 이런 화면이 나왔습니다.
404: NOT_FOUND
Code: DEPLOYMENT_NOT_FOUND
ID: icn1::g2bbd-1785287214941-916f5febf79b"배포를 찾을 수 없다"는데, 배포는 분명히 살아 있었습니다.
먼저 응답 헤더를 봤습니다
에러 화면만 보고 추측하는 대신 헤더를 확인했습니다. 여기에 답이 있는 경우가 많습니다.
$ curl -sI https://shop.example.co.kr
HTTP/2 404
server: cloudflare
x-vercel-error: DEPLOYMENT_NOT_FOUND
x-vercel-id: icn1::lsjc2-1785287295622-a97d4479f9a9
cf-ray: a2283b3b5c79a102-LAX이 두 줄이 핵심이었습니다.
x-vercel-id: icn1::...→ 요청이 Vercel 서울 리전까지 도달했습니다x-vercel-error: DEPLOYMENT_NOT_FOUND→ 도달했는데 매칭에 실패했습니다
즉 DNS도 정상이고 네트워크 경로도 정상입니다. Vercel이 요청을 받긴 받았는데,
"이 요청을 어느 프로젝트에 줘야 할지 모르겠다"고 답한 상황이었습니다.
Vercel은 오직 Host 헤더로 프로젝트를 찾습니다. 여기서부터 원인이 좁혀졌습니다.
원인: Cloudflare Tunnel이 Host를 그대로 넘깁니다
제가 한 설정은 이랬습니다. 공개망 밖의 개발·검증 환경을 연결할 때 쓰던 Cloudflare Tunnel에
public hostname을 하나 더 추가하고, origin service를 https://프로젝트명.vercel.app 으로 지정한 것입니다.
익숙한 도구라 무심코 같은 방식을 썼는데, 트래픽이 이렇게 흘렀습니다.
브라우저 → Cloudflare → 비공개 환경의 터널 커넥터 → Vercel
Host: shop.example.co.kr ← 그대로 전달Cloudflare Tunnel은 origin으로 요청을 넘길 때 원래 Host 헤더를 보존합니다.
그래서 Vercel은 Host: shop.example.co.kr 을 받았고, 그 호스트명을 등록한 프로젝트가 없으니DEPLOYMENT_NOT_FOUND 를 반환한 것입니다.
사실 더 근본적인 문제가 있었습니다
Host 헤더는 표면적인 원인이고, 진짜 문제는 도구를 잘못 골랐다는 것이었습니다.
Cloudflare Tunnel은 공개망에서 접근할 수 없는 것을 노출하기 위한 도구입니다.
접근이 제한된 개발·검증 서버나 로컬 개발 서버처럼요. 고정 IP가 없거나 포트를 열 수 없는
환경에서 커넥터가 바깥으로 연결을 맺어 우회하는 방식입니다.
그런데 Vercel은 이미 공개 인터넷에 있습니다. 노출할 필요가 없습니다.
제 설정대로라면 트래픽이 이렇게 돕니다.
사용자 → Cloudflare 엣지 → 터널 커넥터 → 다시 인터넷 → Vercel 엣지불필요한 우회가 두 번 들어가고, 중간 터널 커넥터가 중단되면 Vercel에 올린 사이트가 죽습니다.
전 세계 엣지에 배포한 의미가 사라지는 구조였습니다.
터널의 HTTP Host Header 옵션을 프로젝트명.vercel.app 으로 덮어쓰면 404 자체는 사라집니다.
하지만 그건 증상만 가리는 것이고, 위의 우회 경로는 그대로 남습니다.
올바른 방법
1. 터널 라우트를 삭제합니다
Cloudflare Zero Trust에서 해당 public hostname 라우트를 지웁니다.
터널이 자동 생성한 DNS 레코드도 함께 정리합니다.
2. 평범한 CNAME을 만듭니다
| Type | Name | Target | Proxy status |
|---|---|---|---|
| CNAME | shop |
cname.vercel-dns.com |
DNS only (회색 구름) |
3. Vercel 프로젝트에 도메인을 등록합니다
프로젝트 → Settings → Domains → Add Existing → 도메인 입력 → Production 연결
DNS가 맞게 설정돼 있으면 몇 초 안에 Valid Configuration 으로 바뀌고,
인증서도 자동으로 발급됩니다.
저는 이렇게 바꾼 뒤 바로 정상 동작했습니다.
$ curl -sI https://shop.example.co.kr | grep -E 'HTTP|server|x-vercel-id'
HTTP/2 200
server: Vercel
x-vercel-id: icn1::iad1::7s9w9-...server 가 cloudflare 에서 Vercel 로 바뀐 것이 보이실 겁니다.
이제 Cloudflare를 거치지 않고 Vercel 엣지로 직접 갑니다.
"프록시(주황 구름)는 켜는 게 좋지 않나요?"
저도 처음엔 켜두는 게 안전하다고 생각했습니다. 하지만 이 경우는 끄는 쪽이 맞습니다.
Vercel 자체가 이미 CDN이기 때문입니다. 서울을 포함한 전 세계 엣지에서 캐싱과
TLS 종료를 처리합니다. 여기에 Cloudflare 프록시를 얹으면 CDN을 두 겹으로 쌓는 셈인데,
얻는 것보다 관리 포인트가 늘어납니다.
- 인증서 갱신 실패 가능성 - Vercel이 도메인 소유를 주기적으로 재확인하는데
프록시가 앞을 가리면 검증이 실패할 수 있습니다 - SSL 모드 사고 - Cloudflare가
Flexible이면 무한 리다이렉트에 빠집니다.Full (strict)로 맞춰야 하는데, 굳이 신경 쓸 일을 만들 이유가 없습니다 - 캐시 정책 충돌 - Vercel이 내려보내는 캐시 헤더를 Cloudflare가 한 번 더 해석합니다
WAF나 봇 차단이 필요하시면 Cloudflare 대신 Vercel Firewall 을 쓰시는 편이 낫습니다.
같은 층위에서 처리하니 이중 프록시 문제가 생기지 않습니다.
정리하면: 언제 무엇을 쓰나
| 상황 | 방식 | 이유 |
|---|---|---|
| 공개망 밖의 개발·검증 환경 | Cloudflare Tunnel | 외부에서 직접 접근할 수 없으므로 커넥터로 연결 |
| 자체 서버에 고정 IP가 있음 | A 레코드 + 프록시 ON | 오리진 IP를 가리고 WAF를 얹음 |
| Vercel, Netlify 등 관리형 호스팅 | CNAME + DNS only | 이미 공개 CDN 위에 있음. 앞에 뭘 더 둘 필요 없음 |
기존 설정에는 공개망 밖의 개발·검증 환경을 연결하는 Tunnel + Proxied 레코드가 있었습니다.
그 레코드에는 목적에 맞는 구성이지만, Vercel에 배포된 shop 은 성격이 달랐습니다.
익숙한 방식을 그대로 적용하다 보니 놓쳤던 것입니다.
남는 교훈
두 가지를 얻었습니다.
첫째, 에러 헤더를 먼저 읽습니다.x-vercel-error 와 x-vercel-id 두 줄이 "어디까지 갔고 무엇이 실패했는지"를
정확히 알려줬습니다. 화면의 404만 보고 DNS 설정을 뒤졌다면 한참 헤맸을 것입니다.
플랫폼이 제공하는 진단 헤더는 대부분 문서화돼 있으니 한 번 확인해두시면 좋습니다.
둘째, 익숙한 도구를 습관으로 쓰지 않습니다.
Cloudflare Tunnel은 좋은 도구입니다. 다만 "공개망에 없는 것을 노출한다"는
목적이 분명한 도구입니다. 이미 공개망에 있는 대상에 쓰면 목적과 어긋납니다.
동작하게 만드는 방법(Host 헤더 덮어쓰기)이 있다고 해서 그게 맞는 방법인 것은 아닙니다.
설정 한 줄로 404는 사라졌겠지만, 중간 터널 커넥터가 중단되면 사이트가 죽는 구조는 그대로였을 테니까요.
관련 글
온프레미스로 돌던 서비스를 Vercel + Supabase 로 옮기고 워커를 분리하기까지,
같은 작업에서 나온 기록입니다.
- Supabase에 붙었더니 ENOTFOUND: 직결, 풀러(Supavisor), 그리고 IPv6
직결 주소가 IPv6 전용이라 붙지 않는 문제와, 커넥션 풀러의 두 가지 모드. - Vercel Hobby 플랜은 기본이 "AI 학습 옵트인"입니다
무료 플랜의 데이터 사용 조항과, 올리기 전에 꺼야 하는 이유. - 서버리스 환경의 백그라운드 작업 분리: Upstash QStash + Redis 적용
큐 드라이버 경계 설계, 워커 엔드포인트 서명 검증, 멱등성. - 큐 드라이버를 memory에서 Upstash로 전환하기: 준비와 점검
전환 전 상태를 동작으로 확인하는 법과 각 환경변수의 역할.
함께 읽기
- Vercel Hobby 플랜은 기본이 "AI 학습 옵트인"입니다온프레미스로 돌아가던 서비스를 Vercel + Supabase로 옮겨보는 중이었습니다.
- 새 서브도메인이 특정 브라우저에서만 안 열릴 때: DNS 캐시 점검법새 서브도메인을 추가한 직후 “Safari에서는 열리는데 Chrome에서는 안 열린다” 같은 일이 생길 수 있습니다. 이럴 때 바로 서버 장애나 DNS 설정 오류라고 판단하기 쉽지만, 실제 원인은 로컬 또는 브라우저 DNS 캐시인 경우가 많습니다.
- 블로그를 서브도메인에서 하위 경로로 옮긴 이유, 그리고 CSP가 애드센스를 막고 있었습니다회사 사이트와 기술 블로그를 따로 운영하는 구성은 흔합니다. 회사는 duolabs.co.kr, 블로그는 blog.duolabs.co.kr.
- Next.js + Postgres 프로젝트 호스팅 비용 비교: 관리형 플랫폼과 VPSNext.js와 PostgreSQL로 만든 웹 애플리케이션을 운영할 때 관리형 플랫폼과 VPS 중 어느 쪽이 더 저렴한지는 월 기본요금만으로 판단하기 어렵습니다. 관리형 플랫폼은 배포와 확장을 대신 처리하고, VPS는 낮은 인프라 비용 대신 서버 운영을 직접 맡아야 합니다.
- 운영에서만 AI 답변이 한 번에 나온다면, nginx가 스트림을 삼키고 있습니다챗봇 답변을 한 글자씩 흘려보내는 것은 기술적 과시가 아닙니다. 사람이 기다릴 수 있게 만드는 장치입니다. 같은 5초라도 빈 화면을 보는 5초와 글자가 차오르는 5초는 완전히 다른 시간입니다.