🦎 Headscale 自建组网中枢:把 Tailscale 控制权攥在自己手里

Headscale 自建组网中枢

不想让组网数据流经官方云?Headscale 让你用一台 VPS 就把 Tailscale 的控制面完全私有化——IP、DNS、ACL、DERP 中继全部自主可控。

Headscale 完整部署

1. 概述

  • Headscale 是 Tailscale 官方云控制面的开源替代品,客户端仍使用官方 Tailscale。
  • 适合场景:设备数量多/私有化要求高/不希望数据过官方服务器,元素全部自主可控(IP 段、DNS、ACL、DERP 中继、审计)。
  • 架构:一台公网 VPS 跑控制面(Headscale)+ 各设备跑客户端(Tailscale)接入。
  • 版本注意:Headscale 迭代快,配置项随版本变化较大,请注意所用版本(如 v0.23.x / v0.27.x / 更新版)。本文以当前主流配置为准。
flowchart LR
    subgraph 公网VPS
        HS[Headscale 控制面<br/>:8080]
        DERP[嵌入式 DERP 中继<br/>:3478/udp]
        CADDY[反代 + HTTPS]
    end
    N1[设备 A · Tailscale] <-->|注册/心跳| HS
    N2[设备 B · Tailscale] <-->|注册/心跳| HS
    N1 <-->|NAT 打洞直连| N2
    N1 <-->|打洞失败走 DERP| DERP

前置条件

  • 一台公网 VPS(Debian 12 / Ubuntu 22.04+,1C/512M 以上即可)。
  • 一个域名,解析一个子域名(如 hs.example.com)A 记录到 VPS,生产环境建议配 HTTPS。
  • Docker + Docker Compose(或直接用二进制部署)。

2. 部署方式一:Docker Compose(推荐)

2.1 目录结构

sudo mkdir -p /opt/headscale/{config,data,run}
cd /opt/headscale

2.2 配置文件 /opt/headscale/config/config.yaml

容器内的路径是 /etc/headscale/var/lib/headscale,与宿主机卷映射对应,别搞混。

server_url: https://hs.example.com          # 客户端访问的地址(必须可被公网访问)
listen_addr: 0.0.0.0:8080                   # 控制面监听(HTTP)
metrics_listen_addr: 127.0.0.1:9090         # Prometheus 指标
grpc_listen_addr: 127.0.0.1:50443           # 远程 CLI(建议仅本机)

noise:
  private_key_path: /var/lib/headscale/noise_private.key   # 启动自动生成

prefixes:
  v4: 100.64.0.0/10                          # Tailscale 标准 CGNAT 网段
  v6: fd7a:115c:a1e0::/48                    # IPv6 ULA
  allocation: sequential                     # sequential | random

derp:
  server:
    enabled: true                            # 开启嵌入式 DERP(含 STUN)
    region_id: 999                           # 自定义区域 ID(避免与官方冲突)
    region_code: "headscale"
    region_name: "Headscale Embedded DERP"
    verify_clients: true                     # 仅允许本组网设备使用
    stun_listen_addr: "0.0.0.0:3478"
    private_key_path: /var/lib/headscale/derp_server_private.key
    automatically_add_embedded_derp_region: true
    ipv4: 你的VPS公网IP                      # 必填!
    ipv6: ""
  urls:
    - https://controlplane.tailscale.com/derpmap/default   # 官方 DERP 兜底
  paths: []                                  # 自定义 derp.yaml 路径
  auto_update_enabled: true
  update_frequency: 3h

database:
  type: sqlite
  debug: false
  sqlite:
    path: /var/lib/headscale/db.sqlite
    write_ahead_log: true

log:
  level: info
  format: text

policy:
  mode: file
  path: /etc/headscale/acl.hujson            # ACL 策略文件(见 ACL 笔记)

dns:
  magic_dns: true
  base_domain: example.com                   # MagicDNS 内网后缀
  override_local_dns: true
  nameservers:
    global: ["1.1.1.1", "8.8.8.8"]
    split: {}
  search_domains: []
  extra_records: []

unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"

disable_check_updates: true
ephemeral_node_inactivity_timeout: 30m
randomize_client_port: false
taildrop:
  enabled: true

精简版(不需要嵌入式 DERP 时)直接把 derp.server 关闭,urls 保留官方即可。

2.3 docker-compose.yml

services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale
    restart: unless-stopped
    command: serve
    volumes:
      - ./config:/etc/headscale
      - ./data:/var/lib/headscale
      - ./run:/var/run/headscale
    ports:
      - "8080:8080"          # 控制面 HTTP(一般反代后可不暴露公网)
      - "3478:3478/udp"      # 嵌入式 DERP 的 STUN(UDP,需公网)
    networks:
      - headscale-net

networks:
  headscale-net:
    name: headscale-net

启动:

sudo docker compose up -d
sudo docker compose logs -f headscale

3. 接入 HTTPS 反向代理

