Cloudflare Zero Trust + Warp의 핵심을 터널·라우트·타깃·액세스 정책·Warp 클라이언트 등록 흐름으로 풀어 설명하고, 사설 서비스를 안전하게 공개하거나 사설 네트워크를 구성하는 방법을 예시와 함께 안내합니다.
얼마 전, Tailscale이 NAT/방화벽 환경에서 제대로 P2P 연결을 뚫지 못하는 상황에 지쳐서, 새로운 걸 배워보기로 했습니다. 바로 Cloudflare Zero Trust + Warp.
새 개념이 너무 많았지만, 정말 오래 헤맨 끝에 이제야 말할 수 있습니다. 바로, 저는 이제 Cloudflare Zero Trust와 Warp를 이해했습니다. 저는 완전히 Cloudflare Zero Trust + Warp로 갈아탔고, 아직 Tailscale도 병행해서 돌리긴 하지만 이제 제가 하는 거의 모든 일은 Zero Trust 터널을 통해 이뤄집니다.
이 글은 기본 개념을 설명합니다. 저처럼 이해하느라 고생하는 분들께 도움이 되길 바랍니다.
왜 이렇게까지 시간을 들여서 배워야 할까요? 무엇을 얻을 수 있을까요?
Zero Trust의 Argo 터널을 사용하면 다음과 같은 정말 멋진 일들을 할 수 있습니다:
ssh host만 입력하면 바로 로그인됩니다.
먼저 간단히 정리하면:
Cloudflare에는 두 가지 도구가 있습니다: Warp Client와 Cloudflared. 서로 상호작용하고 비슷한 점도 있지만 동일하지 않습니다.
Warp Client
Cloudflare 네트워크에 연결해 주는 도구입니다. Zero Trust 네트워크에 클라이언트를 등록하고 정책을 적용하는 역할을 합니다.
보통 클라이언트(사용자 단말)에서 실행되지만, 서버에서도 실행할 수 있습니다.
Warp 클라이언트는 warp-to-warp 라우팅도 지원하며, 이는 Tailscale과 유사한 진정한 P2P 연결입니다.
Cloudflared
_터널_을 생성해 Zero Trust 네트워크에 추가하는 도구입니다.
일반적으로 서버에서 실행하여 네트워크로의 터널을 노출하지만, 클라이언트에서도 실행할 수 있습니다.
클라이언트 측에서는 cloudflared access를 사용해 Zero Trust 네트워크 내 다른 것들과의 연결을 설정할 수 있습니다.
Zero Trust 네트워크에 연결되지 않는 1회용 터널도 만들 수 있어, 테스트에 유용합니다.
이 부분을 이해하는 데 가장 시간이 걸렸습니다. Zero Trust에는 터널(Tunnels), 라우트(Routes), _타깃(Targets)_을 구성하는 기능이 있는데, 서로 어떻게 맞물리는지 설명합니다.
구성의 핵심입니다. 터널은 cloudflared로 배포하며, 간단히 말해 트래픽의 출구 입니다. 말 그대로 어딘가로 끝이 이어진 터널이라고 생각하세요.
터널은 대상 네트워크의 인프라에 배포합니다. 예를 들어 192.168.1.1/24 대역의 집 네트워크가 있다면, 그 네트워크 안에서 항상 켜져 있는 머신에 cloudflared를 배포하면 됩니다. 라우터여도 되고 라즈파이어도 상관없습니다.
서버에서 호스팅하는 서비스라면, 메인 개발 서버나 일반 서버, 또는 Kubernetes 클러스터의 파드에 터널을 둘 수 있습니다.
이제 Warp/Argo 터널을 통해 해당 네트워크로 들어가는 ‘입구’를 갖게 된 셈입니다.
터널은 Zero Trust UI에서 “adopt(채택)”하여 구성할 수도 있고, 해당 머신의 /etc/cloudflared/config.yml 파일에서 직접 구성할 수도 있습니다. 개인 취향이지만, 저는 보통 머신에서 직접 구성합니다.
설정에는 터널로 들어온 요청을 어디로 라우팅 할지 명시합니다. 즉 터널이 들어온 요청을 어떻게 처리할지 알게 됩니다.
아래 예시는 이 터널로 들어온 요청 중 gitlab.widgetcorp.tech라는 호스트명은 localhost:80으로, gitlab-ssh는 로컬 SSH 서버로 라우팅하라고 cloudflared에 지시합니다.
❯ cat /etc/cloudflared/config.yml
tunnel: a2f17e27-cd4d-4fcd-b02a-63839f57a96f
credentials-file: /etc/cloudflared/a2f17e27-cd4d-4fcd-b02a-63839f57a96f.json
ingress:
- hostname: gitlab.widgetcorp.tech
service: http://localhost:80
- hostname: gitlab-ssh.widgetcorp.tech
service: ssh://localhost:22
- service: http_status:404
# Catch-all for WARP routing
- service: http_status:404
warp-routing:
enabled: true
구성만으로는 아무 일도 일어나지 않습니다. 그저 터널을 노출했을 뿐입니다. 이제 필요한 것은 라우트와 타깃입니다.
아주 흔한 사용 사례라 짧게 덧붙입니다. 집 네트워크의 어떤 것을 인터넷에 곧바로 공개하고 싶다면, 다음과 같이 구성할 수 있습니다:
tunnel: a2f17e27-cd4d-4fcd-b02a-63839f57a96f
credentials-file: /etc/cloudflared/a2f17e27-cd4d-4fcd-b02a-63839f57a96f.json
ingress:
- hostname: homeassistant.mydomain.com
service: http://192.168.1.3:80
그다음 Cloudflare DNS 설정으로 가서 도메인 homeassistant.mydomain.com을 터널에 매핑합니다:
CNAME homeassistant.mydomain.com a2f17e27-cd4d-4fcd-b02a-63839f57a96f.cfargotunnel.com
이제 이 도메인으로 가는 모든 트래픽은 cloudflared 터널을 통해 이동하고, 터널은 homeassistant.mydomain.com을 192.168.1.3으로 라우팅하도록 구성되어 있습니다. Warp 클라이언트가 필요 없습니다. Argo 터널이 모든 것을 처리합니다.
참고: 터널을 UI에서 채택(adopt)했고 config.yaml을 쓰지 않는다면, Cloudflare UI에서 일치하는 DNS 레코드를 자동으로 생성할 수 있어 수동으로 만들 필요가 없습니다.
라우트는 트래픽을 어디로 보낼지를 정의합니다.
집에서 Home Assistant가 192.168.1.3에서 돌아가고 있고, 밖에서 접속하고 싶다고 가정해 봅시다. 위에서 우리는 192.168.1.3의 라우터에 cloudflared 터널을 배포했고, 도메인을 Argo 터널로 가리키도록 구성했기 때문에 homeassistant.mydomain.com은 이미 공개되어 있습니다. 그러나 192.168.1.3은 사설 IP이므로 그대로는 공개되지 않습니다.

