由来

这几天折腾了下 headscale 部署,发现网上很多部署文章都互相抄袭没有自己的折腾理解,所以写下部署以及讲解一些关键地方,本文章适合有 Linux 基础和部署过 headscale 或者想部署但是感觉市面上文章写得没有适合命令行选手的。

为什么我需要 headscale

之前一直使用 wireguard 组网,各地互访是没问题。

wireguard-simple

但是最近远程 scrcpy 控制家里的手机发现很慢受不了,也就是因为 wireguard 所有端,除了 ecs 以外都是没有公网 IP,都是 ecs 转发而存在木桶效应,即使每个 wireguard 节点有公网 IP,随着端越来越多配置起来也非常繁琐的。

如果只需要一个 ecs 公网 IP,然后各个端能 NAT 打洞直连(例如上面的左边手机和左下角电脑能打洞成功直连,不走 ecs 中继),速度也会不错呢。

nat-th

没错,tailscale 就是也是使用 wireguard 协议(没有内核模块就使用用户态),然后控制平面很强(可以给各个端推送和更新配置,以及还支持 ACL),而 headscale 就是开源的 tailscale 的控制端。这里不讲解接入 tailscale 官方,而是自建 headscale 。

可能有其他人问为啥不用 zerotier 或者其他啥的,那是因为:

  • 首先 wireguard 是 Linux 内核模块,即使不支持的也有用户态 wireguard 的很多成熟轮子
  • tailscale 的多端支持很好,iOS、MacOS 、安卓、win、Linux
  • 并且 tailscale 支持 IPv6,在公司内部已经使用搭建 IPv6 测试环境经过多轮验证了
  • 控制中心即使不用 tailscale 的也可以选择用 headscale
  • tailscale 外国用户群体大,而且 tailscale 有问题基本上 tailscale 开发者会修复

部署

本教程都是 ecs 公网 IP 形式部署,自己有 https 绿锁证书的,相关配置自己研究,文章涉及到的端口需要在 ecs 安全组以及防火墙相关端口自行放行:

协议 端口
tcp headscale web server 端口
tcp headscale 内置 derp 的 https 端口
udp derp 的 stun 端口

机器基础参数自行设置好,例如转发和文件打开数。

历史版本

归档在我 gist 上 headscale

headscale 部署和设置

参考官方文档 running-headscale-linux-manual ,因为 headscale 不依赖 CGO,所以我使用二进制部署,官方这个文档也是隐藏起来的,怎么部署都可以,不一定要和我一样。

截至 2025/01/25 ,从官方仓库看,正式的 release 版本是 v0.24.1,很多文章都是从 main 下载 config-example.yaml 文件后按照他们文章修改字段对应不上。

1
2
3
4
5
6
7
8
9
mkdir /etc/headscale/

VER=0.24.1
# 自行解决下载问题 # 有些老系统的 PATH 里没 /usr/local/bin/ ,可以放其他路径里
wget https://github.com/juanfont/headscale/releases/download/v${VER}/headscale_${VER}_linux_amd64
# 下载同版本的示例配置文件
wget -O /etc/headscale/config.yaml https://raw.githubusercontent.com/juanfont/headscale/v${VER}/config-example.yaml

chmod a+x /usr/local/bin/headscale

创建 headscale daemon systemd 后台文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
cat > /etc/systemd/system/headscale.service << EOF
[Unit]
Description=headscale controller
After=syslog.target
After=network.target

