Cloudflare Tunnel로 로컬 서버를 안전하게 공개하기
로컬에서 개발 중인 웹 서버를 외부에 공개해야 할 때 어떤 방법을 쓰시나요? ngrok같은 터널링 도구를 많이 사용하는데, 무료 플랜에서는 임시 URL이 매번 바뀌고 커스텀 도메인도 쓸 수 없어서 불편할 때가 있습니다.
Cloudflare Tunnel은 Cloudflare에서 제공하는 무료 터널링 서비스입니다. 내 컴퓨터에서 Cloudflare 네트워크까지 암호화된 아웃바운드 터널을 만들어주기 때문에 방화벽 포트를 열거나 공유기 설정을 건드릴 필요가 없어요. 게다가 자기 소유의 도메인을 연결할 수 있고, Cloudflare의 DDoS 보호와 WAF까지 자동으로 적용됩니다.
이번 글에서는 Cloudflare Tunnel의 동작 원리부터 cloudflared CLI 설치, 빠른 터널과 영구 터널 설정, 시스템 서비스 등록, 그리고 로컬 장비에서 운영할 때의 한계까지 살펴보겠습니다.
Cloudflare Tunnel이란?
Cloudflare Tunnel은 내 서버와 Cloudflare 엣지 네트워크 사이에 암호화된 터널을 만들어주는 서비스입니다. 핵심은 아웃바운드 연결만 사용한다는 점이에요. 여기서 아웃바운드는 내 서버 기준으로 연결을 누가 먼저 시작하느냐를 가리킵니다. 데이터가 한쪽 방향으로만 흐른다는 뜻은 아니에요.
연결 시작: 내 서버의 cloudflared ---> Cloudflare 엣지
데이터 흐름: 내 서버의 cloudflared <==> Cloudflare 엣지
cloudflared가 먼저 Cloudflare로 나가는 연결을 열고, 이 연결이 만들어진 뒤에는 터널 안에서 요청과 응답이 양방향으로 오갑니다.
그래서 “아웃바운드 전용”은 외부에서 내 서버 IP와 포트로 새 연결을 직접 시작할 수 없다는 뜻에 가깝습니다.
일반적으로 외부에서 로컬 서버에 접속하려면 방화벽에서 특정 포트를 열어야 합니다. 포트 포워딩을 설정하거나, 클라우드 서버에 배포하거나, 아니면 ngrok 같은 도구로 터널을 뚫거나요. 이 방법들은 각각 보안 위험이 있거나, 번거롭거나, 제약이 많습니다.
Cloudflare Tunnel은 접근 방식이 다릅니다. 내 서버에서 Cloudflare 방향으로 나가는 연결만 맺고, 사용자의 새 연결은 Cloudflare가 대신 받아서 터널을 통해 전달합니다. 서버 쪽에서 인바운드 포트를 전혀 열지 않아도 되니까 보안이 훨씬 강화되죠.
그러면 실제로 트래픽이 어떻게 흘러가는지 정리해보겠습니다.
- 내 서버에서
cloudflared라는 데몬이 Cloudflare 엣지로 나가는 지속 연결을 엽니다. - 사용자가
my-app.example.com같은 도메인으로 접속하면 Cloudflare가 요청을 받습니다. - Cloudflare가 해당 요청을 이미 열려 있는 터널 연결을 통해 내 서버로 전달합니다.
- 서버가 응답하면 같은 경로를 거쳐 사용자에게 돌아갑니다.
이 과정에서 사용자는 내 서버의 실제 IP 주소를 알 수 없고, 서버는 외부에 어떤 포트도 노출하지 않습니다.
cloudflared 설치
Cloudflare Tunnel을 사용하려면 cloudflared라는 CLI 도구를 설치해야 합니다.
이 도구가 터널을 생성하고 관리하는 역할을 합니다.
macOS에서는 Homebrew로 간단하게 설치할 수 있어요.
brew install cloudflared
Linux에서는 배포판에 따라 패키지 매니저로 설치합니다.
# Debian/Ubuntu
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# RHEL/CentOS
curl -L --output cloudflared.rpm https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
sudo rpm -i cloudflared.rpm
Windows에서는 GitHub 릴리스 페이지에서 .msi 파일을 다운로드하거나, winget을 사용할 수 있습니다.
winget install --id Cloudflare.cloudflared
설치가 완료되면 버전을 확인해봅시다.
cloudflared --version
cloudflared version 2025.2.1 (built 2025-02-10-1015 UTC)
빠른 터널 (Quick Tunnel)
가장 간편한 사용법은 계정 없이 바로 쓸 수 있는 빠른 터널입니다. 로컬에서 8080 포트로 웹 서버가 실행 중이라면 이렇게 한 줄만 입력하면 됩니다.
cloudflared tunnel --url http://localhost:8080
2025-02-15T10:30:00Z INF Requesting new quick Tunnel on trycloudflare.com...
2025-02-15T10:30:01Z INF +--------------------------------------------------------------------------------------------+
2025-02-15T10:30:01Z INF | Your quick Tunnel has been created! Visit it at (it may take some time to be reachable): |
2025-02-15T10:30:01Z INF | https://random-words-here.trycloudflare.com |
2025-02-15T10:30:01Z INF +--------------------------------------------------------------------------------------------+
https://random-words-here.trycloudflare.com 같은 임시 URL이 생성되고, 이 주소로 외부에서 내 로컬 서버에 접속할 수 있습니다.
Cloudflare 계정도 필요 없고 인증 토큰 설정도 없어서 정말 빠르게 시작할 수 있어요.
다만 빠른 터널에는 몇 가지 제한이 있습니다.
URL이 매번 바뀌고, 커스텀 도메인을 연결할 수 없고, cloudflared 프로세스를 종료하면 터널도 사라집니다.
한 가지 더 주의할 점이 있는데요.
빠른 터널 URL은 무작위라 추측하기는 어렵지만, 주소를 아는 사람은 누구나 접속할 수 있습니다.
그래서 인증 없는 관리자 페이지나 민감한 엔드포인트가 그대로 노출되지 않는지 한 번 살펴보는 게 좋아요.
또 trycloudflare.com 엣지가 text/event-stream 응답을 버퍼링하기 때문에 SSE(Server-Sent Events)가 클라이언트까지 전달되지 않는다는 점도 알아두면 좋습니다. (WebSocket은 정상 동작해요.)
LLM 응답 스트리밍처럼 SSE를 쓰는 서비스를 테스트한다면 빠른 터널 대신 영구 터널을 쓰는 게 안전합니다.
데모나 임시 테스트 용도로는 충분하지만, 지속적으로 사용하려면 영구 터널을 설정하는 게 좋습니다.
Cloudflare 로그인
영구 터널을 만들려면 먼저 Cloudflare 계정에 로그인해야 합니다.
cloudflared tunnel login
이 명령어를 실행하면 브라우저가 열리면서 Cloudflare 로그인 페이지로 이동합니다. 로그인하고 터널에 사용할 도메인을 선택하면 인증서가 자동으로 다운로드됩니다.
A browser window should have opened at the following URL:
https://dash.cloudflare.com/argotunnel?aud=&callback=https%3A%2F...
If the browser failed to open, please visit the URL above directly in your browser.
2026-06-27T18:34:53Z INF You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
/Users/dale/.cloudflared/cert.pem
인증서는 ~/.cloudflared/cert.pem에 저장되며, 이후 터널 생성과 관리에 사용됩니다.
이 인증서는 한 번만 발급받으면 되고, 여러 터널에서 공유할 수 있습니다.
영구 터널 만들기
이제 본격적으로 영구 터널을 만들어봅시다. 터널 이름은 자유롭게 지을 수 있습니다.
cloudflared tunnel create my-tunnel
Tunnel credentials written to /Users/dale/.cloudflared/a1b2c3d4-5678-90ab-cdef-1234567890ab.json
Created tunnel my-tunnel with id a1b2c3d4-5678-90ab-cdef-1234567890ab
터널이 생성되면 고유한 UUID가 부여되고, 해당 터널의 자격 증명 파일이 ~/.cloudflared/ 디렉토리에 JSON 형식으로 저장됩니다.
이 파일은 터널을 실행할 때 인증에 사용되므로 안전하게 보관해야 합니다.
생성된 터널 목록은 다음 명령어로 확인할 수 있어요.
cloudflared tunnel list
ID NAME CREATED CONNECTIONS
a1b2c3d4-5678-90ab-cdef-1234567890ab my-tunnel 2025-02-15T10:45:00Z
DNS 레코드 연결
터널을 만들었으면 도메인의 DNS 레코드를 터널에 연결해야 합니다. 이렇게 하면 해당 도메인으로 들어오는 요청이 터널을 통해 내 서버로 전달됩니다.
cloudflared tunnel route dns my-tunnel my-app.example.com
INF Added CNAME my-app.example.com which will route to this tunnel tunnelID=a1b2c3d4-5678-90ab-cdef-1234567890ab
이 명령어는 Cloudflare DNS에 CNAME 레코드를 자동으로 추가합니다.
my-app.example.com이 터널의 UUID를 가리키는 a1b2c3d4-5678-90ab-cdef-1234567890ab.cfargotunnel.com으로 연결되는 거예요.
주의할 점은 이 명령어가 DNS 레코드만 만든다는 것입니다.
~/.cloudflared/config.yml 파일을 자동으로 만들거나 수정하지는 않아요.
DNS 레코드는 “이 호스트명으로 들어온 요청을 이 터널로 보내라”는 Cloudflare 쪽 설정이고, config.yml은 “터널로 들어온 요청을 어떤 로컬 서비스로 넘겨라”는 cloudflared 쪽 설정입니다.
서브도메인뿐 아니라 루트 도메인(example.com)도 연결할 수 있고, 하나의 터널에 여러 도메인을 연결하는 것도 가능합니다.
설정 파일 작성
터널을 실행할 때마다 옵션을 일일이 지정하는 건 번거롭습니다. 설정 파일을 만들어두면 편리하게 관리할 수 있어요.
~/.cloudflared/config.yml 파일을 다음과 같이 작성합니다.
tunnel: a1b2c3d4-5678-90ab-cdef-1234567890ab
credentials-file: /Users/dale/.cloudflared/a1b2c3d4-5678-90ab-cdef-1234567890ab.json
ingress:
- hostname: my-app.example.com
service: http://localhost:8080
- service: http_status:404
tunnel에는 터널 UUID를, credentials-file에는 터널 자격 증명 파일 경로를 지정합니다.
ingress 섹션이 핵심인데요, 어떤 호스트명으로 들어오는 요청을 어디로 보낼지 라우팅 규칙을 정의합니다.
마지막 규칙은 반드시 hostname 없이 기본 응답을 지정해야 합니다.
여기서는 매칭되지 않는 요청에 404를 반환하도록 설정했어요.
여러 서비스를 하나의 터널로 묶는 것도 가능합니다.
tunnel: a1b2c3d4-5678-90ab-cdef-1234567890ab
credentials-file: /Users/dale/.cloudflared/a1b2c3d4-5678-90ab-cdef-1234567890ab.json
ingress:
- hostname: app.example.com
service: http://localhost:3000
- hostname: api.example.com
service: http://localhost:8080
- hostname: grafana.example.com
service: http://localhost:3001
- service: http_status:404
이런 식으로 하나의 터널로 여러 서브도메인을 각각 다른 로컬 서비스에 연결할 수 있습니다.
다만 위처럼 호스트명이 3개라면 DNS 레코드도 3개가 필요합니다.
example.com 같은 루트 도메인 하나만 터널에 연결한다고 해서 app.example.com, api.example.com, grafana.example.com이 자동으로 따라오지는 않아요.
가장 명확한 방법은 각 호스트명을 같은 터널에 하나씩 연결하는 것입니다.
cloudflared tunnel route dns my-tunnel app.example.com
cloudflared tunnel route dns my-tunnel api.example.com
cloudflared tunnel route dns my-tunnel grafana.example.com
서브도메인이 많다면 와일드카드 DNS를 쓸 수도 있습니다.
cloudflared tunnel route dns my-tunnel "*.example.com"
와일드카드는 app.example.com처럼 서브도메인을 한꺼번에 터널로 보내는 데 유용합니다.
하지만 일반적으로 example.com 루트 도메인 자체는 포함하지 않으므로, 루트 도메인도 터널로 보내고 싶다면 별도로 연결해야 합니다.
cloudflared tunnel route dns my-tunnel example.com
터널 실행
설정 파일을 작성했으면 터널을 실행합니다.
cloudflared tunnel run my-tunnel
2025-02-15T11:00:00Z INF Starting tunnel tunnelID=a1b2c3d4-5678-90ab-cdef-1234567890ab
2025-02-15T11:00:00Z INF Version 2025.2.1
2025-02-15T11:00:00Z INF ICMP proxy will use 192.168.1.100 as source for IPv4
2025-02-15T11:00:01Z INF Connection registered connIndex=0 connection=abc123 location=NRT
2025-02-15T11:00:01Z INF Connection registered connIndex=1 connection=def456 location=KIX
2025-02-15T11:00:02Z INF Connection registered connIndex=2 connection=ghi789 location=NRT
2025-02-15T11:00:02Z INF Connection registered connIndex=3 connection=jkl012 location=KIX
로그를 보면 Cloudflare 엣지 로케이션(NRT는 도쿄, KIX는 오사카)에 4개의 연결이 생성된 걸 확인할 수 있습니다.
기본적으로 cloudflared는 가장 가까운 두 곳의 데이터 센터에 각각 2개씩 총 4개의 연결을 유지해서 안정성을 높입니다.
이제 브라우저에서 https://my-app.example.com으로 접속하면 로컬의 8080 포트로 실행 중인 서버에 연결됩니다.
SSL 인증서도 Cloudflare가 자동으로 처리해주기 때문에 별도로 설정할 게 없어요.
터널은 호스팅이 아니다
Cloudflare Tunnel을 처음 쓰면 my-app.example.com 같은 고정 주소가 생기니 마치 로컬 앱이 클라우드에 배포된 것처럼 느껴질 수 있습니다.
하지만 Cloudflare Tunnel은 호스팅 서비스가 아니라 Cloudflare 엣지와 내 원본 서버(origin) 사이의 연결 경로를 만들어주는 도구입니다.
Cloudflare가 앞단에서 요청을 받아주더라도, 뒤쪽에서 cloudflared가 살아 있지 않으면 그 요청을 로컬 서비스까지 전달할 길이 없습니다.
그래서 노트북에서 cloudflared tunnel run my-tunnel을 실행해두었다면, 노트북이 켜져 있고 네트워크에 붙어 있는지가 곧 서비스 가용성입니다.
터미널을 닫거나, cloudflared 프로세스가 죽거나, 와이파이가 끊기거나, 노트북이 절전 모드에 들어가면 외부에서는 더 이상 접속할 수 없어요.
DNS 레코드는 그대로 남아 있어도, Cloudflare에서 원본 서버로 이어지는 커넥터가 사라진 상태이기 때문입니다.
로컬 웹 서버 자체가 꺼진 경우도 마찬가지입니다.
이때는 터널이 살아 있더라도 localhost:8080 같은 대상 포트로 연결할 수 없어서 요청이 실패합니다.
이 한계는 ngrok과 본질적으로 같습니다. ngrok URL이 있어도 노트북이 꺼져 있으면 접속할 수 없는 것처럼, Cloudflare Tunnel도 실행 중인 커넥터와 실제 원본 서비스가 필요합니다. 다만 Cloudflare Tunnel은 커스텀 도메인, DNS 연동, Access 정책, WAF 같은 운영 기능을 붙이기 좋다는 점에서 차이가 납니다. 주소가 고정된다고 해서 서버가 대신 켜져 있는 것은 아니라는 점만 분명히 기억하면 됩니다.
상시 접속이 필요하다면 터널을 노트북이 아니라 항상 켜져 있는 장비에서 실행하는 편이 안전합니다.
홈 서버, NAS, 라즈베리 파이, 작은 VPS처럼 전원과 네트워크가 안정적인 환경이 더 잘 맞아요.
Cloudflare Tunnel은 같은 터널에 cloudflared 복제본(replica)을 여러 개 붙여 가용성을 높일 수도 있습니다.
공식 문서에서도 여러 cloudflared 인스턴스를 같은 터널에 연결해 한 호스트가 내려가도 나머지 복제본이 트래픽을 계속 처리할 수 있다고 설명합니다.
다만 복제본도 결국 원본 서비스에 도달할 수 있어야 합니다.
앱이 오직 내 노트북의 localhost에서만 실행 중이라면, 다른 장비에 커넥터를 하나 더 띄워도 노트북이 꺼진 순간 대신 응답할 서비스는 없습니다.
시스템 서비스로 등록
터미널을 닫으면 터널도 같이 종료되겠죠.
서버를 재시작해도 터널이 자동으로 실행되게 하려면 시스템 서비스로 등록하는 게 좋습니다.
앞에서 본 것처럼 이 설정은 cloudflared 프로세스를 백그라운드에서 계속 띄워두기 위한 장치이지, 꺼진 컴퓨터를 대신 켜주는 기능은 아닙니다.
노트북을 닫으면 절전 모드로 들어가는 환경에서는 서비스로 등록해도 터널이 끊길 수 있어요.
macOS에서는 launchd 서비스로 등록됩니다.
sudo cloudflared service install
설치하면 부팅 시 자동으로 시작하도록 등록됩니다. 바로 실행하거나 상태를 확인하려면 다음 명령어를 사용합니다.
# 서비스 시작
sudo launchctl start com.cloudflare.cloudflared
# 서비스 상태 확인
sudo launchctl list | grep cloudflare
Linux에서는 다음 명령어로 systemd 서비스를 설치할 수 있어요.
sudo cloudflared service install
이 명령어는 ~/.cloudflared/config.yml 파일을 /etc/cloudflared/config.yml로 복사하고, systemd 유닛 파일을 생성합니다.
# 서비스 시작
sudo systemctl start cloudflared
# 서비스 상태 확인
sudo systemctl status cloudflared
# 부팅 시 자동 시작 설정
sudo systemctl enable cloudflared
서비스를 제거하고 싶을 때는 uninstall 명령어를 사용합니다.
sudo cloudflared service uninstall
ngrok과 비교
ngrok도 로컬 서버를 외부에 공개하는 대표적인 도구인데요, Cloudflare Tunnel과는 성격이 좀 다릅니다.
ngrok은 빠른 시작에 최적화되어 있어요. 명령어 하나로 즉시 터널이 열리고 공개 URL을 받을 수 있습니다. 웹훅 테스트나 짧은 데모에는 ngrok이 더 간편합니다. 다만 무료 플랜에서는 URL이 매번 바뀌고, 커스텀 도메인을 사용하려면 유료 플랜이 필요합니다.
Cloudflare Tunnel은 무료로 커스텀 도메인을 연결할 수 있고, DDoS 방어와 WAF가 기본 제공됩니다.
시스템 서비스로 등록하면 서버 재시작 후에도 자동으로 터널이 복구되어서 상시 운영에 적합합니다.
단, 초기 설정이 ngrok보다는 단계가 많고, Cloudflare에 도메인이 등록되어 있어야 합니다.
무엇보다 cloudflared가 실행되는 원본 장비가 계속 켜져 있어야 한다는 전제도 같습니다.
정리하면 일시적인 테스트에는 ngrok이, 도메인을 연결한 지속적인 서비스 운영에는 Cloudflare Tunnel이 적합합니다. 다만 그 “지속적인 운영”은 노트북 하나에 기대기보다, 항상 켜져 있는 장비나 여러 커넥터를 둔 구성을 전제로 생각하는 편이 현실적입니다. 물론 Cloudflare Tunnel의 빠른 터널 기능을 쓰면 ngrok처럼 간편하게 임시 URL을 받을 수도 있으니 상황에 맞게 선택하면 됩니다.
요즘 빠른 터널이 부쩍 많이 보이는 이유
최근 들어 trycloudflare.com으로 끝나는 URL을 자주 마주치게 되는데요.
AI 코딩 에이전트와 MCP 서버, 샌드박스 도구가 늘면서 “로컬이나 격리된 환경에서 돌아가는 서비스를 잠깐 외부에 노출”하는 수요가 커졌기 때문입니다.
대표적으로 Cloudflare Sandbox SDK는 sandbox.tunnels.get(port) 한 줄로 컨테이너 안의 서비스를 빠른 터널로 공개합니다.
계정도 설정도 없이 즉석에서 공개 URL이 나오니, AI가 코드를 실행하고 그 결과를 바로 공유하는 흐름에 잘 맞거든요.
ngrok 무료 플랜이 점점 빡빡해진 것도 빠른 터널로 갈아타는 데 한몫했고요.
다만 앞서 살펴봤듯이 빠른 터널은 디버깅을 돕는 임시 도구이지 상시 운영용이 아닙니다. 그래서 이런 URL을 외부에서 받았다면, 영구적인 주소가 아니라 시간이 지나면 사라질 수 있다는 점을 염두에 두면 좋습니다.
마치며
Cloudflare Tunnel은 로컬 서버를 외부에 안전하게 공개할 수 있는 강력한 도구입니다. 인바운드 포트를 열지 않아도 되니 보안 측면에서 큰 장점이 있고, 무료로 커스텀 도메인과 SSL까지 제공되는 점이 매력적입니다.
빠른 터널로 간단히 시작해보고, 지속적으로 사용할 서비스가 있다면 영구 터널과 시스템 서비스 등록까지 진행해보세요. 홈 서버나 NAS처럼 계속 켜져 있는 장비를 외부에서 접속하거나, 개발 중인 웹 서비스를 안정적으로 공유하는 데 유용하게 쓸 수 있을 거예요. 반대로 노트북에서 잠깐 띄운 서버라면 “노트북이 꺼지는 순간 외부 접속도 끝난다”는 한계를 운영 조건에 꼭 넣어두는 게 좋습니다.
좀 더 깊이 알고 싶다면 Cloudflare Tunnel 공식 문서를 참고해보세요. 여러 커넥터를 붙여 가용성을 높이는 구성은 Tunnel availability and failover 문서에서 확인할 수 있습니다. 여러 기기를 하나의 사설 네트워크로 연결하는 데 관심이 있다면 Tailscale 활용법도 함께 살펴보시면 좋겠습니다.
This work is licensed under
CC BY 4.0