테일넷 접미사가 서버 호스트네임과 겹침
MagicDNS 접미사를 컨트롤 서버 자체 호스트네임의 상위나 하위로 고르면 이름 해석이 깨지는데, 그 모습이 꼭 DNS 장애처럼 보입니다. 둘을 형제 관계로 유지하십시오. 서버는 vpn.example.com, 테일넷은 ts.example.com처럼요.

메시 VPN에는 키를 나눠 줄 누군가가 필요합니다. Tailscale에서는 그 누군가가 회사이고, 순정 WireGuard에서는 그 누군가가 영원히 손수 작업하는 당신입니다. Headscale은 세 번째 답입니다. 같은 오픈소스 클라이언트가, 당신이 익명으로 빌린 서버 위의 코디네이션 서버와 대화합니다. 기기 지도와 접근 규칙, 폴백 릴레이까지 모두 당신 것인 테일넷을 만드는 여섯 단계 — Caddy 뒤의 Headscale, 문서화된 ACL, 자체 리졸버를 가리키는 MagicDNS, 서브넷 라우터, 엑시트 노드, 그리고 내장 DERP. 서버 비용은 월 $3.90입니다. Debian 12에서 검증했습니다.
선택
WireGuard · Tailscale · Headscale
프로비저닝
부팅 전에 고정하는 세 가지 이름
배포
Headscale, Caddy, 설정 파일 하나
등록
사용자, 사전 인증 키, 클라이언트
라우팅
서브넷, 엑시트 노드, ACL
Keep
DERP, 백업, 업그레이드
WireGuard는 일부러 미완성입니다. 두 공개 키 사이를 잇는 작고 빠르고 감사 가능한 터널일 뿐, 그 키들이 서로 어떻게 만나는지에 대해서는 아무런 의견도 갖지 않습니다. 바로 이 생략 덕분에 이 프로토콜이 이만큼 훌륭하고, 또 바로 그 때문에 기기 열다섯 대짜리 WireGuard 네트워크는 스프레드시트가 되어 버립니다. 모든 피어는 다른 모든 피어의 공개 키와 현재 주소를 알아야 하고, 사무실과 기차와 호텔을 오가는 노트북 한 대 한 대가 그 스프레드시트의 일부를 무효로 만듭니다.
메시 VPN은 바로 그 문제에 대한 답이며, 그 구조는 정확히 짚어 둘 가치가 있습니다. 무엇을 신뢰하게 되는지가 여기서 갈리기 때문입니다. 데이터 플레인은 예전 그대로입니다. 각 기기가 자기 키 쌍을 로컬에서 만들고, 개인 키는 자신만 쥔 채, 피어에게 직접 암호화해 보냅니다. 그 위에 컨트롤 플레인이 놓입니다. 공개 키와 엔드포인트를 모아, 각 기기에게 대화가 허용된 피어 목록을 알려 주고, 방해가 되는 라우터 뒤에 있는 두 기기가 서로를 찾도록 돕는 작은 서비스입니다. 이 서비스는 개인 키를 절대 쥐지 않고, 평문 바이트 하나 보는 일이 없습니다.
대신 쥐고 있는 것은 지도이고, 그 지도는 대부분의 사람이 짐작하는 것보다 훨씬 흥미롭습니다. 모든 기기 이름과 모든 사용자 이름. 모든 공개 키. 각 기기가 지금 이 순간 접속 중인 공개 주소는, 당신의 노트북과 휴대폰이 물리적으로 어디에 있는지 실시간으로 알려 주는 기록입니다. 각 기기가 접속하고 접속을 끊는 시각은 당신의 하루를 그려 냅니다. 그리고 누가 무엇에 닿을 수 있는지 정하는 정책은 당신의 조직을 그려 냅니다. 이 중 어느 것도 누군가의 악의를 필요로 하지 않습니다. 그저 코디네이션 서버가 존재하는 이유일 뿐입니다.
메타데이터보다 더 중요한 성질이 하나 더 있습니다. 컨트롤 플레인은 키를 배포하는 주체이므로, 키를 하나 추가할 수도 있는 주체이기도 합니다. 침해당했거나 강요당한 컨트롤 서버는 새 기계가 네트워크의 정당한 구성원이라고 당신의 기기들에게 말할 수 있고, 당신의 기기들은 원래 만들어진 대로 정직하게 그 기계에 암호화해 보낼 것입니다. Tailscale은 이 문제를 진지하게 받아들여 이를 막는 서명 기능까지 만들어 두었습니다. 셀프 호스팅은 같은 문제를 반대 방향에서 풉니다. 그 일을 할 수 있는 당사자가 바로 당신이 되는 것입니다. 네 가지 구성, 그리고 각각이 실제로 내주는 것은 다음과 같습니다.
| 속성 | 수동 WireGuard | Tailscale, 호스팅형 | 당신의 VPS 위 Headscale | Headscale + 나만의 DERP |
|---|---|---|---|---|
| 누가 페이로드를 읽을 수 있는가 | 피어 말고는 아무도 없습니다 | 피어 말고는 아무도 없습니다 | 피어 말고는 아무도 없습니다 | 피어 말고는 아무도 없습니다 |
| 누가 기기 지도를 쥐는가 | 당신, 텍스트 파일 하나로 | 한 회사가, 그 회사의 관할권 안에서 | 당신, 당신의 VPS 위에서 | 당신, 당신의 VPS 위에서 |
| 기기가 어디서 접속하는지 누가 보는가 | 오직 피어 당사자들뿐 | 코디네이션 서버를, 계속해서 | 당신의 서버를, 계속해서 | 당신의 서버를, 계속해서 |
| 다이렉트 연결이 실패하면 누가 릴레이하는가 | 아무도 없습니다 — 그냥 연결이 실패할 뿐 | 제공업체의 릴레이 네트워크 | 그래도 기본값은 여전히 제공업체의 릴레이 | 당신의 릴레이를, 당신의 데이터센터에 |
| 스무 번째 기기를 추가할 때 | 설정 수정 열아홉 번 | 명령어 하나 | 명령어 하나 | 명령어 하나 |
| 적합한 경우 | 기기 수가 적고, 이동하는 기기도 없음 | 운영 부담은 전혀 원하지 않고, 지도는 내주어도 괜찮다면 | 지도를 원하고, 작은 서버 한 대쯤은 운영할 수 있다면 | 지도뿐 아니라 폴백 경로까지 원한다면 |
첫 번째 열은 허수아비가 아닙니다. 고정된 서버 몇 대뿐이라면, 손으로 쓴 WireGuard야말로 정말로 더 나은 엔지니어링 답이고, 패치할 서비스도 하나 줄어듭니다. 이 가이드가 다루는 것은 그 스프레드시트가 더 이상 우습지 않게 되는 지점입니다.
"제 기계들이 서로에게 비공개로 닿게 하고 싶다"는 물음에는 정직한 답이 셋 있고, 이 가이드는 그중 하나일 뿐입니다. 순정 WireGuard가 가장 작습니다. 커널 모듈 하나, 피어당 설정 파일 하나, 인터페이스 말고는 데몬도 없고, 운영체제 말고는 최신 상태로 유지할 것도 없습니다. 당신 기기가 고정 주소를 가진 서버들이고 한 손으로 셀 수 있는 수라면, 여기서 멈추고 대신 WireGuard 가이드를 읽으십시오. 소프트웨어는 더 적고, 고장 날 수 있는 것의 목록은 더 짧아질 것입니다.
Tailscale은 셋 중 가장 큽니다. 밑바탕은 같은 WireGuard이고, 여기에 코디네이션 서비스, 거의 모든 환경 뒤에서도 작동하는 NAT 통과, 여러 대륙에 걸친 릴레이 네트워크, 웹 콘솔, 싱글 사인온, 지원 계약까지 더해집니다. 정말로 훌륭한 소프트웨어이고 무료 티어만으로도 가정에서는 충분합니다. 대가는 돈이 아닙니다. 당신이 소유한 모든 기기의 지도와, 각 기기가 어디서 접속하는지에 대한 기록이 한 회사의 데이터베이스에 저장된다는 것, 그것이 대가입니다.
Headscale은 세 번째 답이며, 둘보다 범위가 좁습니다. 코디네이션 서버를 오픈소스로 다시 구현한 것으로, 운영자는 당신이고, 대화 상대는 동일한 공식 클라이언트입니다. 자동화와 NAT 통과 기능은 그대로 누리는 대신, 운영해야 할 작은 서비스 하나와 기능이 일부뿐이라는 한계, 그리고 읽어야 하는 업그레이드를 떠안게 됩니다. 이것이 공짜라고 말할 사람은 없어야 합니다. 대신 정확히 한 가지를 얻습니다. 그리고 그것이 이 사이트가 신경 쓰는 바로 그것입니다. 지도를 쥔 사람이 당신 말고는 아무도 없다는 것.
수동 WireGuard 설정은 기기 수의 제곱으로 불어납니다. 피어 넷이면 링크 여섯 개, 열이면 마흔다섯 개, 스물이면 백구십 개입니다. 여기에 접속할 때마다 엔드포인트가 바뀌는 로밍 노트북까지 더하면, 유지보수는 더 이상 가끔 하는 일이 아니게 됩니다. 실무에서 이 갈림길은 대개 기기 다섯 대 즈음, 혹은 매일 이동하는 첫 기기가 생기는 시점, 혹은 "누구나 무엇에든 닿을 수 있다"로는 표현할 수 없는 첫 접근 규칙이 필요해지는 시점입니다.
설치할 것 자체가 달라지니, 나중이 아니라 지금 정해 둘 것이 하나 더 있습니다. 이 네트워크가 오직 당신만을 위한 것인지, 아니면 다른 사람도 합류할 것인지입니다. 혼자 쓰는 테일넷은 내부를 활짝 열어 두어도 아무 문제가 없습니다. 동료나 계약직, 가족이 함께 쓰는 테일넷은 첫 주부터 — 단계 05 — 문서화된 접근 규칙이 필요하고, 모두가 로그인을 나눠 쓰는 대신 사용자를 분리해 둬야 합니다. 미리 정해 두면 문단 하나면 끝나지만, 나중에 뜯어고치려면 오후 반나절이 듭니다.
먼저 서버부터 주문하십시오. 패널에서: 주문 → VPS → Sentinel, 이미지는 Debian 12, 지도가 자리 잡을 거점은 헬싱키, 스톡홀름, 오슬로, 레이캬비크 중 원하는 곳으로 고르십시오. 계정을 열 때 이메일 주소는 필요 없고, 청구서는 모네로, 비트코인, 라이트닝을 비롯한 다른 지원 자산으로 정산됩니다. 단계 06의 릴레이까지 운영할 생각이라면 기기 대부분과 가장 가까운 거점을 고르십시오. 릴레이까지의 지연은 그것이 실어 나르는 모든 연결의 지연이 되기 때문입니다.
그런 다음 첫 시간 하드닝 체크리스트를 실행하십시오. 옮긴 포트에서 키 전용 SSH, 무인 업그레이드, 기본 차단 방화벽입니다. 이 아래의 모든 내용은 이미 남의 손을 타지 않은 서버를 전제로 합니다. 그리고 이 서버는 보통보다 훨씬 값어치가 큽니다. 당신이 가진 모든 기기에서 어떤 키를 신뢰할지 결정하는 기계이기 때문입니다.
기기는 합류하는 순간 이 세 가지를 모두 자기 상태에 새겨 넣으므로, 나중에 하나라도 바꾸려면 전체 기기를 손수 재등록해야 합니다. 첫째: 컨트롤 서버 URL — 모든 클라이언트가 접속할 정확한 공개 HTTPS 주소로, 스킴과 호스트네임까지 포함합니다. 둘째: 테일넷 DNS 접미사 — MagicDNS가 내부적으로 응답할 도메인입니다. 반드시 당신이 관리하는 도메인이어야 하고, 컨트롤 서버 자체 호스트네임의 상위나 하위여서는 안 됩니다. 이것이 첫 부팅 실패의 가장 흔한 원인입니다. 셋째: 테일넷이 나눠 줄 주소 범위로, 관례상 IPv4에는 캐리어급 NAT 대역인 100.64.0.0/10을, IPv6에는 유니크 로컬 /48을 씁니다. 이미 사용 중인 네트워크와 겹치지 않는 한 기본값을 그대로 두십시오.
실제로 따라 할 수 있는 예시입니다. 컨트롤 서버는 vpn.example.com으로 응답하고, 테일넷 접미사는 ts.example.com이며, 주소 범위는 기본값 그대로 둡니다. 여기서 접미사가 컨트롤 서버 호스트네임의 상위가 아니라 형제 관계라는 점에 주목하십시오. vpn.example.com과 ts.example.com은 등록 가능 도메인은 공유하지만 어느 쪽도 다른 쪽을 포함하지 않습니다. A 레코드를, IPv6로도 접속하고 싶다면 AAAA 레코드까지 서버로 지정하고, 시작하기 전에 전파될 시간을 주십시오. 리버스 프록시가 처음 부팅될 때 바로 그 이름으로 인증서를 발급받으려 시도하기 때문입니다.
그런 다음 아직 아무것도 리스닝하지 않을 때 방화벽 규칙을 써 두십시오. 컨트롤 서버는 필연적으로 공개 HTTPS 엔드포인트입니다. 어떤 네트워크에 있든 모든 기기가 여기에 닿을 수 있어야 합니다. UDP 포트는 단계 06에서 나만의 릴레이를 운영할 생각일 때만 필요하며, 나중에 그것이 없어서 디버깅하는 것보다 지금 열어 두는 편이 쉽습니다.
ufw default deny incoming
ufw allow 47222/tcp # administrative SSH, moved
ufw allow 80/tcp # ACME HTTP-01 challenge
ufw allow 443/tcp # the control server, and DERP over HTTPS
ufw allow 3478/udp # STUN, only if you run your own relay
ufw enable
ufw status numbered
배포판 패키지 대신 공식 저장소에서 Docker를 설치해, Compose v2가 최신 버전으로 갖춰지게 하십시오. 그런 다음 백업할 가치가 있는 모든 것을 담을 디렉터리 두 개를 만드십시오. 하나는 설정용, 하나는 상태용입니다. 그 밖의 것을 거기 끼워 넣고 싶은 유혹은 참으십시오.
curl -fsSL https://get.docker.com | sh
docker compose version
install -d -m 0750 /srv/headscale/config
install -d -m 0750 /srv/headscale/data
cd /srv/headscale
Headscale과 Caddy, 그게 전부입니다. Headscale은 Docker 네트워크 안에서 평범한 HTTP로 리스닝하고, Caddy가 TLS를 종단하며 첫 부팅 때 인증서를 받아 요청 없이도 갱신합니다. 메트릭 리스너는 localhost에만 남겨 둡니다. 공개용이 아니라 내부 디버그용 창구이기 때문입니다.
파일을 보기 전에 규칙 하나. 이미지를 정확한 버전으로 고정하십시오. Headscale은 1.0 이전 소프트웨어로, 마이너 릴리스를 거치며 설정 키와 CLI 서브커맨드 이름이 한 번 이상 바뀌었습니다. 플로팅 태그를 쓰면 평범한 컨테이너 재시작 한 번이, 다른 모든 것에 닿게 해 주는 것이 본업인 서비스의 장애로 둔갑합니다.
/srv/headscale/docker-compose.yml
services:
headscale:
image: docker.io/headscale/headscale:0.26 # pin it; never :latest
container_name: headscale
command: serve
volumes:
- ./config:/etc/headscale
- ./data:/var/lib/headscale
ports:
- "3478:3478/udp" # STUN, for your own DERP
restart: unless-stopped
caddy:
image: caddy:2-alpine
container_name: caddy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
restart: unless-stopped
volumes:
caddy_data:
/srv/headscale/Caddyfile
vpn.example.com {
reverse_proxy headscale:8080
}
프록시 설정은 이것으로 끝입니다. Caddy는 HTTP/2를 구사하고 시키지 않아도 장기 연결을 업그레이드해 주는데, 모든 클라이언트가 지도 업데이트를 받으려고 연결 하나를 계속 열어 두는 이 상황에서 이 점이 중요합니다. 처음부터 새로 쓰지 말고, 고정해 둔 버전에 딸려 오는 샘플 설정 파일을 가져다 수정하십시오. 파일은 길지만 대부분은 이치에 맞는 기본값이고, 중요한 키는 다음과 같습니다.
/srv/headscale/config/config.yaml — the lines that matter
server_url: https://vpn.example.com # exactly what clients will dial
listen_addr: 0.0.0.0:8080 # inside the Docker network only
metrics_listen_addr: 127.0.0.1:9090 # debug surface, keep it local
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
noise:
private_key_path: /var/lib/headscale/noise_private.key
database:
type: sqlite # postgres for a large fleet
sqlite:
path: /var/lib/headscale/db.sqlite
dns:
magic_dns: true
base_domain: ts.example.com # NOT a parent or child of server_url
nameservers:
global:
- 1.1.1.1 # replaced by your own in step 05
일단 띄운 다음, 짐작하지 말고 로그를 읽으십시오. 인증서를 받지 못했거나 서버 호스트네임과 겹친다는 이유로 DNS 접미사를 거부한 첫 부팅은, 그 사실을 분명히 알리고 멈춥니다.
docker compose up -d
docker compose logs -f headscale
# From your laptop — a control server that is up answers this:
curl -s https://vpn.example.com/health
첫 부팅은 /srv/headscale/data에 개인 키 자료를 생성합니다. 기기가 단 한 대라도 등록되기 전에 지금 바로 안전한 곳에 복사해 두고, 첫 등록이 끝난 뒤 다시 한번 복사하십시오. 이 키를 잃으면 그동안 신뢰해 온 서버를 모든 기기가 거부하게 되며, 유일한 복구책은 전부 손으로 재등록하는 것뿐입니다. 단계 06에서 이 복사본을 진짜 백업으로 만듭니다.
headscale 명령어는 컨테이너 안에 있으므로, 관리 작업은 모두 docker exec로 이루어집니다. 먼저 사용자부터 만드십시오. 사용자는 기기의 소유자이자 접근 규칙이 가리키는 단위이므로, 기계 이름이 아니라 사람이나 역할의 이름을 붙이십시오.
hs() { docker exec -it headscale headscale "$@"; }
hs users create alice
hs users list # note the numeric id — newer releases want it
# A single-use key that expires in an hour is the right default.
hs preauthkeys create --user 1 --expiration 1h
그런 다음 기기에서 공식 클라이언트를 띄우되, 기본 서버 대신 당신의 서버를 가리키게 하십시오. 이것은 편법이 아니라 정식으로 지원되는 플래그입니다. 클라이언트는 오픈소스이고 로그인 서버는 그저 매개변수 하나일 뿐입니다.
# On the device (Debian/Ubuntu shown; every platform has an equivalent):
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up \
--login-server https://vpn.example.com \
--auth-key <the-key-you-just-created>
tailscale status
tailscale ip -4
사전 인증 키가 없으면 클라이언트는 대신 URL 하나를 출력합니다. 그 주소를 열면 서버에서 실행할 등록 명령이 뜨는데, 이것이 대화형 경로이고 지금 눈앞에 있는 기기를 등록할 때 편리합니다. 어느 쪽이든, 축하하기 전에 두 번째 기기부터 하십시오. 기기 하나짜리 메시는 아무것도 증명하지 못합니다. 테일넷 주소로 핑을 보낸 다음, 상태 출력을 꼼꼼히 읽으십시오.
hs nodes list
# On a client, the word after the peer name is the one that matters:
tailscale status
# 100.64.0.2 laptop alice linux active; direct 203.0.113.9:41641
# ^^^^^^ direct, not relay
tailscale netcheck # why it chose what it chose
다이렉트는 두 기기가 서로를 찾아내 트래픽이 피어투피어로 오간다는 뜻입니다. 릴레이는 DERP 서버를 거쳐간다는 뜻으로, 그 자체로 문제없이 작동하지만 지연이 늘고 경로에 제3의 기계가 끼어듭니다. 그리고 기본값에서는 그 기계가 당신 것이 아닙니다. 단계 06은 바로 이것을 당신 것으로 바꾸는 이야기입니다.
데스크톱은 명령줄 플래그 하나로 끝납니다. Android는 설정 화면의 버전 번호 뒤에서 커스텀 서버 항목을 노출합니다. Apple 플랫폼에는 앱 안에 입력란이 아예 없습니다. 로그인하기 전에 컨트롤 서버 URL을 지정하는 작은 구성 프로필을 설치해야 하는데, 한 번은 번거롭지만 그 뒤로는 그대로 유지됩니다. 시간을 아껴 줄 것이 두 가지 있습니다. 추측하지 말고 Headscale 문서의 클라이언트별 페이지를 확인하십시오. Apple 쪽 흐름은 한 번 이상 바뀐 적이 있습니다. 그리고 먼저 Tailscale 자체 서비스에서 앱을 로그아웃하십시오. 그곳에 이미 세션을 쥐고 있는 앱은 로그아웃하거나 재설치하기 전까지 다른 서버를 잘 받아들이지 않는 경우가 많습니다.
지금 바로 들일 만한 습관이 두 가지 있습니다. 사람에게는 한 번 쓰고 마는 단기 사전 인증 키를 쓰고, 재사용 가능하거나 에페메럴한 키는 정말로 그것이 필요한 자동화에만 쓰십시오. 에페메럴 키는 오프라인이 되면 스스로 사라지는 기기를 등록하는데, 이는 CI 러너에는 정확히 맞고 노트북에는 정확히 틀린 선택입니다. 그리고 키 만료에 대한 입장을 지금 정해 두십시오. 등록된 기기에는 만료일이 붙고, 키가 만료된 기기는 누군가 다시 인증해 주기 전까지 닿을 수 없게 됩니다. 이것은 노트북에서는 보안 기능이지만, 새벽 3시의 헤드리스 서버에서는 장애 발생기입니다. 당신의 버전이 만료를 어떻게 다루는지 확인하고 — 서브커맨드가 옮겨 다녔습니다 — 기본값을 어렵게 겪으며 알아내는 대신 기기마다 의도를 갖고 직접 정하십시오.
모든 기기가 서로에게 닿는 테일넷은 그 자체로 이미 쓸모가 있습니다. 여기에 세 가지를 더하면 단순한 터널 묶음이 아니라 하나의 네트워크가 되는데, 이 셋은 사람들이 흔히 걸려 넘어지는 성질을 공유합니다. 클라이언트가 기능을 광고하면, 서버가 그것을 승인해야 한다는 점입니다. 광고만 되고 승인되지 않은 라우트는 조용히, 그냥 존재하지 않는 것이 됩니다. 증상은 라우터까지는 핑이 가지만 그 뒤에 있는 것에는 닿지 않는다는 것입니다.
서브넷 라우터 — LAN 전체를 테일넷에 올립니다. 가정이나 사무실 네트워크에서 기계 한 대가 그 네트워크의 대역을 광고하면, 다른 모든 기기는 별도 설치 없이도 그 뒤에 있는 프린터, NAS, 관리 인터페이스에 닿게 됩니다. 커널의 포워딩부터 켜 두십시오. 그렇지 않으면 패킷은 오류 메시지 하나 없이 라우터에서 멈춰 버립니다.
# On the machine that sits inside the LAN:
echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-ts.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-ts.conf
sudo sysctl -p /etc/sysctl.d/99-ts.conf
sudo tailscale up --login-server https://vpn.example.com \
--advertise-routes=192.168.1.0/24 --accept-routes
# On the control server — approve it, or it does not exist.
# The subcommand moved between releases; check `hs nodes --help` first.
hs nodes list
hs nodes approve-routes --identifier 3 --routes 192.168.1.0/24
엑시트 노드 — 모든 트래픽을 VPS 밖으로 내보냅니다. 같은 메커니즘을 더 넓은 경로에 적용한 것입니다. 스스로를 엑시트 노드로 광고하는 기기는 다른 기기의 인터넷 연결 전체를 실어 나르겠다고 나섭니다. 신뢰할 수 없는 카페나 호텔 네트워크에서는 정확히 이것이 필요하며, 이 가이드에서 개인용 VPN에 가장 가까운 것이 바로 이것입니다.
# On the VPS that will do the exiting (forwarding must be on, as above):
sudo tailscale up --login-server https://vpn.example.com --advertise-exit-node
# Approve it on the control server, then on the laptop:
tailscale set --exit-node=100.64.0.1 --exit-node-allow-lan-access
tailscale set --exit-node= # turn it off again
엑시트 노드는 상용 VPN이 아니며, 스스로에게 그런 것처럼 팔아서는 안 됩니다. 그 주소는 오직 당신 것이고 고정되어 있어서, 카페 와이파이를 상대할 때는 탁월하지만 여러 사이트에 걸친 장기 상관분석 앞에서는 무력합니다. 이 트레이드오프는 필라 가이드에서 자세히 다룹니다. 엑시트 노드를 두 번째의 더 저렴한 VPS에서 돌려, 컨트롤 서버는 포워딩 트래픽에서 완전히 자유롭게 둘 수도 있다는 점도 기억해 두십시오.
접근 규칙 — 두 번째 사람이 합류하기 전에 써 두십시오. 기본 정책은 모두 허용이며, 이는 혼자 쓰는 테일넷에는 맞지만 다른 사람이 들어오는 순간 틀린 선택이 됩니다. 정책은 작은 HuJSON 문서입니다. 사용자 그룹, 기계에 붙이는 태그, 그리고 어떤 출발지가 어떤 포트로 어떤 목적지에 닿을 수 있는지 정하는 규칙 목록으로 이루어집니다. 설정에서 이 파일을 가리키게 하고, 파일은 버전 관리에 두고, 수정할 때마다 리로드하십시오.
{
"groups": {
"group:admin": ["alice"],
"group:contractor":["bob"]
},
"tagOwners": {
"tag:server": ["group:admin"],
"tag:exit": ["group:admin"]
},
"acls": [
// Admins reach everything.
{ "action": "accept", "src": ["group:admin"], "dst": ["*:*"] },
// The contractor reaches one machine, on two ports, and nothing else.
{ "action": "accept", "src": ["group:contractor"],
"dst": ["tag:server:22,443"] }
],
"autoApprovers": {
"routes": { "192.168.1.0/24": ["tag:server"] },
"exitNode": ["tag:exit"]
}
}
이 파일에 대해 짚어 둘 것이 두 가지 있습니다. 정책 언어는 Tailscale의 것이고, Headscale은 그 전부가 아니라 크고 계속 늘어나는 일부만 구현합니다. 특이한 규칙에 기대기 전에 당신 버전의 문서를 확인하십시오. 그리고 정책이 어디에 사는지도 옮겨 다녔습니다. 예전 릴리스는 최상위 설정에서 경로를 지정했고, 최신 릴리스는 규칙을 데이터베이스에 둘 수도 있는 모드와 함께 정책 섹션 아래로 묶었습니다. 둘 다 문서화되어 있으니, 중요한 것은 당신이 고정해 둔 버전이 실제로 무엇을 쓰느냐입니다.
MagicDNS — 짧은 이름, 그리고 어디서나 나만의 리졸버. MagicDNS를 켜면, 모든 기기가 단계 02에서 고른 접미사 안의 짧은 이름으로 응답하게 되어, hosts 파일 없이도 어디서나 ssh nas가 작동합니다. 흥미로운 것은 뒤쪽 절반입니다. 컨트롤 서버는 모든 클라이언트에 네임서버 목록도 함께 밀어 주는데, 이는 프라이빗 DNS 리졸버 가이드에서 만든 리졸버가, 인터넷에서는 전혀 닿을 수 없는 테일넷 주소로만 리스닝하면서도 모바일 데이터로 접속한 휴대폰까지 필터링·캐싱하고 아무것도 로깅하지 않은 채 서빙할 수 있다는 뜻입니다.
dns:
magic_dns: true
base_domain: ts.example.com
override_local_dns: true # beat whatever DHCP handed the device
nameservers:
global:
- 100.64.0.5 # your resolver, on the tailnet
쓰고 있다는 사실조차 잊은 릴레이. 두 기기가 다이렉트 연결을 열 수 없을 때 — 양쪽 다 대칭형 NAT이거나, UDP를 먹어 버리는 방화벽이거나, 통신사가 뭔가 창의적인 짓을 할 때 — 트래픽은 DERP 릴레이로 폴백됩니다. 종단간 암호화는 그대로 유지되므로 릴레이는 암호문만 실어 나릅니다. 릴레이가 보는 것은 누가 누구와 대화 중인지, 얼마나, 언제인지입니다. 기본 설정 그대로라면 Headscale은 당신 클라이언트에 공개 릴레이 맵을 넘겨주는데, 그 릴레이들은 당신이 떠나온 바로 그 회사가 운영합니다. Headscale에는 내장 릴레이가 딸려 있어, 켜기만 하면 이 폴백을 나머지 모든 것과 같은 노르딕 데이터센터 안에 둘 수 있습니다.
derp:
server:
enabled: true
region_id: 999 # custom regions live in the 900s
region_code: "nb-hel"
region_name: "Helsinki (self-hosted)"
stun_listen_addr: "0.0.0.0:3478"
urls:
- https://controlplane.tailscale.com/derpmap/default # remove for yours only
auto_update_enabled: true
update_frequency: 24h
공개 지도를 당신 지역 것과 나란히 두는 편이 현실적인 기본값입니다. 클라이언트는 가장 가까운 릴레이를 선호하므로, 같은 반구에 있는 기기는 당신의 릴레이를 쓰고, 지구 반대편 기기는 노르딕 홉 하나에 발이 묶이지 않습니다. 공개 URL을 없애면 테일넷이 완전히 자기 완결적으로 닫히는 대신, 먼 곳의 기기는 폴백이 나빠집니다. 둘 다 나름의 근거가 있으니, 그냥 넘기지 말고 알면서 고르십시오.
파일 네 개, 오프사이트에, 암호화해서. 이 백업은 없으면 민망할 정도로 작습니다. 데이터베이스, 개인 키 자료, 설정 파일, 정책 파일뿐입니다. 다 합쳐도 어떤 스토리지 청구서에서든 반올림 오차 수준이고, 키 자료가 없으면 네트워크의 모든 기기가 서버를 거부하게 됩니다. 이 서버가 아닌 다른 곳으로, 가능하다면 다른 관할권으로 보내십시오. 백업 가이드가 바로 이것을 다룹니다. 그리고 필요해지기 전에 한 번은 일부러 스크래치 VPS에 복원해 보십시오.
# Stop briefly for a consistent SQLite copy, or use .backup on a live db.
docker compose stop headscale
tar czf /tmp/headscale-$(date +%F).tar.gz -C /srv/headscale config data
docker compose start headscale
restic -r sftp:backup@backup.example.com:/srv/restic backup \
/tmp/headscale-$(date +%F).tar.gz
진짜 위험은 업그레이드입니다. 이것은 1.0 이전 소프트웨어이고, 딱 그렇게 행동합니다. 설정 키 이름이 바뀌었고, CLI 서브커맨드가 옮겨 다녔으며, 어느 릴리스는 재시작이 아니라 문서화된 마이그레이션을 한 번 이상 요구했습니다. 그렇다고 피할 이유는 되지 않습니다. 오히려 루틴을 갖춰야 할 이유입니다. 이미지를 내려받기 전에 릴리스 노트를 읽으십시오. 위에서 말한 아카이브부터 먼저 만들어 두십시오. 설정 파일을 새 버전에 딸려 오는 예시와 비교하십시오. 이름이 바뀐 키는 바로 거기서 스스로 드러납니다. 마이너 버전을 세 개씩이 아니라 하나씩 올리십시오. 그리고 예전 이미지 태그를 적어 두십시오. 30초 만에 되돌릴 수 있다는 사실이야말로 이 모든 일을 부담 없게 만들어 줍니다.
그런 다음 매달 알림을 하나 맞춰 두 가지를 살펴보십시오. 등록된 기기 목록을 보고 6개월 전에 폐기한 기계가 더 이상 구성원으로 남아 있지 않게 하고, 만료일을 보고 아무것도 하필 나쁜 순간에 조용히 떨어져 나가지 않게 하십시오. 메시 VPN은 조용히 실패하는 몇 안 되는 서비스 중 하나입니다. 조용히 만료된 그 기기가 정말 필요해지는 그 한 번의 순간까지는, 모든 것이 계속 잘 돌아가는 것처럼 보입니다.
컨트롤 플레인을 셀프 호스팅하는 것은 실질적이고 구체적인 개선이지, 마술처럼 문제를 사라지게 하는 요술이 아닙니다. 위의 모든 것을 다 해내고 나서도 변하지 않는 사실이 다섯 가지 있고, 이것을 아는 것이야말로 진짜 방어와 그저 위안이 되는 이야기의 차이를 가릅니다.
컨트롤 서버는 이름을 가진 공개 엔드포인트입니다. 모든 기기가 이 주소로 접속하므로 호스트네임이 하나 필요하고, 그 호스트네임은 인증서가 발급되는 순간 인증서 투명성 로그에 실립니다. 어디에도 링크한 적 없어도 찾아낼 수 있으며, 그 주소는 당신의 모든 기기가 공유하는 유일한 상수입니다. 무엇인지 설명하는 이름 대신, 이미 공개해 둔 도메인의 서브도메인을 쓰십시오.
Headscale은 재구현체이고, 스스로도 그렇다고 밝힙니다. Tailscale이 만들거나 지원하는 제품이 아니며, 기능 동등성은 구조적으로 일부에 그치고, 호스팅형 제품이 파는 것 중 일부 — 웹 콘솔, 공개 릴레이 네트워크, 잘 다듬어진 싱글 사인온 경로 — 는 여기 아예 없거나, 누구도 감사하지 않은 서드파티 프로젝트로 대신해야 합니다. 어떤 기능을 두고 계획을 세우기 전에, 그것이 실제로 존재하는지 프로젝트 문서에서 확인하고, 업그레이드는 백그라운드에서 저절로 되는 일이 아니라 하나의 작업으로 다루십시오.
기본 릴레이는 여전히 남의 것입니다. 내장 DERP 서버를 켜기 전까지는, 다이렉트로 붙을 수 없는 연결이 공개 릴레이 네트워크로 폴백됩니다. 페이로드는 여전히 종단간 암호화되어 있어 아무도 읽을 수 없지만, 당신의 두 기기가 대화를 나누고 있다는 사실과 그 양은 당신 소유가 아닌 인프라를 거쳐 갑니다. 대부분의 사람이 첫날 오후에 놓치는 신뢰 사슬의 그 부분이 바로 이것입니다.
엑시트 노드는 안정적인 주소이지, 군중이 아닙니다. 트래픽을 당신 VPS로 돌리면 카페 네트워크와 ISP의 프로파일링은 무력화됩니다. 하지만 상관분석은 무력화하지 못합니다. 그 주소는 오직 당신 것이고 바뀌지 않기 때문입니다. 상용 VPN의 공유 주소는 바로 이 위협 하나를 제외한 모든 면에서 더 나쁩니다.
그 기계는 여전히 어느 건물 안에 있습니다. 임대 서버도 문과 직원이 있는 데이터센터의 실제 하드웨어 위에서 돌아갑니다. 달라지는 것은, 당신 기기들을 모아 놓은 제품 차원의 색인이 없고, 각 기기가 어디서 접속하는지 기록하는 회사 데이터베이스가 없으며, 다음 분기에 슬쩍 바뀔 수 있는 개인정보 정책도 없다는 점입니다. 달라지지 않는 것은, 디스크가 물리적으로 존재하며 그것이 당신 집 안에 있지는 않다는 점입니다.
정확히 무엇을 산 것인지 말하자면: 당신이 가진 모든 기계의 목록, 각 기기가 어디서 접속하는지에 대한 기록, 그리고 새 기기를 추가할 권한까지, 이 모두가 특정한 이름 없이 빌린 서버 위에 놓이게 됩니다. 좁지만 진짜인 주장입니다. 이것이 값어치를 하는 것은 오직 단계 06까지 마쳤을 때뿐입니다. 검증된 복원 절차가 없는 셀프 호스팅 컨트롤 플레인은 프라이버시의 개선이 아니라, 브랜딩만 그럴듯한 단일 장애점일 뿐입니다.
| 티어 | 월별 | 리소스 | 여유 있게 돌아가는 것 |
|---|---|---|---|
| Sentinel | $3.90 | 2 vCPU · 4 GB · 120 GB · 1 Gbps | 컨트롤 서버, 릴레이, 가벼운 엑시트 노드 사용. 딱 맞는 시작점입니다. |
| 두 번째 Sentinel | $3.90 | 엑시트 노드는, 별도의 서버에서 | 포워딩 트래픽을 컨트롤 플레인에서 떼어 놓습니다 |
| Ravelin | $16.90 | 8 vCPU · 16 GB · 480 GB · 2.5 Gbps | 전 가족이 매일 쓰는 엑시트 노드를, 회선 속도 그대로 |
| Bulwark | $32.90 | 16 vCPU · 32 GB · 960 GB · 5 Gbps | 회사의 엑시트 노드와 붐비는 릴레이, 한 곳에 |
이 표의 어떤 항목도 코디네이션 자체를 기준으로 사이징하지 않았습니다. 컨트롤 서버는 기기당 유휴 연결 하나와 킬로바이트 단위로 잴 수 있는 데이터베이스만 쥐고 있어서, 기기 수백 대라도 가장 작은 티어를 괴롭히지 못합니다. 서버를 소모시키는 것은 포워딩 트래픽입니다. 릴레이를 거치는 연결, 그리고 그보다 훨씬 크게는 누군가의 인터넷 연결 전체를 실어 나르는 엑시트 노드입니다. 업링크와 암호화 처리량을 기준으로 사이징하고, 모든 티어에서 대역폭이 무제한이라는 점을 기억하십시오. 남는 질문은 그저 얼마나 빠르길 원하느냐뿐입니다.
MagicDNS 접미사를 컨트롤 서버 자체 호스트네임의 상위나 하위로 고르면 이름 해석이 깨지는데, 그 모습이 꼭 DNS 장애처럼 보입니다. 둘을 형제 관계로 유지하십시오. 서버는 vpn.example.com, 테일넷은 ts.example.com처럼요.
클라이언트는 192.168.1.0/24를 광고하고 있다고 태연히 알려 주지만, 서버가 한 번도 승인한 적이 없어 라우터 뒤에 있는 것은 아무것도 응답하지 않습니다. 어디에도 오류는 없습니다. 서버에서 승인한 다음, 클라이언트가 아니라 라우트 목록을 확인하십시오.
아웃바운드 UDP를 막는 네트워크는 모든 연결을 릴레이로 밀어 넣습니다. 작동은 하지만 느려지고, 경로에 제3의 기계가 끼어듭니다. tailscale status에는 다이렉트 대신 릴레이라고 뜨고, netcheck를 보면 이유를 알 수 있습니다. 방화벽을 고치거나, 이 홉을 알면서 받아들이십시오.
갓 만든 테일넷은 모든 기기가 모든 포트로 서로에게 닿게 해 줍니다. 혼자 쓸 때는 그것으로 맞지만, 계약직 직원의 노트북이 합류하는 순간 틀린 선택이 됩니다. 존재하는 줄도 몰랐던 관리 인터페이스까지 포함해서 말입니다. 초대장을 보내기 전에 정책부터 써 두십시오.
서버를 다시 만들고, 데이터베이스를 복원하고, 키 자료만 빠뜨리면 — 어제까지 신뢰하던 서버를 모든 기기가 거부하게 됩니다. 영리한 복구법 같은 건 없습니다. 있는 것은 휴대폰을 손으로 하나하나 재등록하며 보내는 하루뿐입니다. 첫 기기가 합류하기 전에 데이터 디렉터리부터 복사해 두십시오.
설정 키와 CLI 서브커맨드는 마이너 릴리스를 거치며 이름이 바뀌어 왔습니다. :latest를 쓰면, 아무 상관 없는 재시작 한 번에 새 버전이 딸려 오고 전체 기기의 컨트롤 플레인이 멈춰 버립니다. 태그를 고정하고, 릴리스 노트를 읽고, 의도를 갖고 업그레이드하십시오.
이것을 구축하는 동안 떠오르는 열한 가지 질문 — 애초에 구축해야 할지를 결정짓는 두 가지부터 시작합니다.
아니요, 그리고 그것이 무슨 뜻인지는 정확히 짚어 둘 가치가 있습니다. Headscale은 코디네이션 서버 — 공개 키와 엔드포인트, 정책을 배포하는 Tailscale의 그 부분 — 을 독립적으로 오픈소스 재구현한 것입니다. Tailscale이 만들거나 팔거나 지원하지 않고, 상용 지원 계약도 없으며, 문제가 생기면 티켓을 여는 대신 이슈 트래커를 읽게 됩니다. Headscale이 하는 일은, 공식 오픈소스 Tailscale 클라이언트가 이미 쓰고 있는 프로토콜을 그대로 구사하는 것이며, 그래서 클라이언트를 손대지 않고도 그대로 작동합니다. 여기서 실질적인 결과가 두 가지 따라옵니다. 기능 동등성은 부분적이고 앞으로도 계속 그럴 것입니다. 이 프로젝트가 훨씬 큰 팀이 유지보수하는, 계속 움직이는 목표를 뒤쫓고 있기 때문입니다 — 어떤 기능을 두고 설계하기 전에 해당 버전의 프로젝트 문서를 확인하십시오. 그리고 업그레이드는 보통의 컨테이너 버전업보다 더 신경 써야 합니다. 설정 스키마와 여러 CLI 서브커맨드가 마이너 릴리스를 거치며 이름이 바뀌어 왔고, 이것이 멀쩡히 돌던 설치를 사람들이 망가뜨리는 가장 흔한 방식입니다.
아니요 — 그리고 이 부분이야말로 그냥 믿기보다는 이해할 가치가 있는 구조입니다. 모든 기기는 자기 WireGuard 키 쌍을 로컬에서 생성하고, 개인 키는 결코 그 기기를 벗어나지 않습니다. 기기가 컨트롤 서버에 올리는 것은 공개 키와 현재 엔드포인트, 약간의 메타데이터뿐이며, 내려받는 것도 닿을 수 있는 다른 모든 기기에 대해 똑같은 종류의 정보입니다. 터널은 그 뒤 기기 사이에서 직접 협상되고, 암호화는 서버가 한 번도 쥔 적 없는 두 키 사이의 평범한 WireGuard일 뿐입니다. 그래서 컨트롤 서버는 아무것도 복호화할 수 없고, 이는 Tailscale의 서버도 마찬가지입니다. 컨트롤 서버가 할 수 있는 일은 다른 데 있고, 그쪽은 덜 이야기됩니다. 바로 지도를 쥐는 것입니다. 기기 이름과 사용자 이름, 공개 키, 각 기기가 지금 접속 중인 공개 IP, 어느 기기가 새벽 3시에 깨어났는지, 그리고 누가 누구에게 닿을 수 있는지를 정하는 정책까지 말입니다. 또한 키를 배포하는 주체이기도 해서, 침해당하거나 강요당한 컨트롤 서버는 당신의 네트워크에 기기를 하나 추가할 수도 있습니다. 셀프 호스팅은 수학 문제를 해결하지 않습니다. 다만 행정적인 문제 하나를 당신이 소유한 서버 위로 옮겨 놓을 뿐입니다.
사람들이 예상하는 것보다 훨씬 적습니다. 이 설계에서 안심이 되는 부분입니다. 기기는 마지막으로 받은 네트워크 지도를 캐시해 두고, 이미 맺어진 WireGuard 터널은 피어투피어라서 컨트롤 서버를 거치지 않고, 계속 작동하는 데 그것을 필요로 하지도 않습니다. 평범한 근무일 중 VPS가 재부팅돼도 대개 눈에 띄지 않습니다. 멈추는 것은 변화입니다. 새 기기는 등록할 수 없고, 새 네트워크로 옮긴 기기는 피어를 다시 찾는 데 시간이 더 걸릴 수 있으며, 정책 수정 사항은 퍼지지 않고, 서버가 죽어 있는 동안 키가 만료된 노드는 갱신할 방법이 없습니다. 그러니 컨트롤 서버는 데이터 경로가 아니라 컨트롤 플레인으로 생각하는 것이 맞습니다. 장애는 불편함이지 단절이 아닙니다. 그렇다 해도 이것을 프로덕션으로 다루십시오. 백업해 두고(단계 06), 부서 전체가 테일넷에 의존한다면 자주 재시작되는 다른 무언가와 한 서버를 나눠 쓰지 말고 컨트롤 서버만을 위한 작은 서버를 따로 두십시오.
기기 수를 세고, 그중 몇 대가 이동하는지 세십시오. 작고 고정된 구성이라면 순정 WireGuard가 훌륭한 답입니다. 서너 개의 피어, 고정된 엔드포인트, 각자 설정 파일 하나씩, 커널 모듈 말고는 돌릴 것도 없습니다. 이 가이드에 나오는 무엇보다도 소프트웨어가 적고, 공격 표면이 작고, 유지보수가 덜 필요합니다. 저희 WireGuard 가이드에서 처음부터 끝까지 다룹니다. 대가는, WireGuard가 일부러 키 배포 기능을 두지 않았다는 점입니다. 그래서 기기가 하나 늘 때마다 다른 모든 기기를 손봐야 하는데, 이 작업량은 보유 기기 수의 제곱으로 늘어납니다. 게다가 호텔 와이파이와 모바일 데이터를 오가는 로밍 클라이언트는, 자신이 옮겨간 곳을 계속 모두에게 알려 줄 누군가가 필요합니다. 컨트롤 플레인이 사 주는 것이 바로 그것입니다. 실무에서 갈림길은 대략 기기 다섯 대 즈음, 혹은 매일 네트워크를 바꾸는 첫 번째 노트북이 생기는 시점, 혹은 "계약직 노트북은 스테이징 서버에만 닿고 그 외에는 아무 데도 못 닿게" 같은 규칙을 처음 원하는 시점입니다. 그 아래라면 WireGuard를 쓰고 조용함을 누리십시오.
예 — 완전히 같은 공식 클라이언트이고, 다른 컨트롤 서버를 가리키게 하는 것은 편법이 아니라 정식으로 지원되는 동작입니다. 다만 플랫폼마다 세부 방식이 달라서, 바로 그 지점에서 저녁 시간을 통째로 날리기 쉽습니다. Linux, macOS, Windows에서는 클라이언트의 명령줄 플래그(--login-server)나 이에 해당하는 설정을 쓰면 됩니다. Android에서는 설정 화면의 버전 번호 뒤에 이 옵션이 앱 자체에 노출되어 있습니다. iOS, iPadOS, tvOS에는 앱 안에 입력란이 아예 없습니다. 로그인하기 전에 컨트롤 서버 URL을 지정하는 작은 구성 프로필을 설치해야 하는데, 처음 한 번은 번거롭지만 그 뒤로는 그대로 유지됩니다. Headscale 문서는 바로 이것을 위해 플랫폼별 페이지를 두고 있으니, 짐작하지 말고 열어 보는 편이 낫습니다. 특히 Apple 쪽 흐름은 한 번 이상 바뀐 적이 있습니다. 플랫폼을 가리지 않는 주의사항이 하나 있습니다. 이미 Tailscale 자체 서비스에 로그인되어 있던 앱은, 다른 서버를 받아들이기 전에 대개 로그아웃이 필요하고 때로는 재설치까지 필요합니다.
네트워크를 작동시키기 위해서는 아닙니다. 다만 대체로 프라이빗한 것과 한결같이 프라이빗한 것의 차이이니, 알면서 정하십시오. DERP 서버는 두 기기가 다이렉트 연결을 열 수 없을 때 쓰이는 릴레이입니다. 양쪽 다 대칭형 NAT이거나, UDP를 먹어 버리는 회사 방화벽이거나, 통신사가 뭔가 창의적인 짓을 할 때입니다. 릴레이를 거친 트래픽도 여전히 종단간 암호화된 WireGuard이므로, 릴레이 운영자가 보는 것은 내용이 아니라 암호문입니다. 그들이 보는 것은 메타데이터입니다. 어느 두 기기가 대화 중인지, 얼마나, 언제. 기본 설정 그대로라면 Headscale은 당신 기기에 Tailscale의 공개 DERP 맵을 넘겨주는데, 이는 그 릴레이들이 Tailscale의 것이라는 뜻입니다. 컨트롤 플레인은 셀프 호스팅했지만, 폴백 데이터 경로는 떠나온 그 회사에 그대로 남겨 둔 셈입니다. Headscale에는 내장 DERP 서버가 딸려 있어, 켜기만 하면 컨트롤 서버와 같은 노르딕 데이터센터에 릴레이가 생깁니다. 정직하게 말하면 트레이드오프는 커버리지입니다. Tailscale은 여러 대륙에서 릴레이를 운영하지만 당신은 하나만 운영하므로, 지구 반대편의 기기는 그쪽 릴레이를 쓰는 편이 낫습니다. 둘 다 켜 두고 당신 것도 지도에 넣는 것이 꽤 합리적인 절충안입니다.
예, 그리고 이것이야말로 직접 구축할 만한 더 나은 이유 중 하나입니다. Headscale은 합류하는 모든 기기에 DNS 설정을 밀어 줄 수 있어서, 카페 와이파이의 노트북도 모바일 데이터의 휴대폰도 당신이 운영하는 리졸버를 통해 이름을 해석하게 됩니다. 필터링하고, 캐싱하고, 로컬 네트워크가 쿼리를 보지 못하게 하면서 말입니다. 테일넷의 전역 네임서버를, 직접 운영하는 Unbound나 AdGuard Home이 돌아가는 서버의 테일넷 IP로 지정하고, DHCP가 기기에 뭘 넘겨주든 그것을 이기도록 오버라이드를 설정하십시오. 제대로 맞춰야 할 것이 두 가지 있습니다. 리졸버는 공개 인터넷이 아니라 테일넷 안에서만 닿을 수 있어야 합니다. 그것이 핵심이고, 이는 곧 포트 53을 세상에 대해 마침내 닫을 수 있다는 뜻이기도 합니다. 그리고 모든 기기에 당신이 고른 접미사 안의 짧은 이름을 붙여 주는 MagicDNS는, 그 접미사가 당신이 관리하는 것이면서 컨트롤 서버 자체 호스트네임의 상위나 하위가 아니어야 합니다. 이것을 잘못 맞추면 DNS 버그처럼 보이지만 실제로는 설정 버그인, 헷갈리는 실패로 이어집니다.
컨트롤 플레인만 놓고 보면 작습니다. 그것도 아주 작습니다. Headscale은 기기, 키, 정책의 작은 데이터베이스를 담은 Go 바이너리 하나이고, 코디네이션 트래픽은 기기당 하나씩 걸리는 롱폴링 연결이 이룬 잔물결일 뿐이라 대부분의 시간에는 거의 아무것도 실어 나르지 않습니다. Sentinel — 2 vCPU, 4 GB, 120 GB NVMe, 월 $3.90 — 이면 가정이든 소규모 회사든, 심지어 기기 수백 대라도 눈치채지 못한 채 돌아갑니다. 사이징이 흥미로워지는 건 같은 서버가 두 번째 일까지 맡을 때뿐입니다. 내장 DERP 릴레이는 실제 트래픽을 포워딩하므로 코어보다 업링크를 원합니다. 엑시트 노드는 기기의 트래픽을 전부 포워딩하므로 업링크와 넉넉한 대역폭 허용치, 그리고 회선 속도의 암호화를 따라갈 CPU를 원합니다. 둘 다 계획한다면 트래픽 기준으로 사이징하고 컨트롤 서버는 있으나 마나 한 반올림 오차쯤으로 여기십시오. 아니면 더 낫게, 엑시트 노드를 두 번째 서버로 옮기고 컨트롤 플레인은 계속 지루하게 두십시오.
기술적으로는 비슷하지만 프라이버시 관점에서는 거의 정반대이니, 눈을 똑바로 뜨고 볼 가치가 있습니다. 엑시트 노드는 기기의 인터넷 연결 전체를 당신의 VPS로 돌립니다. 그래서 웹사이트는 당신 집이나 호텔 주소 대신 VPS 주소를 보고, 로컬 네트워크는 당신의 브라우징 대신 암호화된 터널 하나만 보게 됩니다. 여기까지는 상용 VPN과 다르지 않습니다. 다른 것은 군중입니다. 상용 VPN의 주소는 서로 무관한 고객 수천 명이 함께 씁니다. 바로 그 때문에 트래픽을 당신에게 귀속시키기가 어려워집니다. 반면 엑시트 노드의 주소는 오직 당신이, 안정적으로, 영원히 씁니다. 훔쳐보는 카페 네트워크나 프로파일을 쌓는 ISP를 상대할 때 엑시트 노드는 탁월합니다. 하지만 여러 사이트에 걸쳐 몇 달에 걸친 활동을 상관분석하는 관찰자를 상대할 때는, 전용 주소야말로 끈질긴 식별자가 되고 공유 주소는 그렇지 않습니다. 엑시트 노드는 그것이 잘하는 일에 쓰십시오. 신뢰할 수 없는 로컬 네트워크, 자신의 서비스에 접속하기, 고정 주소를 기대하는 것들을 위한 안정적인 주소 말입니다. 그리고 이것이 다루지 못하는 위협 모델 부분은 필라 가이드에서 읽으십시오.
네 가지가 있고, 그중 하나만 뻔합니다. 데이터베이스 — 기본은 SQLite, 옮겼다면 PostgreSQL — 는 사용자, 기기, 공개 키, 라우트, 사전 인증 키를 담고 있습니다. 첫 부팅 때 생성되는 개인 키 자료는 사람들이 흔히 잊는 부분입니다. 잃어버리면 1년 동안 대화해 온 서버를 모든 기기가 거부하게 되고, 결국 전체 기기를 손으로 재등록해야 합니다. 설정 파일이 중요한 이유는 단계 02에서 정한 세 가지 이름을 그대로 담고 있기 때문이며, 그것을 기억에 의존해 재구성하다 보면 하나쯤 자기도 모르게 바꿔 버리기 십상입니다. 그리고 접근 규칙을 디스크에 두고 있다면, 정책 파일이야말로 가장 다시 쓰고 싶지 않은 파일입니다. 이 네 가지는 디렉터리 두 곳에 들어 있고, 다 합쳐도 사진 한 장보다 가볍습니다. 암호화한 뒤 이 서버가 아닌 다른 기계로, 가능하면 다른 관할권으로 보내십시오 — 백업 가이드가 바로 이럴 때 쓰라고 있는 것입니다 — 그리고 한 번은 일부러 스크래치 VPS에 복원해서, 지금 가진 파일이 정말 필요한 파일인지 확인하십시오.
컨트롤 서버는 평범하고 얌전한 워크로드입니다. HTTPS 트래픽이 조금, UDP 트래픽이 조금, 그 외에는 사실상 트래픽이랄 것이 없습니다. 그래서 이용 약관 어디에도 이것을 문제 삼을 조항이 없습니다. 엑시트 노드는 정책 설명이 아니라 한 문장으로 충분합니다. 기기가 그곳을 거쳐 보내는 모든 것은 당신의 VPS 주소로, 당신 계정 아래에서 인터넷에 나갑니다. 그러니 당신 기기들이 무엇을 하든 그 주소가 그것으로 알려지게 됩니다. 그건 문제가 아니며 정확히 이 기능이 존재하는 이유입니다. 다만 그렇기 때문에 모르는 사람과 엑시트 노드를 나눠 쓰는 것은, 오픈 릴레이를 운영하는 것과 마찬가지로 좋지 않은 생각입니다. 모든 티어에서 대역폭은 무제한이고 릴레이를 거친 트래픽도 평범한 트래픽이므로, DERP 서버와 엑시트 노드는 종량제가 아니라 기본 포함됩니다. 그 밖에도: 계정에 실명이 필요 없고, 청구서는 모네로나 비트코인으로 정산되며, 당신이 소유한 모든 기기의 지도를 쥔 이 기계는 누구의 색인에도 잡히지 않습니다.
Sentinel — 헬싱키, 스톡홀름, 오슬로, 레이캬비크에서 2 vCPU와 4 GB를 월 $3.90에. Headscale, Caddy, 나만의 DERP 릴레이를 돌리기에 충분하고, 같은 서버에서 엑시트 노드까지 원한다면 그럴 여유도 있습니다. 가입 시 이메일도, 신분증도 필요 없고, 당신이 가진 모든 기기의 목록을 쥔 회사 데이터베이스도 없습니다.
마지막 검토일 · 2026-10-12 · 출처 · Headscale 문서(설정 레퍼런스, 클라이언트 설정, ACL, DERP, 원격 CLI), Tailscale 클라이언트 및 엑시트 노드 문서, WireGuard 프로토콜 논문, Caddy 문서 · 주기 · 연간
This guide is one spoke of a larger series. The pillar walks the three privacy layers end to end — the sibling spokes below dive into the specifics.
Three independent layers — signup, payment, network — explained, legal context included, common mistakes flagged.
Your own WireGuard tunnel — server config, peer keys, kill-switch.
A v3 .onion address that survives reboots and IP changes.
Unbound recursing from the root, AdGuard Home filtering, DoH and DoT.
Deploy your own MCP server on a no-KYC VPS — TLS, streamable HTTP, OAuth.
북유럽에서 구축한 프라이빗 호스팅.