[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5

# Optional security enhancements
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
WorkingDirectory=/var/lib/headscale
ReadWritePaths=/var/lib/headscale /var/run/headscale
AmbientCapabilities=CAP_NET_BIND_SERVICE
RuntimeDirectory=headscale

[Install]
WantedBy=multi-user.target
EOF

因为 headscale 是一个控制中心,不需要特权,我们运行在非 root 用户下,添加用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
useradd \
--create-home \
--home-dir /var/lib/headscale/ \
--system \
--user-group \
--shell /usr/sbin/nologin \
headscale

mkdir -p /var/run/headscale/

#创建空的 SQLite 数据库文件和 derp 文件:
touch /var/lib/headscale/db.sqlite /etc/headscale/derp.yaml
chown -R headscale:headscale /var/run/headscale/ /var/lib/headscale
chmod a+r /etc/headscale/config.yaml /etc/headscale/derp.yaml

接下来 vi /etc/headscale/config.yaml 对照下面注释说明, 修改配置文件一些内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# server_url 写外网访问的 ip+端口
# 由于是ecs,所以写公网 IP,以及后面的端口要和 listen_addr 的一致
# 80 443 8080 需要备案
server_url: http://<ecs_public_ip>:8081
# 改为四个0,或者对应公网 IP 的内网网卡 IP 都行
listen_addr: 0.0.0.0:8081

prefixes:
# 因为我机器在阿里云上修改为下面网段,阿里云的 100.63.0.0/10 有用
v4: 100.63.0.0/16
# 如果不希望给设备下发 ipv6 可以注释了
v6: fd7a:115c:a1e0::/48

derp:
server:
# 使用用 headscale 内嵌的 derper 服务器
enabled: false
# 注释掉不使用官方的 derp 服务
#urls: []
# - https://controlplane.tailscale.com/derpmap/default
# 下面写 ecs 公网 IP
ipv4: x.x.x.x

# 关闭 headscale 自动更新,避免 break change 无法启动
disable_check_updates: true

dns:
# 关闭 magic_dns
magic_dns: false
# 设置为你自己的标识,否则后续 tailscale 端连接上显示是 user@example.com
base_domain: xxx

# 下面前俩修改为国内的 dns
nameservers:
global:
- 223.5.5.5
- 223.6.6.6

# 随机端口要打开, tailscale 客户端会使用41641 端口建立 wireguard 链接,这个端口会被中间网络设备阻止
randomize_client_port: true

安装和设置 headscale 补全,因为 headscale 是 daemon 和 cli 两部分,daemon 起来后 cli 很多命令可以操作:

1
2
3
4
apt update
apt install -y bash-completion
headscale completion bash > /etc/bash_completion.d/headscale
. /etc/bash_completion.d/headscale

启动 headscale daemon 进程:

1
2
3
4
5
6
7
8
# 测试文件
headscale configtest

headscale serve
# 配置文件没问题就 ctrl +c 取消掉使用 systemd 启动
chown -R headscale:headscale /var/lib/headscale
systemctl daemon-reload
systemctl enable --now headscale

derp 部署

先 ecs 上也部署一个,先查看机器上的 iptables 模式:

1
2
3
4
5
$ iptables -w -V
iptables v1.4.21
iptables v1.8.4 (legacy)
#----
iptables v1.8.9 (nf_tables)

前者都是 legacy 模式,后者是 nf_tables,下面的 tailscale 需要设置 firewall-mode 为 iptables 或者 nftables:

https://github.com/tailscale/tailscale/issues/14900 现阶段 tailscale in docker 如果是类似条件会导致问题,一般发生在 ubuntu:24.04 和 iptables 版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.3 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo
$ iptables -w -V
iptables v1.8.10 (nf_tables)

对于 ubuntu 24.04 和 1.8.10 nft 模式,推荐切模式重启后再部署:

1
2
3
update-alternatives --set iptables "/usr/sbin/iptables-legacy"
update-alternatives --set ip6tables "/usr/sbin/ip6tables-legacy"
reboot

创建 compose 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
cat > docker-compose.yml << EOF
services:

# https://tailscale.com/kb/1282/docker
tailscale:
hostname: tailscale
container_name: tailscale
restart: unless-stopped
network_mode: host
image: docker.m.daocloud.io/tailscale/tailscale:v1.74.1
#image: tailscale/tailscale:unstable
cap_add:
- NET_ADMIN
- NET_RAW
- sys_module
volumes:
- /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro
- /lib/modules:/lib/modules
- /run/xtables.lock:/run/xtables.lock:rw
- /var/run/tailscale/:/var/run/tailscale/
- ./tailscale_data:/var/lib/tailscale
devices:
- /dev/net/tun:/dev/net/tun
environment:
TS_EXTRA_ARGS: --advertise-tags=tag:container
TS_AUTH_ONCE: "true"
TS_HOSTNAME: ecs
TS_USERSPACE: "false"
# 禁用收集或发送任何日志数据,会发往 https://log.tailscale.io
TS_NO_LOGS_NO_SUPPORT: "true"
TS_SOCKET: "/var/run/tailscale/tailscaled.sock"
TS_STATE_DIR: /var/lib/tailscale/
#TS_LOGIN_SERVER: "http://127.0.0.1:528"
#TS_CONTROL_IS_PLAINTEXT_HTTP: "true"
TS_DEBUG_FIREWALL_MODE: nftables # 使用和宿主机模式一致的

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
# 容器生成的证书存放,如果是自己的绿锁证书,存放文件名为
# $DERP_DOMAIN.key $DERP_DOMAIN.crt
- ./cert:/cert
- /var/run/tailscale/:/var/run/tailscale/
environment:
DERP_DOMAIN: my.mydomain.no # 域名,由于不是绿锁 https,随意写,和后面 derp.yaml 一致即可
DERP_ADDR: ':12345' # https 端口
DERP_STUN_PORT: '3478' # udp port
DERP_HTTP_PORT: '-1'
DERP_VERIFY_CLIENTS: "true"
DERP_CERT_DIR: /cert
depends_on:
- tailscale
EOF
docker-compose up -d

关于 derp,从现有的 tailscale 代码情况来看,必须使用 https,而 我的 derp 镜像启动脚本 内是会自动生成证书的,如果使用自己证书挂载进去即可。

如果 ecs 是阿里云,阿里云的内网 DNS 网段会被上面的 tailscale 添加的 iptables drop 掉,需要提前放行下:

1
iptables -I INPUT -s 100.100.0.0/16 -j ACCEPT

客户端接入

所有客户端有微皮恩的,需要把 ecs 的公网 IP 设置成直连不走代理,以及如果是家里宽带,可以把 Upnp 打开,这样打洞直连成功率会高些。

创建 authkeys

Tailscale 中有一个概念叫 tailnet,你可以理解成租户,租户与租户之间是相互隔离的,具体看参考 Tailscale 的官方文档: What is a tailnet。
Headscale 也有类似的实现叫 user,即用户。我们需要先创建一个 user,以便后续客户端接入,例如:

1
headscale user create default

其他客户端接入需要首先在服务端生成 pre-authkey 的 key :

1
2
3
4
5
6
7
# 生成一个过期时间 365d 且可以重复使用的 authkey
$ headscale preauthkeys --user default create --reusable --expiration 365d

# 查看已经生成的 key:
$ headscale preauthkeys --user default list
ID | Key | Reusable | Ephemeral | Used | Expiration | Created | Tags
1 | 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:

1
2
3
4
5
# 也可以拷贝出来宿主机上执行
# docker cp tailscale:/usr/local/bin/tailscale /usr/bin/tailscale
docker exec -ti tailscale sh

tailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname ecs --accept-dns=false --authkey 49f9cd....

可以 headscale 上查看信息:

1
2
3
$ headscale node list
ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Online | Expired
1 | ecs | ecs | [3ixoT] | [VFZTB] | default | 100.63.0.1, | false | 2025-01-25 05:20:50 | 0001-01-01 00:00:00 | online | no

Linux 上 tailscale 会利用 tun 创建网卡,路由表在 52 里:

1
$ ip route show table 52
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 修改下面的注册表:

1
2
3
New-Item -Path "HKLM:\SOFTWARE\Tailscale IPN"
New-ItemProperty -Path 'HKLM:\Software\Tailscale IPN' -Name UnattendedMode -PropertyType String -Value always
New-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/ 下载,请下载 1.72.0 ,如果不是 https url 不要尝试使用 v1.76 <= x < v1.80 我尝试开发版本 1.78.1-t89039 版本无法注册,见原因 https://github.com/tailscale/tailscale/issues/13794 。

安装后在 右上角 - Accounts - 三个点 - Settings Accounts Use an alternate server,输入 http://xxx:8081,然后下面 Use an auth key 可能会没使用 authkey 跳转到浏览器出现一个 headscale nodes register ... --key nodekey:xxxx ,所以我们需要回到 headscale 上命令授权:

1
2
3
4
# 日志里会打印长串 key,所以不需要在其他端复制 nodekey
journalctl -xe --no-pager -u headscale | grep key

headscale nodes register --user default --key mkey: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 模块包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
VER=1.74.1
wget https://pkgs.tailscale.com/stable/tailscale_${VER}_arm64.tgz
tar zxvf tailscale_*_arm64.tgz
mkdir -p /etc/tailscale/
mv tailscale_*_arm64/tailscal* /usr/bin
rm -rf tailscale_*_arm64

cat << EOF > /etc/init.d/tailscale
#!/bin/sh /etc/rc.common

# Copyright 2020 Google LLC.
# SPDX-License-Identifier: Apache-2.0

USE_PROCD=1
START=99
STOP=1

start_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 env TS_DEBUG_FIREWALL_MODE=iptables

procd_set_param respawn
procd_set_param stdout 1
procd_set_param stderr 1

procd_close_instance
}