控制面建议走域名 + HTTPS(客户端登录时对证书敏感)。可选 Caddy / Nginx / Nginx Proxy Manager。

3.1 方案一:Caddy(全自动证书,最简单)

创建 Caddyfile,与 headscale 处于同一 compose 网络时直接用服务名:

hs.example.com {
    reverse_proxy headscale:8080
}

容器:

  caddy:
    image: caddy:latest
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - headscale-net

3.2 方案二:Nginx(关键点)

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 443 ssl http2;
    server_name hs.example.com;
    ssl_certificate     /etc/nginx/ssl/fullchain.pem;   # 自备证书
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;      # 指向 headscale HTTP
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_read_timeout 3600;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_redirect http:// https://;
    }
}
server { listen 80; server_name hs.example.com; return 301 https://$host$request_uri; }

4. 方式二:二进制 + systemd(对部署完全掌控)

# 下载二进制(到 GitHub Releases 取最新版)
curl -L -o /usr/local/bin/headscale https://github.com/juanfont/headscale/releases/download/<版本>/headscale_<版本>_linux_amd64
chmod +x /usr/local/bin/headscale

# 创建运行用户与目录
sudo useradd --home /var/lib/headscale --system headscale
sudo mkdir -p /etc/headscale /var/lib/headscale /var/run/headscale
sudo chown -R headscale:headscale /var/lib/headscale /var/run/headscale

# 写入 config.yaml(同 2.2,路径为 /etc/headscale/config.yaml)
sudo chown root:headscale /etc/headscale/config.yaml
sudo chmod 0640 /etc/headscale/config.yaml

创建 /etc/systemd/system/headscale.service

[Unit]
Description=Headscale Control Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=headscale
Group=headscale
WorkingDirectory=/var/lib/headscale
ExecStart=/usr/local/bin/headscale serve
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s
RuntimeDirectory=headscale
RuntimeDirectoryMode=0750
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/headscale /var/run/headscale

[Install]
WantedBy=multi-user.target
sudo -u headscale headscale configtest     # 先校验配置
sudo systemctl daemon-reload
sudo systemctl enable --now headscale
sudo systemctl status headscale

5. 初始化

5.1 创建用户(namespace)

docker exec -it headscale headscale users create default
docker exec -it headscale headscale users create admin
docker exec -it headscale headscale users list

5.2 生成 API Key(供 UI / 脚本)

docker exec headscale headscale apikeys create --expiration 999d

5.3 生成预认证 Key(preauthkey,供无人值守设备)

docker exec headscale headscale preauthkeys create --user default --reusable --expiration 90d

6. 客户端接入

6.1 Linux

curl -fsSL https://tailscale.com/install.sh | sh

# 交互式注册(浏览器打开链接)
sudo tailscale up --login-server https://hs.example.com

# 或配合预认证 Key 免交互
sudo tailscale up --login-server https://hs.example.com \
  --auth-key <preauthkey> \
  --accept-routes \
  --accept-dns=false

6.2 查看与注册(如果用了 –login-server 但需手动注册节点)

# 客户端命令输出注册 key(形如 nodekey:...),再在服务端执行:
docker exec headscale headscale nodes register --user default --key nodekey:xxxx
docker exec headscale headscale nodes list

6.3 Windows

  • 正常安装 Tailscale 客户端后,导入注册表切换控制面,或用命令:
    • 官方提供的 /windows 注册表导入方式;手动改 HKLM\SOFTWARE\Tailscale IPN 下的 LoginURL 为自建地址(按官方指引为准)。

6.4 iOS / Android

  • 安装官方 App 后,在登录页点击右上角菜单/图标(连击),进入"Use a custom server / Alternative server",填写 https://hs.example.com

6.5 用 Docker 跑 Tailscale 节点(如 NAS 共享内网)

services:
  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    environment:
      TS_AUTHKEY: <preauthkey>
      TS_EXTRA_ARGS: --login-server=https://hs.example.com --advertise-routes=192.168.1.0/24 --accept-routes
      TS_USERSPACE: "false"
    volumes:
      - ./state:/var/lib/tailscale
    devices:
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - NET_RAW
    network_mode: host
    restart: always

7. Web 管理面板(可选)

Headscale 本身是 CLI,可加可视化面板。

7.1 Headplane(新一代 UI,功能最全)

  headplane:
    image: ghcr.io/tale/headplane:latest
    restart: unless-stopped
    volumes:
      - ./config/headplane.yaml:/etc/headplane/config.yaml
      - ./data/headplane:/var/lib/headplane
      - ./config:/etc/headscale:ro            # 复用 headscale 配置
    environment:
      - HEADSCALE_ADDR=http://headscale:8080
    networks: [headscale-net]

headplane.yaml 要点:

server:
  host: "0.0.0.0"
  port: 3000
  cookie_secret: "<随机32位字符串: tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32>"
  cookie_secure: true
  data_path: "/var/lib/headplane"

headscale:
  url: "http://headscale:8080"
  public_url: "https://hs.example.com"
  config_path: "/etc/headscale/config.yaml"
  config_strict: true

integration:
  docker:
    enabled: false
  • 访问:https://hs.example.com/admin/(Caddy/Traefik 里再把 /admin 路由到 headplane:3000)。
  • 首次打开需填写 5.2 生成的 API Key。

7.2 轻量替代:headscale-ui / headscale-admin

  headscale-ui:
    image: ghcr.io/gurucomputing/headscale-ui:latest
    restart: unless-stopped
    networks: [headscale-net]

8. 自建 DERP 中继(打洞失败兜底)

打洞直连失败时走 DERP 中继。公网质量差时建议自建独立 DERP。

sequenceDiagram
    participant A as 设备A(NAT后)
    participant B as 设备B(NAT后)
    participant DER as DERP中继(公网)
    A->>DER: 尝试 NAT 打洞
    A-->>B: 直连成功?
    alt 直连失败
        A->>DER: 建立中继隧道
        DER->>B: 转发加密流量
    else 直连成功
        A--xB: 端到端加密直连
    end

8.1 嵌入式 DERP(在上面 config.yaml 已启用)

自带 STUN(UDP 3478)+ 中继,需在 derp.server 填好 ipv4 公网 IP。

8.2 独立 DERP 服务器(derper,生产推荐)

  derper:
    image: fredliang/derper:latest
    container_name: derper
    restart: unless-stopped
    ports:
      - "41000:41000/udp"   # STUN
      - "40000:40000"       # DERP 主端口;若自有证书可再开需要端口
    environment:
      DERP_DOMAIN: "47.1.1.1"        # 你的 IP 或域名
      DERP_CERT_MODE: manual
      DERP_CERT_DIR: /certs
      DERP_ADDR: ":40000"
      DERP_STUN: "true"
      DERP_STUN_PORT: "41000"
      DERP_HTTP_PORT: "-1"
      DERP_VERIFY_CLIENTS: "true"    # 防白嫖;需本地 tailscaled.sock 或 tailscale 节点
    volumes:
      - ./certs:/certs               # 自签证书,命名 <ip>.key / <ip>.crt

headscale 侧通过 derp.paths 引用:

# /etc/headscale/derp.yaml
regions:
  900:
    regionid: 900
    regioncode: cn-custom
    regionname: China
    nodes:
      - name: node
        regionid: 900
        hostname: 47.1.1.1
        ipv4: 47.1.1.1
        stunport: 41000
        derpport: 40000
        insecurefortests: true      # 自签证书时需 true

验证:客户端 tailscale netchecktailscale debug derp-map


9. 常用管理命令速查

命令(docker exec headscale headscale …) 作用
users create <name> / users list 创建 / 列出用户分组
nodes list / nodes list -u <user> -t 节点列表(带 tag)
nodes register --user <u> --key nodekey:xxx 手动注册节点
nodes delete -i <id> 删除节点
nodes tag -i <id> -t tag:xxx 给节点打标签
nodes expire -i <id> 强制节点过期
apikeys create --expiration 999d 创建 API Key
preauthkeys create --user <u> --reusable --expiration 90d 创建预认证 Key
policy set -f policy.hujson 数据库模式下发 ACL
headscale configtest / tailscale netcheck 配置校验 / 网络诊断
headscale debug derp-map 查看 DERP 列表与连通

10. 常见问题排查

现象 处理
客户端登录打不开链接 server_url 与反代域名不一致;未走 https;反代未配 WebSocket(Upgrade 头)
反复要求登录 / 节点掉线 检查服务器时间(NTP);密钥过期;重启 systemd 服务或容器
节点已 up 但不在线 确认客户端 --login-server 指向正确域名;nodes list 查看在线状态
DERP 连不上 STUN 端口 udp/3478(或 41000)未放行;内核防火墙 ufw/firewalld 拦截
子网路由不通 客户端要 --accept-routes;路由需在控制台批准或 autoApprovers 豁免
客户端登录提示 certificate 域名证书未配置;临时可用 talk to hostname directly 或补齐证书
UI 面板登录失败 API Key 过期(重新 apikeys create);public_url / url 填错
Docker 卷权限报错 挂载目录 owner 需对应容器 user(如 1000:1000
ACL 不生效 Headscale 需 policy.mode: file + 正确 path,且文件语法正确

11. 安全建议

  1. 控制面必须放在反向代理后并启用 HTTPS,不要直接暴露裸 HTTP 8080。
  2. 默认放行改为最小权限:ACL 写"允许互访自己 + 显式放行",多用户场景务必收敛。
  3. 生产节点(无人值守服务器)打 tag: 并关闭密钥过期,用预认证 Key 接入。
  4. derp.server.verify_clients: true 或 derper --verify-clients,防止中继被白嫖。
  5. 定期备份数据库(SQLite 文件)与 config.yaml。
  6. 关注 Headscale GitHub Releases,及时升级(各版本 config 字段可能变化)。
评论 0 打赏 分享

相关推荐

评论交流