Headscale: Tự xây dựng Control Plane cho mạng Mesh VPN Tailscale riêng tư

Network tutorial - IT technology blog
Network tutorial - IT technology blog

Vấn đề với Tailscale mặc định

Tailscale rất tiện — cài vào, đăng nhập, các máy kết nối với nhau ngay mà không cần đụng firewall. Nhưng sau một thời gian dùng cho hệ thống production, mình bắt đầu để ý ra điểm gai mắt: toàn bộ control plane đang nằm trên server của Tailscale Inc. Mọi thông tin về thiết bị, IP, routing policy đều phụ thuộc vào một bên thứ ba mà mình không kiểm soát được.

Cá nhân dùng thì không ảnh hưởng gì. Nhưng với công ty phải tuân thủ data residency — GDPR, PDPA hay internal security policy — thì đây là vấn đề nghiêm túc cần giải quyết, không phải lo lắng thừa. Chưa kể free plan giới hạn 100 thiết bị — nghe nhiều nhưng khi scale lên với IoT hay container thì hết nhanh lắm.

Đó là lý do mình chuyển sang Headscale — implementation mã nguồn mở của Tailscale control plane, tự host trên VPS của mình.

Headscale là gì và nó hoạt động thế nào

Tailscale hoạt động theo mô hình split architecture với hai phần tách biệt hoàn toàn.

  • Control plane: quản lý danh sách thiết bị, phân phối key, xác thực, ACL policy. Mặc định nằm trên controlplane.tailscale.com.
  • Data plane: traffic thực sự đi trực tiếp giữa các peer qua WireGuard (peer-to-peer), control plane không chạm vào data.

Headscale thay thế phần control plane đó. Bạn tự host nó trên server của mình, còn Tailscale client trên các thiết bị dùng y như cũ — chỉ cần trỏ về Headscale server thay vì server của Tailscale.

Cụ thể hơn: traffic thực sự không chạm qua Headscale. Dữ liệu vẫn đi thẳng peer-to-peer qua WireGuard — Headscale chỉ làm broker: phân phối public key, duy trì danh sách node, xử lý auth. Nhẹ đến mức VPS 1 CPU 1GB RAM chạy thoải mái cho vài chục node, CPU thường xuyên dưới 5%.

Cài đặt Headscale trên VPS

Yêu cầu môi trường

  • VPS chạy Ubuntu 22.04 hoặc Debian 12 (mình dùng Ubuntu 22.04)
  • Domain trỏ về IP của VPS (ví dụ: hs.example.com)
  • Port 443 và 80 mở trên firewall
  • Nginx làm reverse proxy (khuyến nghị)

Bước 1: Tải và cài Headscale

# Tải binary mới nhất (kiểm tra version tại GitHub releases)
wget https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb

# Cài đặt
sudo dpkg -i headscale_0.23.0_linux_amd64.deb

# Kiểm tra
headscale version

Bước 2: Cấu hình Headscale

File config nằm tại /etc/headscale/config.yaml. Mở ra và chỉnh các phần quan trọng:

server_url: https://hs.example.com

listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090

grpc_listen_addr: 0.0.0.0:50443
grpc_allow_insecure: false

private_key_path: /var/lib/headscale/private.key
noise:
  private_key_path: /var/lib/headscale/noise_private.key

ip_prefixes:
  - 100.64.0.0/10

derp:
  server:
    enabled: false   # Dùng DERP server của Tailscale (hoặc tự host)
  urls:
    - https://controlplane.tailscale.com/derpmap/default

dns_config:
  nameservers:
    - 1.1.1.1
  domains: []
  magic_dns: true
  base_domain: mesh.example.com

db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite

log:
  level: info

acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: [email protected]
tls_letsencrypt_hostname: ""
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
tls_letsencrypt_challenge_type: HTTP-01

Chú ý kỹ server_url — phải khớp chính xác với domain bạn dùng. Sai chỗ này là client không kết nối được, không có error message rõ ràng, debug mất cả buổi.

Bước 3: Cấu hình Nginx reverse proxy

sudo apt install nginx certbot python3-certbot-nginx -y

# Tạo SSL certificate
sudo certbot --nginx -d hs.example.com

Tạo config Nginx tại /etc/nginx/sites-available/headscale:

server {
    listen 80;
    server_name hs.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name hs.example.com;

    ssl_certificate /etc/letsencrypt/live/hs.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/hs.example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_buffering off;
    }
}
sudo ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Bước 4: Khởi động Headscale