stop_service() {
/usr/bin/tailscaled --cleanup
}
EOF

chmod a+x /etc/init.d/tailscale
/etc/init.d/tailscale enable
/etc/init.d/tailscale start

tailscale status

登录

1
2
3
tailscale up --login-server=http://<HEADSCALE_PUB_ENDPOINT>:8081 --accept-routes=true --hostname openwrt --accept-dns=false --authkey 49f9cd....

tailscale status

查看日志:

1
logread tailscaled

主路由不设置路由下,转发放行+ snat:

1
2
# 例如 n1 取消桥接就是 -i eth0 了
iptables -I ts-forward -i br-lan -o tailscale0 -j MARK --set-xmark 0x40000/0xff0000

tailscale client 使用

每个 tailscale 端都可以执行命令来查看和排查一些信息。

1
2
3
# debug 命令隐藏在 --help 了,可以 tailscale debug --help 自行查看
# 打印 derp-map 信息
tailscale debug derp-map

查看和检测当前网络,会输出当前 derp 服务器信息:

1
2
3
4
5
6
7
8
9
10
11
$ tailscale netcheck

Report:
* 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:

1
2
3
$ tailscale ping 100.63.0.3
pong from redmi8 (100.63.0.3) via DERP(custom) in 30ms
pong from redmi8 (100.63.0.3) via 192.168.0.107:47316 in 32ms