다음과 같이 정의할 수 있습니다:
192.168.1.1/24 같은 라우트를 터널에 지정하여, 전체 IP 대역의 모든 트래픽을 그 터널로 보냅니다(예: 192.168.1.245도 터널로 갑니다).192.168.1.3/32를 터널에 지정하여, 오직 192.168.1.3으로 가는 트래픽만 그 터널로 보낼 수도 있습니다.구성해 두면, 사용자가 Zero Trust 네트워크에 연결된 Warp 클라이언트를 켰을 때 Warp 클라이언트가 192.168.1.3으로 가는 요청을 감지하고 Cloudflare 네트워크를 통해 해당 터널로 라우팅합니다. 마치 어디로 가야 할지 차들을 안내하는 교통정리 요원 같은 존재라고 보면 됩니다.
Warp 클라이언트가 연결되어 있지 않으면 192.168.1.3은 현재 로컬 네트워크에서만 해석됩니다. 연결되어 있다면 터널로 해석됩니다.
라우팅 대상 IP는 실제로 존재하지 않아도 됩니다! 예를 들어 마음에 드는 임의의 IP(예: 10.128.1.1)를 터널로 라우팅하고, 터널이 내부 라우팅에 따라(예: 192.168.1.1로) 전달하도록 할 수 있습니다. 이는 완전히 가상 네트워크를 구축할 수 있게 해 주기 때문에 매우 강력합니다.
라우트는 여기까지고, 이후에 무엇이 일어날지는 위에서 만든 터널 설정에 달려 있습니다. 터널이 들어온 요청을 로컬호스트로 보낼지 다른 곳으로 보낼지 결정합니다.
정리하면, 라우트(route)는 Warp 클라이언트에게 해당 트래픽을 어디로 라우팅해야 하는지 알려줍니다.
이제 두 가지가 동작합니다:
homeassistant.mydomain.com — Cloudflare DNS 레코드가 Argo 터널을 가리키고, 터널은 이를 192.168.1.3으로 포워딩합니다. DNS 레벨이므로 Warp 없이도 모두 접근할 수 있습니다.192.168.1.3 — Warp 클라이언트가 요청을 감지해 Argo 터널로 라우팅하고, 터널은 이를 해당 네트워크의 192.168.1.3으로 포워딩합니다. 이는 Warp가 연결되어 있어야 동작하며, Zero Trust 조직의 사람들에게만 보입니다.이건 이해하는 데 꽤 시간이 걸렸습니다.
타깃은 Zero Trust로 보호하려는 특정 인프라 를 정의하는 데 필요합니다. 네트워크 안의 어떤 것을 가리키는 포인터 같은 존재입니다. 라우트와 맞물려 동작하지만 항상 필요한 것은 아닙니다.
예를 들어 cloudflared 터널을 통해 192.168.1.3(홈어시스턴트)이 노출되어 있다고 합시다. 기본적으로 Zero Trust 조직에 속해 있고 Warp 클라이언트를 설치한 사람은 누구나 192.168.1.3의 Home Assistant에 접근할 수 있습니다.
타깃을 사용하면 이를 바꿀 수 있습니다. 예컨대 호스트명 homeassistant.mydomain.com을 라우트 192.168.1.3/32와 연결하는 타깃을 정의하면, 여기에 액세스 정책을 붙일 수 있습니다. 192.168.1.3/24처럼 전체 네트워크를 타깃에 지정해 접근 제어를 할 수도 있습니다. 10.128.1.1 같은 가상 IP로도 작동합니다!

타깃만으로는 아무 일도 하지 않습니다. 그저 서비스나 네트워크를 가리킬 뿐입니다. “여기가 홈어시스턴트야”, 혹은 “여기가 내 집 네트워크야”라고 알려주는 것입니다.
위 예시를 이어가 봅시다:
homeassistant.mydomain.com을 192.168.1.3으로 라우팅합니다.homeassistant.mydomain.com이 Argo 터널을 가리키게 했습니다.192.168.1.3으로 가는 라우트 도 같은 터널을 향하도록 만들었습니다.192.168.1.3을 가리키는 타깃 도 만들었습니다.사용자가 192.168.1.3이든 homeassistant.mydomain.com이든 접근하면, Warp 클라이언트가 요청을 터널로 라우팅하고, 터널은 이를 192.168.1.3으로 전달합니다. Home Assistant가 로드되고 모든 게 잘 동작하죠.
그런데, 우리가 원하는 동작이 맞을까요? 아마 아닐 겁니다.
여기서 액세스 정책이 빛을 발합니다!
액세스 정책을 사용하면 리소스를 공개 상태로 두면서도 Cloudflare Zero Trust 액세스를 통해 보호할 수 있습니다. 즉, 192.168.1.3은 Warp에 연결되어 있어야만(라우팅이 작동해야만) 접근 가능하지만, 공개 도메인인 homeassistant.mydomain.com에는 보안을 추가할 수 있습니다.
Access -> Applications -> Add an Application -> Self-hosted 로 이동하세요.
여기서 무엇을 보호할지, 그리고 어떻게 보호할지 정의할 수 있습니다.
앞선 예시대로, 공개 호스트명 homeassistant.mydomain.com이나 IP 192.168.1.3(혹은 둘 다)을 추가하고, 누가 접근할 수 있을지 정책을 붙입니다.
정책에는 Include(“OR”)와 Require(“AND”) 셀렉터를 지정할 수 있습니다.
그리고 Actions 가 있습니다:
가장 흔한 사용 예: homeassistant.mydomain.com은 공개 상태입니다. 공개를 유지하되, 보안 레이어를 하나 더 얹고 싶습니다.
include 정책을 추가하고, email 셀렉터 중 하나를 선택해 접근을 허용할 사용자의 이메일을 입력하세요. 이제 Zero Trust 조직에 인증된 사용자 중 지정한 이메일만 Home Assistant에 접근할 수 있고, Warp는 필요하지 않습니다.
require 규칙으로 더 단단하게 만들 수 있습니다: Login Method 셀렉터 규칙을 추가하고 GitHub 같은 특정 로그인 방법을 선택하세요. 이제 특정 이메일을 가진 사용자이면서 GitHub으로 인증까지 해야 Home Assistant에 접근할 수 있으며, Warp는 여전히 필요하지 않습니다.

