🔥 Cloudflare Tunnel 완전 정복: 원리부터 설정까지

#cloudflare#tunnel#cloudflared#zero-trust#networking
1862자
20분

lecture image
출처: Cloudflare Tunnel 공식 문서

집에서 사이드 프로젝트를 돌리다 보면 한 번쯤은 이런 상황을 겪는다. "로컬에서 도는 이 서비스를, 외부에서 접속하게 하려면 어떻게 해야 하지?" 공유기 관리자 페이지에 들어가서 포트포워딩을 설정하고, 방화벽에 구멍을 뚫고, DDNS를 설정하고, 혹시 모를 보안 위협에 불안해하며 잠을 청했던 기억이 있다면 — 이 글이 도움이 될 거다.

Cloudflare Tunnel은 이 모든 과정을 뒤집어 버린다.

1. Cloudflare Tunnel이 뭔가

한마디로 말하면 **"내 서버에서 Cloudflare로 나가는 터널"**이다. 전통적인 방식이 "외부에서 내 서버로 들어오는 길을 만드는 것"이었다면, Cloudflare Tunnel은 "내 서버가 Cloudflare에게 먼저 손을 내미는 것"이다.

서버에 cloudflared라는 경량 데몬을 설치하면, 이 데몬이 Cloudflare의 글로벌 네트워크로 아웃바운드 연결을 맺는다. 그 이후 외부 사용자는 Cloudflare를 통해 내 서비스에 접근하게 된다. 내 서버의 IP는 외부에 노출되지 않는다.

HTTP 웹 서버는 물론이고, SSH, RDP, TCP, Unix 소켓까지 지원한다.

2. 전통적 방식의 문제점

포트포워딩 + 공인 IP 방식으로 서비스를 노출하면 몇 가지 문제가 생긴다.

보안 구멍: 방화벽에 인바운드 포트를 열어야 한다. 포트 80, 443을 여는 순간 전 세계에서 내 서버로 직접 접근할 수 있다. DDoS 공격이든, 무차별 대입 공격이든, 내 IP가 노출되는 순간 타깃이 된다.

복잡한 설정: 공유기 포트포워딩 → DDNS 설정 → SSL 인증서 발급 → 방화벽 규칙 관리. 하나라도 빠뜨리면 접속이 안 되거나, 보안에 구멍이 생긴다.

IP 변경: 가정용 인터넷은 IP가 바뀔 수 있다. DDNS를 설정해도 전파 지연이 있다.

VPN의 한계: VPN을 쓰면 보안은 해결되지만, 연결이 불안정하고 속도 저하가 있다. 그리고 한번 VPN에 접속하면 네트워크 전체에 접근 가능한 것도 위험하다. 이건 Zero Trust 관점에서 좋지 않다.

3. 아웃바운드 전용 연결의 원리

Cloudflare Tunnel의 핵심은 **아웃바운드 전용 연결 모델(outbound-only connection model)**이다. 이게 왜 중요한지 단계별로 보자.

연결 과정

  1. 서버에 cloudflared를 설치하고 실행한다.
  2. cloudflared가 Cloudflare의 글로벌 네트워크로 아웃바운드 연결을 만든다.
  3. 이 연결은 양방향 통신이 가능한 터널이 된다.
  4. 외부 사용자가 myapp.example.com에 접속하면, Cloudflare가 이 터널을 통해 내 서버로 요청을 전달한다.

왜 아웃바운드인가

대부분의 방화벽은 아웃바운드 트래픽을 기본적으로 허용한다. cloudflared는 이 표준을 활용한다. 서버에서 Cloudflare로 나가는 연결만 있으면 되니까, 인바운드 포트를 하나도 열 필요가 없다. 방화벽에서 모든 인바운드 트래픽을 차단해도 터널은 정상 동작한다.

이건 보안 모델을 완전히 뒤집는 거다. "어떤 포트를 열어야 하지?"가 아니라 "모든 걸 닫고, Cloudflare를 통해서만 접근하게 하자"로 바뀐다.

연결 아키텍처

cloudflared는 실행 시 **4개의 장기 연결(long-lived connection)**을 만든다. 이 4개의 연결은 최소 2개의 서로 다른 데이터센터에 분산된다. 하나의 데이터센터에 장애가 생겨도 나머지로 트래픽을 처리할 수 있다.

프로토콜은 QUIC을 우선 사용하고, 안 되면 HTTP/2로 폴백한다. 모든 터널 연결은 TLS 1.3과 포스트 퀀텀 암호화로 보호된다.