status 查看以及 peer 的信息:

1
2
tailscale status
tailscale status --json

修改当前节点信息,支持修改的属性 –help 自行查看:

1
2
# tailscale set --help
tailscale set --hostname=xxx

或者可以 down 后 up 带单一需要修改的参数执行下:

1
2
3
tailscale down
tailscale up --xxx
# 然后会打印全部参数,复制执行下

查看当前的参数列表,官方没有 cmdline 形式打印,只有 json 的:

1
tailscale debug prefs

打通内网

Linux 端都要开启转发,windows 和安卓转发自行查找怎么配置。

1
2
3
echo 'net.ipv4.ip_forward = 1' | tee /etc/sysctl.d/ipforwarding.conf
echo 'net.ipv6.conf.all.forwarding = 1' | tee -a /etc/sysctl.d/ipforwarding.conf
sysctl -p /etc/sysctl.d/ipforwarding.conf

然后在 server 端查看 node ID :

1
2
3
4
5
$ headscale node list
ID | Hostname | Name | MachineKey | NodeKey | User | IP addresses | Ephemeral | Last seen | Expiration | Online | Expired
1 | ecs | ecs | [3ixoT] | [VFZTB] | default | 100.63.0.1, | false | 2025-01-26 07:12:50 | 0001-01-01 00:00:00 | online | no
2 | localhost | ax18 | [egfcx] | [LzS5J] | default | 100.63.0.2, | false | 2025-01-26 07:13:14 | 0001-01-01 00:00:00 | online | no
3 | localhost | redmi8 | [uqIVP] | [l6sL7] | default | 100.63.0.3, | false | 2025-01-26 07:13:06 | 0001-01-01 00:00:00 | online | no

