剪藏:headscale部署

剪藏:headscale部署
但是最近远程 scrcpy 控制家里的手机发现很慢受不了,也就是因为 wireguard 所有端,除了 ecs 以外都是没有公网 IP,都是 ecs 转发而存在木桶效应,即使每个 wireguard 节点有公网 IP,随着端越来越多配置起来也非常繁琐的。
如果只需要一个 ecs 公网 IP,然后各个端能 NAT 打洞直连,速度也会不错呢。
没错,tailscale 就是这样用户态实现 wireguard 协议,然后控制平面很强(可以给各个端推送和更新配置,以及还支持 ACL),而 headscale 就是开源的 tailscale 的控制端。这里不讲解接入 tailscale 官方,而是自建 headscale 和 derper。
部署本教程都是 ecs 公网 IP 形式部署,自己有 https 绿锁证书的,相关配置自己研究,文章涉及到的端口需要在 ecs 安全组以及防火墙相关端口自行放行:
| 协议 | 端口 |
|---|---|
| tcp | headscale web server 端口 |
| tcp | derp 的 https 端口 |
| udp | derp 的 stun 端口 |
机器基础参数自行设置好,例如转发和文件打开数。
headscale 部署和设置参考官方文档 running-headscale-linux-manual ,因为 headscale 不依赖 CGO,所以我使用二进制部署,官方这个文档也是隐藏起来的,怎么部署都可以,不一定要和我一样。
截至 2024/07/25 ,从官方仓库看,正式的 release 版本是 v0.22.3,而 main、pre-release 版本的配置文件和老版本文件有一些不兼容的字段,很多文章都是从 main 下载 config-example.yaml 文件后按照他们文章修改字段对应不上。
12345678910 |
wget -O /usr/local/bin/headscale https://github.com/juanfont/headscale/releases/download/v0.22.3/headscale_0.22.3_linux_amd64chmod a+x /usr/local/bin/headscalemkdir /etc/headscale/wget -O /etc/headscale/config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.22.3/config-example.yaml |
|---|
创建 headscale daemon systemd 后台文件:
123456789101112131415161718192021222324252627 |
cat > /etc/systemd/system/headscale.service << EOF[Unit]Description=headscale controllerAfter=syslog.targetAfter=network.target[Service]Type=simpleUser=headscaleGroup=headscaleExecStart=/usr/local/bin/headscale serveRestart=alwaysRestartSec=5# Optional security enhancementsNoNewPrivileges=yesPrivateTmp=yesProtectSystem=strictProtectHome=yesWorkingDirectory=/var/lib/headscaleReadWritePaths=/var/lib/headscale /var/run/headscaleAmbientCapabilities=CAP_NET_BIND_SERVICERuntimeDirectory=headscale[Install]WantedBy=multi-user.targetEOF |
|---|
因为 headscale 是一个控制中心,不需要特权,我们运行在非 root 用户下,添加用户:
1234567891011121314 |
useradd \ --create-home \ --home-dir /var/lib/headscale/ \ --system \ --user-group \ --shell /usr/sbin/nologin \ headscalemkdir -p /var/run/headscale/touch /var/lib/headscale/db.sqlite /etc/headscale/derp.yamlchown -R headscale:headscale /var/run/headscale/ /var/lib/headscalechmod a+r /etc/headscale/config.yaml /etc/headscale/derp.yaml |
|---|
接下来 vi /etc/headscale/config.yaml 修改配置文件一些内容:
12345678910111213141516171819202122232425262728293031323334353637 |
server_url: http://<ecs_public_ip>:8081listen_addr: 0.0.0.0:8081ip_prefixes: - 100.64.0.0/10derp: server: enabled: false paths: [/etc/headscale/derp.yaml]disable_check_updates: truedns_config: override_local_dns: false magic_dns: false base_domain: xxxrandomize_client_port: true |
|---|
安装和设置 headscale 补全,因为 headscale 是 daemon 和 cli 两部分,daemon 起来后 cli 很多命令可以操作:
1234 |
apt updateapt install -y bash-completionheadscale completion bash > /etc/bash_completion.d/headscale. /etc/bash_completion.d/headscale |
|---|
启动 headscale daemon 进程:
12345678 |
headscale configtestheadscale servechown -R headscale:headscale /var/lib/headscalesystemctl daemon-reloadsystemctl enable --now headscale |
|---|
derper 部署headscale 是控制层面,下发信息和路由配置,而 derper 是中继和打洞服务器,利用修改版本的 stun 协议打洞,例如两个无公网 IP 但是可以访问到公网的客户端,客户端和另一个客户端建立连接都是先 连 derp 看看自己和对端能否打洞成功,成功就直连对方,否则就走 derper 中继来转发。
官方推荐的使用 https 绿锁证书部署,但是我们没有。并且官方不提供 derp 编译,所以需要 hack 和编译,相关文件在 compile-and-packages/tailscale,基于 alpine 镜像,并且能自动生成 https 证书。
这里使用编译好的 docker 镜像部署,二进制部署因为默认使用一些路径,所以坑比较少,这里使用 docker host 网络部署并说明一些东西。
先查看机器上的 iptables 模式:
12345 |
$ iptables -w -Viptables v1.4.21iptables v1.8.4 (legacy)iptables v1.8.9 (nf_tables) |
|---|
前者都是 legacy 模式,后者是 nf_tables,下面的 tailscale 需要设置 firewall-mode 为 iptables 或者 nftables:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748 |
cat > docker-compose.yml << EOFservices: derper: container_name: derper image: registry.aliyuncs.com/zhangguanzhang/derper:v1.70.0 restart: unless-stopped network_mode: host volumes: - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro - ./cert:/cert - /var/run/tailscale/tailscaled.sock:/var/run/tailscale/tailscaled.sock:ro environment: DERP_DOMAIN: my.mydomain.no DERP_ADDR: ':12345' DERP_STUN_PORT: '3478' DERP_HTTP_PORT: '-1' DERP_VERIFY_CLIENTS: "true" DERP_CERT_DIR: /cert depends_on: - tailscale tailscale: hostname: tailscale container_name: tailscale restart: unless-stopped network_mode: host image: docker.m.daocloud.io/tailscale/tailscale:v1.70.0 cap_add: - NET_ADMIN - NET_RAW - sys_module command: tailscaled -no-logs-no-support volumes: - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro - /dev/net/tun:/dev/net/tun - /lib/modules:/lib/modules - /run/xtables.lock:/run/xtables.lock:rw - /var/run/tailscale/:/var/run/tailscale/ - ./tailscale_data:/var/lib/tailscale environment: TS_HOSTNAME: ecs TS_DEBUG_FIREWALL_MODE: iptables EOF |
|---|
为了防止 derper 被白嫖,所以 官方文档 推荐开启 --verify-clients 选项,然后同时部署一个 tailscale 客户端,derper 会从这个 tailscale 的 sock 文件获取所有认证过的 peer 信息,所以上面挂载 sock 和 depends_on 以及使用同一版本的 derper 和 tailscale 。
修改 headscale 指定的 derp 文件 vi /etc/headscale/derp.yaml 的内容:
12345678910111213141516171819202122232425262728293031 |
regions: 900: regionid: 900 regioncode: custom regionname: custom_name nodes: - name: 900a regionid: 900 hostname: my.mydomain.no ipv4: <ecs_public_ip> derpport: 12345 stunport: 3478 stunonly: false insecurefortests: true |
|---|
3年前 tailscale derp 就在 >= 1.20 的增加字段 derpport 和 insecurefortests
而 三年前的 headscale 版本是 0.14.0 里看到使用的 tailscale 版本是 v1.20.4,压根不存在网上一些 insecurefortests 只能写 json 里配置的说法(DERPPort 和 InsecureForTests 都是 golang json tag,yaml 读取如果没 yaml tag 就使用 json tag),网上好多人互相抄袭还单独创建一个 web server 写 json 文件,我不理解。
123 |
docker-compose up -dcurl -vk https://127.0.0.1:12345/ |
|---|
客户端接入所有客户端有微皮恩的,需要把 ecs 的公网 IP 设置成直连不走代理,以及如果是家里宽带,可以把 Upnp 打开,这样打洞直连成功率会高些。
创建 authkeysTailscale 中有一个概念叫 tailnet,你可以理解成租户,租户与租户之间是相互隔离的,具体看参考 Tailscale 的官方文档: What is a tailnet。
Headscale 也有类似的实现叫 user,即用户。我们需要先创建一个 user,以便后续客户端接入,例如:
1 |
headscale user create default |
|---|
其他客户端接入需要首先在服务端生成 pre-authkey 的 key :
| 1234567 | $ headscale preauthkeys --user default create --reusable --expiration 365d $ headscale preauthkeys --user default listID \| Key \| Reusable \| Ephemeral \| Used \| Expiration \| Created \| Tags1 \| 49f9cd7f4e7b3e33023a9064xxxxxebf00778d2xxxxxxxxx \| false \| false \| false \| 2025-07-24 14:32:45 \| 2024-07-24 14:32:45 \| |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
tailscale 也是分为 tailscaled 的 daemon 和 tailscale 的 cli 工具,windows、Linux 以及安卓的 Magisk 模块等都可以使用 cli 工具操作和排查,这点很重要。
下面是 tailscale up 时候一些常用通用选项:
-
--login-server: 指定使用的中央服务器地址(必填) -
--advertise-routes: 向中央服务器报告当前客户端处于哪个内网网段下, 便于中央服务器让同内网设备直接内网直连(可选的)或者将其他设备指定流量路由到当前内网(可选),多条路由英文逗号隔开 -
--accept-routes: 是否接受中央服务器下发的用于路由到其他客户端内网的路由规则(可选) -
--accept-dns: 是否使用中央服务器下发的 DNS 相关配置(可选, 推荐关闭) -
--hostname: 设置 machine name,否则默认会以 hostname 注册上去,特别安卓的 hostname 无法修改
tailscale cli 官方文档 https://tailscale.com/kb/1080/cli,也可以自己 tailscale --help 看命令帮助。
Linux 接入##### derp 上的客户端先接入 derp 也就是 ecs 上那个 tailscale:
12345 |
docker exec -ti tailscale shtailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname ecs --accept-dns=false --authkey 49f9cd.... |
|---|
可以 headscale 上查看信息:
| 1234 | $ headscale node listAn updated version of Headscale has been found (0.23.0-beta1 vs. your current v0.22.3). Check it out https://github.com/juanfont/headscale/releasesID \| Hostname \| Name \| MachineKey \| NodeKey \| User \| IP addresses \| Ephemeral \| Last seen \| Expiration \| Online \| Expired1 \| ecs \| ecs \| [3ixoT] \| [VFZTB] \| default \| 100.64.0.1, \| false \| 2024-07-25 05:20:50 \| 0001-01-01 00:00:00 \| online \| no |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
Linux 上 tailscale 会利用 tun 创建网卡,路由表在 52 里:
1 |
$ ip route show table 52 |
|---|
另外要注意,由于 derper 依赖这个 tailscale,所以这个 tailscale 不要乱重启(因为是授权信息来源),可能会导致其他端会断开一下子,例如我就遇到安卓端玩游戏会断开下。
Linux 客户端官方相关文档:
| 1 | curl -fsSL https://tailscale.com/install.sh \| sh |
| --- | --- | --- |
然后自己 tailscale up --...... 登录。
windows在 https://tailscale.com/download 下载安装(安装失败换 msi 安装包 https://pkgs.tailscale.com/stable/#windows ),如果 msi 安装包启动报错 iphelp 啥的相关,在 services.msc 里把 ip helper 属性设置自启动后启动下。
启动后,官方文档 windows-client 说需要 powershell 修改下面的注册表:
123 |
New-Item -Path "HKLM:\SOFTWARE\Tailscale IPN"New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value alwaysNew-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name LoginURL -PropertyType String -Value http://YOUR-HEADSCALE-URL:8081 |
|---|
但是没必要,而且系统托盘图标点击 login 可能无法弹出浏览器页面,可以 powershell 或者 gitbash 里执行 tailscale cli 注册:
1 |
tailscale up --login-server http://xxx:8081 --hostname laptop --accept-routes=true --accept-dns=false --authkey 49f9cd.... |
|---|
如果执行后卡住的,在 services.msc 里把 tailscale 重启下后再试试。
windows 开启 --unattended 锁屏不会断开连接,一些 windows 早期版本问题见下面 issue :
windows 关闭上传日志到 log.tailscale.io 可以在 C:\ProgramData\Tailscale 下新增 tailscaled-env.txt 写入 env:
1 |
TS_NO_LOGS_NO_SUPPORT=true |
|---|
安卓https://github.com/tailscale/tailscale-android
推荐 https://f-droid.org/packages/com.tailscale.ipn/ 下载,安装后在 右上角 - Accounts - 三个点 - Settings Accounts Use an alternate server,输入 http://xxx:8081,然后下面 Use an auth key 可能会没使用 authkey 跳转到浏览器出现一个 headscale nodes register ... --key nodekey:xxxx ,所以我们需要回到 headscale 上命令授权:
| 1234 | journalctl -xe --no-pager -u headscale \| grep nodekeyheadscale nodes register --user default --key nodekey:xxxxx |
| --- | --- | --- |
对于一些没有浏览器也没 tailscale cli 的都可以这样手动授权下。安卓上点击每个 peer 进去的右上角图标等于 tailscale ping xxx ,会显示能否直连和延迟。也可以后续使用 Magisk tailscale,那样可以有 cli 了,另外 apk 是使用 V-P-N 形式,断网和切换流量会断开,而 Magisk tailscale 则不会。
adb 启动
1 |
adb shell am start -n com.tailscale.ipn/com.tailscale.ipn.MainActivity |
|---|
openwrt参照 https://github.com/adyanth/openwrt-tailscale-enabler,需要有 kmod-tun 模块包。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051 |
VER=1.70.0wget https://pkgs.tailscale.com/stable/tailscale_${VER}_arm64.tgztar zxvf tailscale_*_arm64.tgzmkdir -p /etc/tailscale/mv tailscale_*_arm64/tailscal* /usr/binrm -rf tailscale_*_arm64cat << EOF > /etc/init.d/tailscale#!/bin/sh /etc/rc.common# Copyright 2020 Google LLC.# SPDX-License-Identifier: Apache-2.0USE_PROCD=1START=99STOP=1start_service() { procd_open_instance procd_set_param command /usr/bin/tailscaled # Set the port to listen on for incoming VPN packets. # Remote nodes will automatically be informed about the new port number, # but you might want to configure this in order to set external firewall # settings. # procd_append_param command --port 41641 # OpenWRT /var is a symlink to /tmp, so write persistent state elsewhere. procd_append_param command --state /etc/config/tailscaled.state # Persist files for TLS cert & Taildrop files procd_append_param command --statedir /etc/tailscale/ procd_append_param command -no-logs-no-support procd_set_param respawn procd_set_param stdout 1 procd_set_param stderr 1 procd_close_instance}stop_service() { /usr/bin/tailscaled --cleanup}EOF/etc/init.d/tailscale enable/etc/init.d/tailscale starttailscale status |
|---|
登录
123 |
tailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname openwrt --accept-dns=false --authkey 49f9cd....tailscale status |
|---|
查看日志:
1 |
logread tailscaled |
|---|
tailscale client 使用每个 tailscale 端都可以执行命令来查看和排查一些信息。
123 |
tailscale debug derp-map |
|---|
查看和检测当前网络,会输出当前 derp 服务器信息:
1234567891011 |
$ tailscale netcheckReport: * UDP: true * IPv4: yes, xx.x.xx.xxx:54417 * IPv6: no, but OS has support * MappingVariesByDestIP: * PortMapping: * Nearest DERP: tx * DERP latency: - custom: 500µs (tx) |
|---|
tailscale ping 命令可以用于测试 IP 连通性, 同时可以看到时如何连接目标节点的. 默认情况下 Ping 命令首先会使用 Derper 中继节点通信, 然后尝试 P2P 连接; 一旦 P2P 连接成功则自动停止 Ping:
123 |
$ tailscale ping 100.64.0.3pong from redmi8 (100.64.0.3) via DERP(custom) in 30mspong from redmi8 (100.64.0.3) via 192.168.0.107:47316 in 32ms |
|---|
status 查看以及 peer 的信息:
12 |
tailscale statustailscale status --json |
|---|
修改当前节点信息,支持修改的属性 –help 自行查看:
12 |
tailscale set --hostname=xxx |
|---|
或者可以 down 后 up 带单一需要修改的参数执行下:
123 |
tailscale downtailscale up --xxx |
|---|
derp 调试| 1 | curl -vk https://127.0.0.1:12345/debug/vars |
| --- | --- |
打通内网Linux 端都要开启转发,windows 和安卓转发自行查找怎么配置。
| 123 | echo 'net.ipv4.ip_forward = 1' \| tee /etc/sysctl.d/ipforwarding.confecho 'net.ipv6.conf.all.forwarding = 1' \| tee -a /etc/sysctl.d/ipforwarding.confsysctl -p /etc/sysctl.d/ipforwarding.conf |
| --- | --- | --- | --- |
然后在 server 端查看 node ID :
| 123456 | $ headscale node listAn updated version of Headscale has been found (0.23.0-beta1 vs. your current v0.22.3). Check it out https://github.com/juanfont/headscale/releasesID \| Hostname \| Name \| MachineKey \| NodeKey \| User \| IP addresses \| Ephemeral \| Last seen \| Expiration \| Online \| Expired1 \| ecs \| ecs \| [3ixoT] \| [VFZTB] \| default \| 100.64.0.1, \| false \| 2024-07-26 07:12:50 \| 0001-01-01 00:00:00 \| online \| no2 \| localhost \| ax18 \| [egfcx] \| [LzS5J] \| default \| 100.64.0.2, \| false \| 2024-07-26 07:13:14 \| 0001-01-01 00:00:00 \| online \| no3 \| localhost \| redmi8 \| [uqIVP] \| [l6sL7] \| default \| 100.64.0.3, \| false \| 2024-07-26 07:13:06 \| 0001-01-01 00:00:00 \| online \| no |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
假设 ID==1 的局域网是 192.168.31.0/24 网段,我们希望其他 ID 设备上能访问到,先查看路由:
| 1234 | $ headscale routes listID \| Machine \| Prefix \| Advertised \| Enabled \| Primary.....1 \| ax18 \| 192.168.31.0/24 \| true \| false \| false |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
1 |
headscale routes enable -r 1 |
|---|
其他节点查看路由结果:
| 12 | $ ip route show table 52 \| grep "192.168.31.0/24"192.168.31.0/24 dev tailscale0 |
| --- | --- | --- |
其他节点启动时需要增加
--accept-routes=true选项来声明 “我接受外部其他节点发布的路由”。
现在你在任何一个 Tailscale 客户端所在的节点都可以 ping 通家庭内网的机器了,你在公司或者星巴克也可以像在家里一样用同样的 IP 随意访问家中的任何一个设备。
一个正在运行的节点增加路由可以使用 set 命令:
12 |
tailscale set --advertise-routes xx.xx.xx.0/24,xx,xxx.xxx.00.00/16 |
|---|
信息修改例如安卓的 hostname 由于没有 cli 无法修改,而默认 db 使用 sqlite,可以修改:
123456 |
$ sqlite3 /var/lib/headscale/db.sqlite.tablesupdate machines set given_name="redmi8" where id=3;修改 ip 需要先 tailscale down 改好后再 tailscale up |
|---|
其他的修改自己琢磨。