제가 좋아하는 또 다른 정책은 Warp로 연결되었을 때 로그인 화면을 아예 건너뛰는 것입니다. 이미 우리 Zero Trust 조직에 등록(enroll)되어 Warp 클라이언트가 프로비저닝된 사용자라면, 다시 인증을 요구할 필요가 없겠죠.
방금 만든 정책은 수정하지 말고, 별도의 정책을 하나 더 추가하세요. Gateway 셀렉터를 선택하고 동작을 Allow 또는 Bypass 로 설정합니다.

‘Warp’를 쓰지 마세요. Warp 셀렉터는 소비자용 1.1.1.1 앱을 포함해 Warp가 실행 중인 누구에게나 일치합니다. 반면 Gateway 는 DNS 또는 프로비저닝된 Warp 클라이언트를 통해 여러분의 게이트웨이로 접속하는 경우에만 일치합니다.
(‘Gateway’ 셀렉터는 Warp 클라이언트에서 WARP 인증 ID 허용이 켜져 있을 때만 사용할 수 있습니다.)
이제 동작은 다음과 같습니다:
이 구성은 Warp로 연결했는지 여부와 관계없이 Home Assistant에 매우 편리하게 접근할 수 있게 해 줍니다.
여기까지 괜찮으신가요?
네트워크는 사실상 완성됐습니다. 로그인으로 보호된 homeassistant.mydomain.com이 터널을 통해 사설 네트워크로 들어가 최종적으로 192.168.1.3에서 종료되고, Warp가 연결됐을 때만 동작하는 192.168.1.3 직접 라우트도 있습니다.
또한 GitHub과 특정 이메일로 로그인한 사용자만 Home Assistant에 접근할 수 있도록 정책도 마련했습니다.
그럼 Warp 클라이언트는 어떻게 배포하죠? 사실 똑같습니다. 정책을 몇 개 만들면 됩니다.
Settings -> Warp Client로 이동하세요.
Enrollment Permissions에서 누가 등록(enroll)할 수 있는지에 대한 정책을 지정합니다. 예를 들어 GitHub으로 인증한 “[email protected]”은 등록을 허용합니다. Login Methods 에서는 Zero Trust 조직에 등록하려는 사람이 사용할 수 있는 로그인 방법을 지정합니다.

_WARP authentication identity settings_를 켜면 정책에서 Gateway 셀렉터를 사용할 수 있게 되어, 구성된 WARP 클라이언트를 로그인 방법처럼 활용할 수 있습니다.
여기 주의: 누군가 등록에 성공하면 Warp를 통해 사실상 여러분의 Zero Trust 네트워크 안으로 들어오게 됩니다. 반드시 강력하게 보호하세요.
그리고 Profile settings에서 Warp 클라이언트의 동작 방식 을 정의합니다. 예: 프로토콜(MASQUE 또는 WireGuard), 서비스 모드, WARP 라우팅에서 제외할 IP/도메인(예: 로컬 네트워크는 WARP를 거치지 않도록), 포함/제외 모드 설정 등.
제가 추천하는 추가 설정:

이제 Warp 클라이언트(https://developers.cloudflare.com/warp-client/)를 열고, 여러분의 네트워크로 로그인하세요. 그러면 Device Enrollment 화면에서 지정한 로그인 페이지가 열리고, 설정한 등록 정책을 모두 검사합니다.
모두 통과하면 축하합니다. 이제 여러분의 WARP 클라이언트는 Zero Trust 네트워크에 연결되었습니다. 클라이언트는 터널과 라우트 설정에 따라 192.168.1.3으로 가는 트래픽을 터널을 통해 라우팅하기 시작할 것입니다.
🎉
이 가이드를 따라오셨다면, 다음을 구성한 것입니다:
homeassistant.mydomain.com인 요청을 192.168.1.3으로 포워딩합니다.192.168.1.3으로 가는 모든 트래픽을 사설 네트워크의 터널로 전달하고, 최종적으로 192.168.1.3에서 종료되게 하는 라우트 (요청 라우팅을 위해 Warp 연결이 필요)homeassistant.mydomain.com — Warp에 연결하지 않은 사람도 192.168.1.3에서 실행 중인 Home Assistant에 접근할 수 있습니다.공개 도메인도, 192.168.1.3으로의 라우트도 꼭 필요하지는 않습니다. 집 밖에서 Home Assistant를 노출하는 두 가지 상이한 선택지일 뿐입니다. 하나는 모두가 볼 수 있는 공개 도메인 이름을 사용하는 방법이고, 다른 하나는 등록된 Warp 연결을 명시적으로 요구하는 방법입니다.
이 글에서 다루지 않은 것들:
관심이 있다면 더 확장해 보겠습니다. X나 Bluesky에서 알려주세요.
즐거운 터널링! ⛅