假设 ID==1 的局域网是 192.168.31.0/24 网段,我们希望其他 ID 设备上能访问到,先查看路由:

1
2
3
4
5
$ headscale routes list
ID | Machine | Prefix | Advertised | Enabled | Primary
.....
1 | ax18 | 192.168.31.0/24 | true | false | false
headscale routes enable -r 1

其他节点查看路由结果:

1
2
$ 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 命令:

1
2
# 多条用英文逗号间隔
tailscale set --advertise-routes xx.xx.xx.0/24,xx,xxx.xxx.00.00/16

信息修改

修改 node 的 name,使用

1
headscale nodes rename -h

因为默认 db 使用 sqlite,可以修改一些信息:

1
2
3
4
5
$ sqlite3 /var/lib/headscale/db.sqlite
.tables

# ip_addresses="100.63.0.3"
修改 ip 需要先 tailscale down 改好后再 tailscale up,修改后配置会同步到所有 tailscale client 上,特别是游戏可能会闪断下

其他的修改自己琢磨。

openwrt ssh

如果你的 dropbear 只监听 br-lan 接口下,希望在其他 tailscale 节点上通过 tailscale 网络 ssh 到 openwrt 上(也就是目标 IP 是这个 openwrt 的 tailscale0 网卡IP),web 上的 系统-管理权 是无法做到的指定多个网卡的,需要配置层面修改 dropbear 相关,先通过以下命令查看,必须有 DirectInterface 选项:

1
grep -m1 -A25 'validate_section_dropbear\(\)' /etc/init.d/dropbear

支持 DirectInterface 可以尝试下面的看看能额外监听 tailscale0 不:

1
2
3
4
5
6
7
8
cat >> /etc/config/dropbear << EOF
config dropbear
option PasswordAuth 'on'
option RootPasswordAuth 'on'
option Port '22'
option DirectInterface 'tailscale0'
EOF
/etc/init.d/dropbear restart

我这里不支持,所以 web 上的 系统-管理权 不指定接口,就是监听 0.0.0.0,如果 op 上有公网 IP 或者 IPv6,监听 0.0.0.0 需要设置防火墙避免 ssh 被爆破。

一些说明

这里只介绍异地组网部分,其他的去看官方文档。

参考

请有自己的理解能力,不要随便照抄,端口啥的也可以自定义下。


一、搭建headscale服务端

1.创建相关文件并配置

这里需要手动创建 db.sqlite,这是根服务器的数据库文件。另外,还需要下载官网给的配置文件示例,然后需要修改一部分。

1
2
3
4
mkdir -p /home/docker/headscale/config && \
mkdir -p /home/docker/headscale/data && \
touch /home/docker/headscale/data/db.sqlite && \
wget https://github.com/juanfont/headscale/raw/v0.22.3/config-example.yaml -O /home/docker/headscale/config/config.yaml

经评论提醒,需要修改下载镜像对应版本号的 config-example.yaml,否则可能配置文件无法对应导致启动报错。

修改相关配置文件,比如配置文件中配置 127.0.0.1 的话,那么就只能本机访问。这里修改为 0.0.0.0 那么就所有的 ip 都能访问。

1
2
3
4
5
6
sed -i 's/127.0.0.1/0.0.0.0/g' /home/docker/headscale/config/config.yaml
# 这将修改以下几个地方,其中server_url还需要另外修改
# 1. server_url: http://0.0.0.0:8080
# 2. listen_addr: 0.0.0.0:8080
# 3. metrics_listen_addr: 0.0.0.0:9090
# 4. grpc_listen_addr: 0.0.0.0:50443

另外因为server_url是对外访问和登录的地址,如果你需要使用域名并开启https,那么照我如下配置。如果你仅仅使用ip,那么这个地址修改为ip+端口的方式即可。

域名填自己的

1
sed -i 's#http://0.0.0.0:8080#https://headscale.amjun.com#g' /home/docker/headscale/config/config.yaml

修改 dns 配置文件,如果不进行修改,那么登录时选择接受服务器的 dns 地址就会出现域名无法解析的情况。注意,这里的 dns 地址可以有多个,如果有需要自行添加即可。

1
sed -i 's/1\.1\.1\.1/114.114.114.114/g' /home/docker/headscale/config/config.yaml

客户端可以通过 主机名 + 用户 + 基础域名 访问任意一台终端,所以这里修改下基础域名,根据自己的实际域名进行填写。

域名填自己的

1
sed -i 's/example.com/amjun.com/' /home/docker/headscale/config/config.yaml

设置客户端随机端口,这里是听见有说不开机随机端口可能出现只能加入一台客户端的情况,为了保险还是选择开启。

1
sed -i 's/randomize_client_port: false/randomize_client_port: true/' /home/docker/headscale/config/config.yaml

启用内嵌的derp

当然你可以使用官方提供的 derp 服务器,但如果你想搭建自己的 derp 服务器,又不想另外搭建一个 derp 服务器,那么这是有必要的。

注意:使用此 derp 服务器,需要有域名并配置 ssl,否则请参考:Docker 搭建中继服务器 derp - 纯 IP 实现 。

1
2
3
4
5
6
7
8
9
10
11
12
# /home/docker/headscale/config/config.yaml
derp:
server:
# xxx
enabled: true
# xxx
region_id: 999
# xxx
region_code: "headscale"
region_name: "Headscale Embedded DERP"
# xxx
stun_listen_addr: "0.0.0.0:3478"

enabled 选项默认为 false,需要设置为 true。

3.启动headscale服务端

1
2
3
4
5
6
7
8
9
10
docker run -d \
--name headscale \
--restart always \
-v /home/docker/headscale/config:/etc/headscale/ \
-v /home/docker/headscale/data:/var/lib/headscale \
-p 8080:8080 \
-p 9090:9090 \
-p 3478:3478/udp \
headscale/headscale:0.22.3 \
headscale serve

下面是一些相关的命令:

以前是 namespace 的概念,现在似乎改为 user 了。

1
2
3
4
5
6
7
8
# 查看用户
docker exec -it headscale headscale users ls
# 创建用户
docker exec -it headscale headscale users create hz
# 生成apikey
docker exec -it headscale headscale apikey create
# 查询apikey
docker exec -it headscale headscale apikey ls

二、搭建web-ui

这一步是搭建 headscale 的 web-ui 界面,这不是必要的,因为阿蛮君更多使用命令,这只是有助于更好的查看相关信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
docker run -d \
--name headscale-webui \
--restart always \
-v /home/docker/headscale/config:/etc/headscale/:ro \
-v /home/docker/headscale/web-ui/data:/data \
-u root \
-p 5000:5000 \
-e HS_SERVER=https://headscale.amjun.com \
-e DOMAIN_NAME=https://headscale.amjun.com \
-e SCRIPT_NAME=/admin \
-e AUTH_TYPE=Basic \
-e BASIC_AUTH_USER=admin \
-e BASIC_AUTH_PASS=admin \
-e KEY="2uHP6BSVocX+wcWU5mzuXA7JvnZA70UaTadB8L1heOo=" \
--restart always \
ifargle/headscale-webui:latest