4. 핵심 용어 정리

Cloudflare Tunnel 문서를 읽다 보면 반복적으로 나오는 용어가 있다. 한번 정리해두면 이후 문서를 읽을 때 훨씬 편하다.

Tunnel: 내 오리진 서버와 Cloudflare 네트워크 사이의 보안 연결. UUID로 식별된다.

Connector (cloudflared): cloudflared 데몬 인스턴스. 하나의 인스턴스가 4개의 연결을 최소 2개 데이터센터에 생성한다.

Replica: 같은 터널을 여러 호스트에서 실행하는 추가 cloudflared 인스턴스. 고가용성을 위한 것이다. Cloudflare가 지리적으로 가장 가까운 Replica로 라우팅한다.

Remotely-managed Tunnel: Cloudflare 대시보드에서 만들고 설정을 Cloudflare에 저장하는 터널.

Locally-managed Tunnel: CLI로 만들고 로컬 config 파일로 관리하는 터널.

Quick Tunnel: 계정도 DNS도 설정도 필요 없이 trycloudflare.com 서브도메인으로 즉시 로컬 서비스를 노출하는 임시 터널.

Ingress Rule: 들어오는 요청을 어떤 로컬 서비스로 보낼지 결정하는 규칙. hostname과 path를 기반으로 매칭한다.

5. cloudflared 설치

macOS

Homebrew로 한 줄이면 된다.

bash
brew install cloudflared
bash
brew install cloudflared

Linux (Debian/Ubuntu)

Cloudflare 패키지 저장소를 추가하고 설치한다.

bash
# Cloudflare GPG key 추가
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
 
# apt 저장소 추가 (stable)
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
 
# 설치
sudo apt-get update && sudo apt-get install cloudflared
bash
# Cloudflare GPG key 추가
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
 
# apt 저장소 추가 (stable)
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
 
# 설치
sudo apt-get update && sudo apt-get install cloudflared

바이너리 직접 다운로드도 가능하다.

bash
# amd64
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/
bash
# amd64
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/

Docker

Docker Hub에 공식 이미지가 있다.

bash
docker pull cloudflare/cloudflared:latest
bash
docker pull cloudflare/cloudflared:latest

Windows

winget으로 설치한다.

powershell
winget install --id Cloudflare.cloudflared
powershell
winget install --id Cloudflare.cloudflared

설치 후 버전을 확인한다.

bash
cloudflared --version
bash
cloudflared --version

참고: Cloudflare는 최신 릴리스 기준 1년 이내의 cloudflared 버전만 지원한다. 그 이전 버전은 호환성 문제가 생길 수 있으니 주기적으로 업데이트하는 게 좋다.

6. Quick Tunnel: 10초 체험

뭔가를 설명하기 전에 직접 체험하는 게 가장 빠르다. Quick Tunnel은 Cloudflare 계정도 없이 로컬 서비스를 인터넷에 노출한다.

로컬에서 아무 웹 서버나 띄우고:

bash
# Python 간이 서버 예시
python3 -m http.server 8080
bash
# Python 간이 서버 예시
python3 -m http.server 8080

다른 터미널에서:

bash
cloudflared tunnel --url http://localhost:8080
bash
cloudflared tunnel --url http://localhost:8080

몇 초 후 터미널에 https://random-words.trycloudflare.com 같은 URL이 뜬다. 이 URL로 어디서든 접속할 수 있다.

주의할 점:

  • 동시 요청 200개 제한이 있다.
  • SSE(Server-Sent Events)를 지원하지 않는다.
  • SLA 보장이 없다. 테스트와 개발 용도다.
  • 터미널을 닫으면 터널도 사라진다.

Quick Tunnel은 빠른 데모, 웹훅 테스트, 임시 빌드 공유에 딱이다. 프로덕션에는 쓰면 안 된다.

7. 대시보드에서 터널 만들기 (Remotely-managed)

본격적으로 쓰려면 Cloudflare 계정이 필요하다. 대시보드 방식은 GUI로 설정하고, 설정을 Cloudflare 클라우드에 저장한다.

사전 준비

  • Cloudflare 계정
  • Cloudflare에 등록된 도메인 (웹 애플리케이션 퍼블리시 시)

단계

  1. Cloudflare One 대시보드NetworksConnectorsCloudflare Tunnels 이동
  2. Create a tunnel 클릭
  3. 커넥터 타입으로 Cloudflared 선택
  4. 터널 이름 입력 (예: home-server, dev-api)
  5. Save tunnel 클릭
  6. 서버 OS에 맞는 설치 명령어가 나온다. 복사해서 서버에서 실행한다.
  7. 커넥터가 연결되면 대시보드에 표시된다.