sudo systemctl enable headscale
sudo systemctl start headscale
sudo systemctl status headscale

Thêm thiết bị vào mạng Headscale

Tạo user (namespace)

Headscale dùng khái niệm “user” để nhóm thiết bị:

# Tạo user mới
headscale users create myteam

# Liệt kê users
headscale users list

Đăng ký thiết bị Linux/macOS

Trên máy cần kết nối (cài Tailscale client bình thường):

# Trỏ về Headscale server của bạn thay vì server Tailscale
tailscale up --login-server https://hs.example.com

Lệnh này sẽ in ra một URL để xác thực. Trên server Headscale, approve bằng:

# Lấy danh sách node chờ approve
headscale nodes list --user myteam

# Approve node (thay NODE_KEY bằng key từ output ở trên)
headscale nodes register --user myteam --key NODE_KEY

Approve thủ công ổn cho vài máy, nhưng khi deploy hàng loạt thì dùng auth key cho tiện hơn nhiều:

# Tạo reusable auth key
headscale preauthkeys create --user myteam --reusable --expiration 24h

# Trên client dùng auth key
tailscale up --login-server https://hs.example.com --authkey tskey-auth-XXXX

Kiểm tra kết nối

# Trên server Headscale
headscale nodes list

# Output mẫu:
# ID | Hostname    | Name        | MachineKey | NodeKey | User   | IP addresses         | Ephemeral | Last seen
# 1  | web-server  | web-server  | ...        | ...     | myteam | 100.64.0.1           | false     | 2026-04-25 10:30
# 2  | dev-laptop  | dev-laptop  | ...        | ...     | myteam | 100.64.0.2           | false     | 2026-04-25 10:31
# Trên client kiểm tra kết nối
tailscale status
tailscale ping 100.64.0.1

Câu chuyện debug thực tế

Sau khoảng 3 tháng chạy production, mình gặp một vấn đề khó chịu: intermittent packet loss chỉ xảy ra vào giờ cao điểm — buổi sáng từ 9–11h và chiều 14–16h. Các connection vẫn “up” theo Tailscale status nhưng latency tăng vọt từ 5ms lên 200ms+, rồi drop packet.

Ban đầu mình nghĩ là vấn đề mạng ISP. Nhưng dùng tailscale ping --verbose mới thấy: traffic không đi direct (peer-to-peer) mà đang relay qua DERP server. Lý do là NAT traversal thất bại, hai peer không punch-through được với nhau.

Fix là thêm một DERP server tự host gần hơn về mặt địa lý, và cấu hình ACL để force direct connection khi có thể. Từ đó stable hẳn.

Bài học rút ra: Tailscale/Headscale trông đơn giản nhưng phía dưới là WireGuard + DERP + NAT traversal. Khi có sự cố cần biết layer nào đang fail, không thể chỉ nhìn vào tailscale status là xong.

Quản lý ACL Policy

Headscale hỗ trợ HuJSON policy (tương tự Tailscale ACL). Tạo file /etc/headscale/acl.yaml:

acls:
  # Dev team truy cập tất cả
  - action: accept
    src: ["myteam:*"]
    dst: ["myteam:*:*"]

  # Production servers chỉ nhận kết nối từ specific nodes
  - action: accept
    src: ["myteam:dev-laptop"]
    dst: ["myteam:prod-server:22,80,443"]
# Load policy
headscale policy set --path /etc/headscale/acl.yaml

Kết luận

Sau 6 tháng chạy Headscale trên production với ~30 node, nhận xét thẳng thắn: nó hoạt động tốt và đáng tin cậy. Nếu bạn đã quen với Tailscale và cần tự host — vì privacy, compliance, hay đơn giản không muốn bị giới hạn 100 thiết bị — Headscale là lựa chọn trưởng thành nhất hiện có.

Cái hay nhất của Headscale: toàn bộ Tailscale client trên các node không cần đổi gì — chỉ thêm --login-server là xong. Một vài tính năng của Tailscale cloud chưa được port sang, như Tailscale SSH cert hay một số ACL tag nâng cao. Nhưng với nhu cầu kết nối máy chủ, dev team, homelab — hoàn toàn không thiếu gì.

Chi phí vận hành thực tế: VPS $5/tháng chạy Headscale cho 30 node, so với Tailscale Personal Pro $6/user/tháng — với team nhỏ thì break-even sau 1–2 tháng, chưa kể lợi ích về data sovereignty mà không có con số nào định giá được.

Share: