테크 경제

Tailscale과 Cloudflare Tunnel 동시 사용 시 DNS 충돌 해결 — Ubuntu 24.04 실전 가이드

Tailscale과 Cloudflare Tunnel 동시 사용 시 DNS 충돌 해결 — Ubuntu 24.04 실전 가이드

홈서버에 Tailscale 깔고 Cloudflare Tunnel도 연결했는데, 갑자기 내부 서비스가 안 열리는 상황 겪어봤나요? DNS가 꼬여서 100.x.x.x 주소로 접속이 안 되거나, 터널로 연결한 도메인이 엉뚱한 곳으로 가는 경우 — 저도 처음엔 이유를 몰라서 3시간 넘게 헤맸어요. 이 가이드는 Ubuntu 24.04 기준으로, 두 도구를 동시에 쓸 때 생기는 DNS 충돌 패턴과 해결 방법을 실제 설정 파일 기반으로 정리했어요.

핵심 요약

  • Tailscale은 100.100.100.100 MagicDNS를 시스템 DNS 앞에 끼워넣는데, Cloudflare Tunnel의 cloudflared도 로컬 DNS 프록시를 써요. 이 둘이 충돌하면 내부망 해석이 무너져요.
  • Ubuntu 24.04는 systemd-resolved를 기본 DNS 리졸버로 사용해요. 이 파일 하나를 잘못 건드리면 두 도구 모두 먹통이 돼요.
  • 해결의 핵심은 Split DNS — Tailscale 도메인(ts.net)은 Tailscale이, 퍼블릭 도메인은 Cloudflare가 맡도록 라우팅을 명확히 분리해야 해요.
  • tailscale up --accept-dns=false 옵션으로 MagicDNS를 비활성화한 뒤, systemd-resolved에 직접 조건부 DNS 설정을 넣는 방식이 가장 안정적이에요.
  • Tailscale v1.78+, cloudflared 2025.x 버전 조합에서 이 가이드가 검증됐어요.

왜 이 두 도구를 같이 쓰면 DNS가 꼬이는가

Tailscale과 Cloudflare Tunnel은 각자 훌륭한 도구예요. Tailscale은 WireGuard 기반 메시 VPN으로, 홈서버를 외부에서 안전하게 접속하는 데 쓰이고, Cloudflare Tunnel은 포트 포워딩 없이 퍼블릭 도메인으로 서비스를 노출하는 데 써요.

문제는 둘 다 DNS를 건드린다는 거예요.

  • Tailscale MagicDNS: 100.100.100.100을 시스템 DNS에 주입해서 hostname.tail12345.ts.net 같은 주소를 해석해요.
  • cloudflared: 기본적으로 DoH(DNS over HTTPS)를 1.1.1.1로 연결하지만, --proxy-dns 옵션이나 일부 설정에서 로컬 포트를 점유해요.
  • systemd-resolved: Ubuntu 24.04의 기본 리졸버인데, 127.0.0.53을 Listen 포트로 써요. 여기서 설정이 꼬이면 두 서비스 모두 영향받아요.

세 요소가 뒤엉키면 결국 이런 증상이 나타나요:

  • Tailscale 내부 주소(100.x.x.x)로 ping은 되는데 서비스 이름으로 접속 안 됨
  • Cloudflare 터널 도메인이 외부에서 열리지 않거나 타임아웃
  • nslookup으로 확인하면 엉뚱한 DNS 서버가 응답

구성 방식 비교

항목Split DNS (이 가이드 방식)MagicDNS 전면 비활성화cloudflared 없이 Nginx Proxy
설정 복잡도중간낮음낮음
Tailscale 내부 DNS정상 작동수동 관리 필요정상 작동
퍼블릭 도메인Cloudflare 처리Cloudflare 처리포트 개방 필요
보안높음 (포트 비노출)높음낮음 (80/443 개방)
유지보수중간높음 (호스트 관리)낮음
권장 상황두 도구 동시 사용단순 VPN 전용방화벽 제어 가능 환경

Split DNS 방식이 복잡해 보이지만, 한 번 잡아두면 두 도구가 서로 간섭 없이 각자 역할을 해요. MagicDNS를 아예 끄면 Tailscale의 핵심 편의 기능 하나가 날아가버리거든요.


단계별 설정 가이드

사전 준비

  • Ubuntu 24.04 LTS (서버 또는 데스크탑 버전)
  • Tailscale v1.78 이상 설치 완료, 계정 연결 상태
  • cloudflared 2025.x 버전, Cloudflare 계정 및 도메인 연결 완료
  • sudo 권한

Step 1: 현재 DNS 상태 진단

설정 바꾸기 전에 지금 상태를 먼저 확인해요.

# systemd-resolved 현재 상태 확인
resolvectl status

# Tailscale이 주입한 DNS 확인
resolvectl dns tailscale0

# cloudflared가 점유한 포트 확인 (5053이 기본)
ss -tlnp | grep cloudflared

# DNS 해석 테스트: Tailscale 내부 주소
resolvectl query $(tailscale status --json | jq -r '.Self.DNSName')

출력 결과에서 tailscale0 인터페이스에 100.100.100.100이 잡혀있고, eth0ens3에도 다른 DNS가 잡혀있으면 충돌 가능 상태예요.


Step 2: Tailscale MagicDNS 분리 모드로 재시작

MagicDNS를 완전히 끄는 게 아니라, 시스템 DNS를 덮어쓰지 않도록 설정을 바꿔요.

# 기존 Tailscale 연결 해제 후 DNS 자동 설정 비활성화로 재연결
sudo tailscale up \
  --accept-dns=false \
  --accept-routes=true

# 재연결 후 상태 확인
tailscale status

--accept-dns=false 옵션이 핵심이에요. 이렇게 하면 Tailscale이 100.100.100.100을 시스템 전체 DNS로 밀어넣지 않아요. 대신 Step 3에서 직접 라우팅을 잡아줄 거예요.


Step 3: systemd-resolved에 Split DNS 설정

/etc/systemd/resolved.conf.d/ 디렉토리에 설정 파일을 새로 만들어요. 기존 resolved.conf를 직접 수정하면 시스템 업데이트 때 덮어쓰일 수 있어서, 드롭인 방식을 써요.

sudo mkdir -p /etc/systemd/resolved.conf.d/
sudo nano /etc/systemd/resolved.conf.d/tailscale-cloudflare.conf

파일 내용:

# Tailscale + Cloudflare Tunnel Split DNS 설정
# Tailscale MagicDNS는 ts.net 도메인만 처리
# 그 외 퍼블릭 DNS는 Cloudflare DoH로 처리

[Resolve]
# 기본 DNS: Cloudflare (퍼블릭 도메인용)
DNS=1.1.1.1 1.0.0.1
FallbackDNS=8.8.8.8

# DNSSEC 검증 활성화
DNSSEC=yes

# mDNS 비활성화 (홈서버 환경에서 불필요)
MulticastDNS=no

저장 후 Tailscale 인터페이스에 100.100.100.100을 직접 연결해요:

# tailscale0 인터페이스에 MagicDNS를 ts.net 도메인 전용으로 설정
sudo resolvectl dns tailscale0 100.100.100.100
sudo resolvectl domain tailscale0 ts.net ~ts.net

# systemd-resolved 재시작
sudo systemctl restart systemd-resolved

# 설정 확인
resolvectl status tailscale0

~ts.net 앞의 ~가 핵심이에요. 이 표기가 “ts.net 도메인은 이 DNS 서버로만 보내라"는 라우팅 규칙을 만들어줘요.


Step 4: cloudflared 설정 검토

cloudflared 서비스 파일에서 DNS 프록시 관련 옵션을 확인해요.

# cloudflared 서비스 상태 확인
sudo systemctl status cloudflared

# 설정 파일 위치 확인
cat /etc/cloudflared/config.yml

config.yml에서 아래 항목이 없는지 확인해요. 있으면 제거하세요:

# 이 설정은 로컬 DNS 포트를 점유해서 충돌 유발 — 제거할 것
# proxy-dns: true
# proxy-dns-port: 53

# 올바른 최소 설정
tunnel: your-tunnel-id
credentials-file: /etc/cloudflared/your-tunnel-id.json

ingress:
  - hostname: yourdomain.com
    service: http://localhost:8080
  - service: http_status:404

변경 후 서비스 재시작:

sudo systemctl restart cloudflared
sudo systemctl status cloudflared

Step 5: 부팅 시 자동 적용 스크립트

재부팅하면 resolvectl dns tailscale0 설정이 날아가요. systemd 서비스로 고정해줘야 해요.

sudo nano /etc/systemd/system/tailscale-dns-fix.service
[Unit]
Description=Tailscale Split DNS 설정 적용
After=network-online.target tailscaled.service
Wants=network-online.target