: 서버가 제한적인 방화벽 뒤에 있다면, 포트 7844로 Cloudflare에 접근 가능한지 먼저 확인하자.

애플리케이션 퍼블리시

터널을 만든 후 Published applications 탭에서:

  1. 서브도메인과 도메인을 입력한다 (예: app.example.com)
  2. 서비스 타입 선택 (HTTP, HTTPS, TCP 등)
  3. 로컬 URL 입력 (예: localhost:3000)
  4. Save

이제 app.example.com으로 접속하면 로컬의 포트 3000 서비스에 연결된다. Cloudflare의 CDN, WAF, DDoS 방어가 자동으로 적용된다.

프라이빗 네트워크 연결

웹 앱이 아니라 내부 네트워크를 연결하고 싶다면:

  1. CIDR 탭에서 프라이빗 IP 또는 CIDR 범위를 입력한다 (예: 10.0.0.0/24)
  2. Complete setup 클릭

이렇게 하면 Cloudflare WARP 클라이언트를 통해 내부 네트워크에 접근할 수 있다.

8. CLI로 터널 만들기 (Locally-managed)

대시보드 대신 CLI로 모든 걸 제어하고 싶다면 Locally-managed 방식을 쓴다. 설정이 로컬 파일에 저장되므로 Git으로 관리할 수 있다는 장점이 있다.

로그인

bash
cloudflared tunnel login
bash
cloudflared tunnel login

브라우저가 열리고 Cloudflare 계정으로 인증하면, ~/.cloudflared/cert.pem 파일이 생성된다.

터널 생성

bash
cloudflared tunnel create my-tunnel
bash
cloudflared tunnel create my-tunnel

이 명령은 두 가지를 만든다:

  • 터널 UUID (예: 6ff42ae2-765d-4adf-8112-31c55c1551ef)
  • 자격 증명 파일: ~/.cloudflared/<UUID>.json

config.yml 작성

~/.cloudflared/config.yml 파일을 만든다.

yaml
tunnel: 6ff42ae2-765d-4adf-8112-31c55c1551ef
credentials-file: /root/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json
 
ingress:
  - hostname: app.example.com
    service: http://localhost:3000
  - hostname: api.example.com
    service: http://localhost:8080
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - service: http_status:404
yaml
tunnel: 6ff42ae2-765d-4adf-8112-31c55c1551ef
credentials-file: /root/.cloudflared/6ff42ae2-765d-4adf-8112-31c55c1551ef.json
 
ingress:
  - hostname: app.example.com
    service: http://localhost:3000
  - hostname: api.example.com
    service: http://localhost:8080
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - service: http_status:404

마지막 줄의 - service: http_status:404필수다. 어떤 hostname에도 매칭되지 않는 요청에 대한 catch-all 규칙이다.

DNS 레코드 등록

bash
cloudflared tunnel route dns my-tunnel app.example.com
cloudflared tunnel route dns my-tunnel api.example.com
bash
cloudflared tunnel route dns my-tunnel app.example.com
cloudflared tunnel route dns my-tunnel api.example.com

이 명령은 Cloudflare DNS에 CNAME 레코드를 자동으로 추가한다.

터널 실행

bash
cloudflared tunnel run my-tunnel
bash
cloudflared tunnel run my-tunnel

설정 검증

config 파일이 올바른지 확인한다.

bash
cloudflared tunnel ingress validate
bash
cloudflared tunnel ingress validate

특정 URL이 어떤 ingress rule에 매칭되는지도 테스트할 수 있다.

bash
cloudflared tunnel ingress rule https://app.example.com
bash
cloudflared tunnel ingress rule https://app.example.com

9. Ingress Rules 심화

하나의 터널로 여러 서비스를 동시에 연결할 수 있다. 이게 Cloudflare Tunnel의 강력한 점이다.

매칭 순서

cloudflared는 요청이 들어오면 ingress rule을 위에서 아래로 순서대로 평가한다. 처음 매칭되는 규칙으로 라우팅한다.

와일드카드

yaml
ingress:
  - hostname: "*.example.com"
    service: http://localhost:8080
  - service: http_status:404
yaml
ingress:
  - hostname: "*.example.com"
    service: http://localhost:8080
  - service: http_status:404

*.example.comapp.example.com, api.example.com 등 모든 서브도메인에 매칭된다.

경로(Path) 매칭