其中 /home/docker/headscale/config 为上面服务端的映射目录,这两个目录需要一致。HS_SERVER 和 DOMAIN_NAME 填写自己的域名。KEY 是用来加密待会需要保存的 apikey 的字符串,使用命令 openssl rand -base64 32 生成。

接下来需要创建 headscale 服务器的 api-key,这里设置一个比较久的过期时间。

三、配置nginx和web-ui

1.配置nginx

如果前面server_url没有配置 ssl 加密,那这一步也不是必要的。

添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name headscale.amjun.com;
ssl_certificate /etc/nginx/conf.d/cert/amjun.com.cer;
ssl_certificate_key /etc/nginx/conf.d/cert/amjun.com.key;

location ^~/ {
proxy_pass http://localhost:8080/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $server_name;
proxy_redirect https:// https://;
proxy_buffering off;
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 $http_x_forwarded_proto;
}

location ^~/admin/ {
proxy_pass http://localhost:5000/admin/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

server {
listen 80;
server_name headscale.amjun.com;
rewrite ^(.*)$ https://$host:443$1 permanent;
}

保存配置后重启 nginx,如果配置正确,那么通过 https://域名/admin 应该可以访问到 web-ui 界面,输入容器启动时配置的用户名密码即可进入。

Docker 搭建 headscale 异地组网完整教程

2.配置web-ui

上面提示输入 apikey,这个需要 headscale 服务端生成,这里设置一个比较长的时间,根据自己的需要即可。

1
docker exec -it headscale headscale apikeys create -e 10000d

运行命令后将生成的 key 输入,点击 SAVE 即可(不需要点击 Test,因为这个按钮是无意义的),概览页面如下:

Docker 搭建 headscale 异地组网完整教程

四、创建用户和授权密钥

headscale 个人理解是用户就是命名空间,进行网络隔离,所以需要先创建用户。

1
2
#  创建用户
docker exec -it headscale headscale users create hz

授权密码的作用是,客户端登陆时不需要再服务端进行确认,阿蛮君总是喜欢使用简单的方式,所以就选择这种方式了。

1
2
3
4
# reusable 参数代表可以重复使用,不加的话密钥只能用来一个客户端加入
docker exec -it headscale headscale preauthkeys create -e 10000d --reusable -u hz
# 查看密钥
docker exec -it headscale headscale preauthkeys list -u hz

这里记得记录好授权密钥,下面将会用到。

五、客户端安装

headscale 相当于是重新实现了 tailscale 的服务端,客户端基本还是用的 tailscale。

tailscale 客户端下载地址:https://tailscale.com/download/

1. windows

1.1 安装客户端

下载 windows 客户端并进行安装。

1.2 安装注册表

访问 https://headscale.amjun.com/windows,可以看见配置的教程。

提示需要修改注册表,这里可以直接 curl https://headscale.amjun.com/windows/tailscale.reg 下载文件后,双击运行文件进行安装即可。

1.3 登录

在 tailscale 的安装目录下使用 cmd 打开,输入命令:

1
2
cd C:\Program Files\Tailscale\
tailscale login --login-server https://headscale.amjun.com --authkey b6a9b4f4e9c3a7c7e7b9b1a3a6b9e6b7a5e7c2a1e3a0a4a1 --accept-dns=false --accept-routes

这里的 authkey 是第四步生成的,然后在服务器使用命令查看。

1
docker exec -it headscale headscale nodes ls

2. linux

2.1 脚本安装

1
2
curl -fsSL https://tailscale.com/install.sh | sh && \
tailscale login --login-server https://headscale.amjun.com --authkey b6a9b4f4e9c3a7c7e7b9b1a3a6b9e6b7a5e7c2a1e3a0a4a1 --accept-dns=false --accept-routes

这里仅仅安装方式不同,登录的命令完全一样。

2.2 docker安装

1
2
3
4
5
6
7
8
9
docker run -d \
--name=tailscaled \
-v /home/docker/tailscale/:/var/lib \
-v /dev/net/tun:/dev/net/tun \
-e TS_STATE_DIR=/var/lib/state/ \
--network=host \
--restart always \
--privileged tailscale/tailscale:v1.44.0 \
tailscaled --tun=tailscale0 -no-logs-no-support=true

这里需要将 TS_STATE_DIR 指定的文件夹持久化,否则删除容器再新建后,在服务端能看见重复的设备。并且需要指定 --tun=tailscale0,不然似乎不能在宿主机创建网卡。

然后使用如下命令进行登录。

1
docker exec -it tailscaled tailscale login --login-server https://headscale.amjun.com --accept-dns=false --accept-routes=false --authkey b6a9b4f4e9c3a7c7e7b9b1a3a6b9e6b7a5e7c2a1e3a0a4a1 --advertise-routes=172.21.9.0/24,172.30.1.0/24,172.26.1.0/24,172.20.2.0/23

根据自己网络情况设置 --advertise-routes,我这是需要通过这台内网机器访问公司其他网络。

如果出现如下错误:

running [/sbin/ip6tables -t filter -N ts-input –wait]: exit status 3: modprobe: can’t change directory to ‘/lib/modules’: No such file or directory
ip6tables v1.8.8 (legacy): can’t initialize ip6tables table `filter’: Table does not exist (do you need to insmod?)
Perhaps ip6tables or your kernel needs to be upgraded

执行以下命令:

1
2
# 防止探测ip6table
sudo modprobe ip6table_filter

3. 安卓

3.1 安装并配置

目前官方暂不可以自定义登录链接,需要下载可以自定义登录链接的安卓客户端,阿蛮君这里提供了一份可以使用的客户端,链接放在后面。

安装后,点击右上角三个设置自定义登录地址:

Docker 搭建 headscale 异地组网完整教程

点击保存并重启,再点击 sign in,将看见如下内容。

Docker 搭建 headscale 异地组网完整教程

3.2 服务端注册

根据提示在服务端运行命令:

1
docker exec -it headscale headscale nodes register --user hz --key nodekey:xxxx

注意这里的 USERNAME 需要修改为自己刚刚创建的用户名。

下载地址

tailscale安卓客户端:https://pan.baidu.com/s/1VPkD9mGRexCndLJWimKNtQ?pwd=akak

其他问题

宣告了路由却不通

  1. 检查本机路由是否有问题
1
ip route
  1. 确保宣告路由的节点没有冲突

Serving (Primary) 为生效的路由。

1
docker exec -it headscale headscale nodes list-routes
  1. 检查子网节点上的 ip 转发
1
2
3
4
5
6
7
# 查看转发是否开启 (1 为开启)
sysctl net.ipv4.ip_forward

# 如果是 0,临时开启:
sysctl -w net.ipv4.ip_forward=1

# 永久开启:在 /etc/sysctl.conf 中添加或修改 net.ipv4.ip_forward=1,然后执行 sysctl -p
  1. 检查子网路由器的防火墙

这是最常见的原因。即使内核开启了转发,防火墙也可能拦截了流量。

执行以下命令以允许 Tailscale 流量(

1
2
3
4
5
6
7
# 假设接口名为 tailscale0
iptables -A FORWARD -i tailscale0 -j ACCEPT
iptables -A FORWARD -o tailscale0 -j ACCEPT

# 开启 NAT(非常关键:如果目标机器不知道把包还给 Tailscale 节点,必须做此设置)
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# 注意:将 eth0 替换为 mydev 连接 172.21.9.0 网络的真实物理网卡名