[Service]
Type=oneshot
# tailscale0 인터페이스에 MagicDNS 라우팅 재적용
ExecStart=/usr/bin/resolvectl dns tailscale0 100.100.100.100
ExecStart=/usr/bin/resolvectl domain tailscale0 ts.net ~ts.net
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable tailscale-dns-fix
sudo systemctl start tailscale-dns-fix

동작 확인 및 실전 예시

설정 완료 후 검증

# 1. Tailscale 내부 DNS 해석 확인
resolvectl query myserver.tail12345.ts.net
# 예상 결과: 100.x.x.x 주소 반환

# 2. 퍼블릭 도메인 해석 확인 (Cloudflare 터널)
resolvectl query yourdomain.com
# 예상 결과: Cloudflare IP 반환 (104.x.x.x 또는 172.x.x.x)

# 3. 일반 인터넷 도메인 확인
resolvectl query google.com
# 예상 결과: 1.1.1.1이 응답

# 4. Tailscale 터널을 통한 서비스 접속 테스트
curl -v http://myserver.tail12345.ts.net:3000

흔한 에러와 해결

# 에러: "NXDOMAIN" — ts.net 도메인이 해석 안 될 때
# 원인: tailscale0 인터페이스 DNS가 빠진 경우
resolvectl dns  # 모든 인터페이스 DNS 출력으로 확인

# 에러: cloudflared "context deadline exceeded"
# 원인: 1.1.1.1 연결 자체가 Tailscale 라우팅에 걸린 경우
# 해결: Tailscale에서 1.1.1.1 트래픽 제외
sudo tailscale up --accept-dns=false --exit-node-allow-lan-access=true

자주 빠지는 함정과 체크리스트

흔한 실수

  • /etc/resolv.conf 직접 수정: Ubuntu 24.04에서 이 파일은 systemd-resolved가 심볼릭 링크로 관리해요. 직접 수정하면 재부팅 때 초기화돼요.

    • 해결: 반드시 resolved.conf.d/ 드롭인 방식 사용
  • cloudflared proxy-dns 옵션 활성화 상태: 이 옵션이 켜져 있으면 포트 53을 점유하려 해서 systemd-resolved와 충돌해요.

    • 해결: config.yml에서 해당 옵션 제거
  • Tailscale 업데이트 후 MagicDNS 재활성화: tailscale up 재실행 시 --accept-dns=false를 빠뜨리면 다시 원점이에요.

    • 해결: /etc/tailscale/에 설정 스크립트로 관리

운영 전 체크리스트

  • resolvectl status tailscale0에서 100.100.100.100 확인
  • resolvectl query [ts.net 호스트명] 성공
  • resolvectl query [퍼블릭 도메인] Cloudflare IP 반환
  • 재부팅 후 tailscale-dns-fix 서비스 자동 실행 확인
  • cloudflared proxy-dns 옵션 비활성화 상태 확인
  • Tailscale 연결 시 --accept-dns=false 옵션 포함 확인

마무리

설정이 좀 많아 보이지만, 핵심은 딱 두 가지예요.

Tailscale에게는 ts.net만 맡기고, 나머지는 Cloudflare에게 맡긴다.

--accept-dns=false로 MagicDNS의 시스템 전체 점령을 막고, resolvectl domain tailscale0 ~ts.net으로 라우팅을 명확히 분리하면, 두 도구가 서로 발 안 밟고 잘 돌아가요. 참고로 이 방식이 항상 정답은 아니에요 — 단순히 VPN만 쓰는 환경이라면 MagicDNS 전면 비활성화 쪽이 오히려 관리하기 편할 수 있어요.

Step 1부터 순서대로 따라가면 30분 안에 잡힐 거예요. 설정 중에 막히는 부분이나 특이한 에러가 생기면 댓글로 남겨주세요. 비슷한 상황 겪은 분들이 꽤 많거든요.

다음에 볼 것: Tailscale ACL로 서비스별 접근 제어 + Cloudflare Access와 연동해서 외부 접속에 SSO 붙이는 방법도 이어서 정리할 예정이에요.


참고: Tailscale 공식 문서 - DNS 설정 | Cloudflare Tunnel 공식 문서

참고자료

  1. [홈서버 VPN 끝판왕] WireGuard 대신 Tailscale + NPM + Split DNS 완벽 구축 가이드
  2. Tailscale VPN 실무 가이드: 설치부터 운영까지의 실전 구성 방법 :: GilliLab - 정보관리기술사 노트
  3. Tailscale - 나무위키

Photo by NASA on Unsplash