yaml
ingress:
  - hostname: example.com
    path: /api/.*
    service: http://localhost:8080
  - hostname: example.com
    service: http://localhost:3000
  - service: http_status:404
yaml
ingress:
  - hostname: example.com
    path: /api/.*
    service: http://localhost:8080
  - hostname: example.com
    service: http://localhost:3000
  - service: http_status:404

example.com/api/users는 8080으로, example.com/about는 3000으로 간다.

지원하는 서비스 타입

  • http://localhost:8080 — HTTP 웹 앱
  • https://localhost:443 — TLS 웹 앱
  • ssh://localhost:22 — SSH 접속
  • rdp://localhost:3389 — 원격 데스크톱
  • tcp://localhost:5432 — DB 등 TCP 서비스
  • unix:/path/to/socket — 소켓 기반 서비스
  • hello_world — 테스트용 내장 서비스
  • http_status:404 — catch-all 응답

10. Docker Compose로 운영하기

프로덕션 환경에서는 Docker Compose로 cloudflared를 관리하는 게 편하다.

Remotely-managed 방식 (간단)

대시보드에서 터널을 만들고 발급받은 토큰으로 실행한다.

yaml
# docker-compose.yml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token <YOUR_TUNNEL_TOKEN>
    network_mode: host
yaml
# docker-compose.yml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run --token <YOUR_TUNNEL_TOKEN>
    network_mode: host

network_mode: host를 쓰면 다른 컨테이너의 포트에 localhost로 접근할 수 있다.

Locally-managed 방식 (설정 파일 사용)

yaml
# docker-compose.yml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate --config /etc/cloudflared/config.yml run
    volumes:
      - ./cloudflared/config.yml:/etc/cloudflared/config.yml:ro
      - ./cloudflared/credentials.json:/etc/cloudflared/credentials.json:ro
yaml
# docker-compose.yml
services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate --config /etc/cloudflared/config.yml run
    volumes:
      - ./cloudflared/config.yml:/etc/cloudflared/config.yml:ro
      - ./cloudflared/credentials.json:/etc/cloudflared/credentials.json:ro

Docker 환경에서 cloudflared가 다른 컨테이너의 서비스에 접근하려면, Docker 네트워크를 공유하거나 host 네트워크 모드를 써야 한다. Docker 내부 서비스를 가리킬 때는 localhost 대신 컨테이너 이름이나 서비스 이름을 써야 한다.

yaml
# config.yml (Docker 환경)
tunnel: <TUNNEL_UUID>
credentials-file: /etc/cloudflared/credentials.json
 
ingress:
  - hostname: app.example.com
    service: http://webapp:3000  # Docker 서비스 이름 사용
  - hostname: api.example.com
    service: http://api-server:8080
  - service: http_status:404
yaml
# config.yml (Docker 환경)
tunnel: <TUNNEL_UUID>
credentials-file: /etc/cloudflared/credentials.json
 
ingress:
  - hostname: app.example.com
    service: http://webapp:3000  # Docker 서비스 이름 사용
  - hostname: api.example.com
    service: http://api-server:8080
  - service: http_status:404

11. 방화벽 설정

Cloudflare Tunnel의 보안 모델을 제대로 활용하려면, 방화벽에서 인바운드를 전부 차단하고 필요한 아웃바운드만 허용해야 한다.

필수 아웃바운드 허용

cloudflared는 포트 7844로 Cloudflare에 연결한다. TCP(HTTP/2)와 UDP(QUIC) 모두 사용한다.

  • region1.v2.argotunnel.com:7844
  • region2.v2.argotunnel.com:7844

선택 아웃바운드 (포트 443)

  • api.cloudflare.com — API 통신
  • update.argotunnel.com — 자동 업데이트
  • pqtunnels.cloudflareresearch.com — 포스트 퀀텀 키 교환

Linux iptables 예시

bash
# 루프백 허용
sudo iptables -A INPUT -i lo -j ACCEPT
 
# 이미 연결된 세션 허용
sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
 
# SSH 인바운드는 유지 (관리용)
sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT
 
# 나머지 인바운드 전부 차단
sudo iptables -A INPUT -j DROP
bash
# 루프백 허용
sudo iptables -A INPUT -i lo -j ACCEPT
 
# 이미 연결된 세션 허용
sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
 
# SSH 인바운드는 유지 (관리용)
sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT
 
# 나머지 인바운드 전부 차단
sudo iptables -A INPUT -j DROP

Debian/Ubuntu에서 규칙을 영구 저장하려면:

bash
sudo apt install iptables-persistent
sudo netfilter-persistent save
bash
sudo apt install iptables-persistent
sudo netfilter-persistent save

연결 테스트

bash
# DNS 확인
dig A region1.v2.argotunnel.com
 
# 포트 연결 확인
nc -zv region1.v2.argotunnel.com 7844
bash
# DNS 확인
dig A region1.v2.argotunnel.com
 
# 포트 연결 확인
nc -zv region1.v2.argotunnel.com 7844

12. 고가용성: Replica

프로덕션에서 터널 하나, cloudflared 하나로 운영하면 단일 장애점이 된다. Replica를 쓰면 해결된다.

같은 터널을 여러 호스트에서 실행하면 된다:

bash
# 호스트 A
cloudflared tunnel run my-tunnel
 
# 호스트 B (같은 터널, 같은 credentials)
cloudflared tunnel run my-tunnel
bash
# 호스트 A
cloudflared tunnel run my-tunnel
 
# 호스트 B (같은 터널, 같은 credentials)
cloudflared tunnel run my-tunnel

Cloudflare는 자동으로 지리적으로 가장 가까운 Replica로 라우팅한다. 하나가 죽으면 나머지로 자동 페일오버된다.

Docker Compose에서도 deploy.replicas로 여러 인스턴스를 띄울 수 있다.

13. 실전 활용: SSH 접속

Cloudflare Tunnel로 SSH를 연결하면, 22번 포트를 인터넷에 열지 않고도 어디서든 서버에 접속할 수 있다.

서버 측 설정

config.yml에 SSH 서비스를 추가한다:

yaml
ingress:
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - service: http_status:404
yaml
ingress:
  - hostname: ssh.example.com
    service: ssh://localhost:22
  - service: http_status:404

클라이언트 측 설정

접속하는 쪽에도 cloudflared를 설치하고, SSH config에 ProxyCommand를 추가한다:

# ~/.ssh/config
Host ssh.example.com
  ProxyCommand cloudflared access ssh --hostname %h
# ~/.ssh/config
Host ssh.example.com
  ProxyCommand cloudflared access ssh --hostname %h

이제 평소처럼 SSH 접속하면 된다:

bash
ssh user@ssh.example.com
bash
ssh user@ssh.example.com

내부적으로는 cloudflared가 Cloudflare를 경유해서 터널을 통해 SSH 트래픽을 전달한다. 22번 포트는 닫힌 채로.

Cloudflare Access 정책을 추가하면 이메일 인증, SSO 등 추가 인증 계층도 붙일 수 있다.

14. Remotely-managed vs Locally-managed

어떤 걸 써야 할까? 상황에 따라 다르다.

Remotely-managed가 좋은 경우:

  • GUI로 편하게 관리하고 싶을 때
  • 여러 사람이 관리할 때
  • 설정을 중앙에서 통합 관리하고 싶을 때

Locally-managed가 좋은 경우:

  • config를 Git으로 버전 관리하고 싶을 때
  • CI/CD 파이프라인에 터널 설정을 포함시킬 때
  • IaC(Infrastructure as Code)를 실천할 때

두 방식 모두 기능적으로는 동일하다. 관리 방식의 차이일 뿐이다.

포트포워딩은 이제 안녕

나도 한동안 포트포워딩과 Nginx 리버스 프록시 조합으로 홈 서버를 운영했다. Cloudflare Tunnel로 바꾸고 나서 가장 크게 달라진 건, 방화벽 인바운드 규칙을 전부 지웠는데도 외부 접속이 잘 된다는 사실이었다. 그 안도감은 직접 경험해봐야 안다.

Quick Tunnel로 10초만 체험해보면 감이 잡힐 거다. cloudflared tunnel --url http://localhost:8080 이 한 줄이면 된다.

참고 자료

YouTube 영상

채널 보기
AI를 위한 선형대수학 - 소개 | 선형대수학
숫자 하나가 AI 모델의 운명을 바꾼다? | 선형대수학
트라이(Trie)에서 단어를 삭제하는 방법 | Trie 자료구조 이야기
트라이(Trie) 자료구조: 파이썬으로 삽입(Insert) 연산 구현하기 | Trie 자료구조 이야기
Trie 자료구조 완전 정복 - 개념부터 시각화까지 | Trie 자료구조 이야기
Trie 자료구조 완전 정복 - 개념부터 시각화까지 | Trie 자료구조 이야기
벡터의 정의와 덧셈 연산 | 선형대수학
7편, 파이썬으로 구현하는 B-Tree