Headscale 组网教程(域名版)

headscale 组网教程(域名版)

说明:此教程以域名方式部署 Headscale + DERP,并使用 Cloudflare DNS(不要开启小黄云)。
假设服务器 IP 为 1.2.3.4,域名为 abc.xyz,示例配置与命令已包含在下文,请按需替换域名、端口与邮箱等信息。


第一步:域名解析(Cloudflare)

在 Cloudflare 中将域名解析到服务器 IP(不要启用代理/小黄云):

1
2
derp.abc.xyz  -> 1.2.3.4
head.abc.xyz -> 1.2.3.4

第二步:设置防火墙规则

建议放行必要端口(可以选择放行所有端口或只放行需要的端口):

1
2
3
4
5
sudo apt update
sudo apt install ufw -y
sudo ufw allow 22,80,443,2222,3333/tcp
sudo ufw enable
sudo ufw status

第三步:申请证书(Let’s Encrypt + Cloudflare DNS 插件)

  1. 安装 Certbot 与 Cloudflare 插件:
1
2
3
sudo apt update && sudo apt upgrade -y
sudo apt install -y certbot python3-certbot-dns-cloudflare
sudo apt install micro nano
  1. 创建并配置 Cloudflare API 令牌文件:
1
2
sudo mkdir -p /etc/cloudflare
sudo nano /etc/cloudflare/cloudflare.ini

cloudflare.ini 中写入(替换为自己的 Token):

1
dns_cloudflare_api_token = "YOUR_CLOUDFLARE_API_TOKEN"

Cloudflare Token 设置建议:

  • 权限:Zone → DNS → Edit
  • Zone Resources:Include → Specific zone → 选域名 abc.xyz

设置文件权限:

1
sudo chmod 600 /etc/cloudflare/cloudflare.ini
  1. 为两个域名分别申请证书:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# DERP 域名证书
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/cloudflare/cloudflare.ini \
-d derp.abc.xyz \
--agree-tos \
--email [email protected] \
-n

# Headscale 域名证书
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/cloudflare/cloudflare.ini \
-d head.abc.xyz \
--agree-tos \
--email [email protected] \
-n

检查证书路径:

1
2
sudo ls -l /etc/letsencrypt/live/head.abc.xyz/
sudo ls -l /etc/letsencrypt/live/derp.abc.xyz/

证书文件位置示例:

1
2
3
4
Certificate is saved at: /etc/letsencrypt/live/derp.abc.xyz/fullchain.pem
Key is saved at: /etc/letsencrypt/live/derp.abc.xyz/privkey.pem
Certificate is saved at: /etc/letsencrypt/live/head.abc.xyz/fullchain.pem
Key is saved at: /etc/letsencrypt/live/head.abc.xyz/privkey.pem

第四步:搭建 DERP 服务器

以 root 身份创建并运行安装脚本 tailscale.sh。脚本内容如下(保存为 tailscale.sh 并赋可执行权限):

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/bin/bash
set -euo pipefail

# ===== 输入 =====
read -p "请输入 DERP 域名(例如 derp.example.com): " DERP_DOMAIN
read -p "请输入 DERP 监听端口(默认443): " DERP_PORT
DERP_PORT=${DERP_PORT:-443}
read -p "请输入 HTTP 端口(填0关闭HTTP,可输入0或:0): " HTTP_PORT
HTTP_PORT="${HTTP_PORT#:}" # 防止误填 :0,规范化为 0

# ===== 关闭防火墙(先关后面部署完成后再开启)=====
disable_firewall() {
if command -v ufw >/dev/null 2>&1; then
ufw disable || true; echo "ufw 已关闭"
fi
if command -v firewall-cmd >/dev/null 2>&1; then
systemctl stop firewalld || true
systemctl disable firewalld || true
echo "firewalld 已关闭"
fi
if command -v iptables >/dev/null 2>&1; then
iptables -F || true; iptables -X || true
iptables -t nat -F || true; iptables -t nat -X || true
iptables -t mangle -F || true; iptables -t mangle -X || true
iptables -t raw -F || true; iptables -t raw -X || true
echo "iptables 规则已清除"
fi
}
disable_firewall

echo "== 安装依赖"
apt update && apt upgrade -y
apt install -y wget git openssl curl tar nginx

echo "== 安装 Go 并构建 derper(二进制放到 /etc/derp/derper)"
GO_VERSION="go1.25.4"
rm -rf /usr/local/go
wget -O /tmp/${GO_VERSION}.linux-amd64.tar.gz "https://go.dev/dl/${GO_VERSION}.linux-amd64.tar.gz"
tar -C /usr/local -xzf /tmp/${GO_VERSION}.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
grep -q '/usr/local/go/bin' /etc/profile || echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile
# 不 source /etc/profile,避免 debuginfod 未定义变量中断

go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct || true

mkdir -p /etc/derp
# 优先 go install,失败则切 direct 再试
go install tailscale.com/cmd/derper@latest || { go env -w GOPROXY=direct; go install tailscale.com/cmd/derper@latest; }
install -m 0755 "$(go env GOPATH)"/bin/derper /etc/derp/derper

echo "== 使用证书(软链到 /etc/derp)"
ln -sf /etc/letsencrypt/live/${DERP_DOMAIN}/fullchain.pem /etc/derp/${DERP_DOMAIN}.crt
ln -sf /etc/letsencrypt/live/${DERP_DOMAIN}/privkey.pem /etc/derp/${DERP_DOMAIN}.key
ls -l /etc/derp/${DERP_DOMAIN}.crt /etc/derp/${DERP_DOMAIN}.key

echo "== 创建并启动 derper 的 systemd 服务"
cat > /etc/systemd/system/derp.service <<EOF
[Unit]
Description=TS Derper
After=network.target
Wants=network.target

[Service]
User=root
Restart=always
ExecStart=/etc/derp/derper -hostname ${DERP_DOMAIN} -a :${DERP_PORT} -http-port ${HTTP_PORT} -certmode manual -certdir /etc/derp
RestartPreventExitStatus=1

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable derp
systemctl restart derp
sleep 1
systemctl status derp --no-pager || true

echo "== 安装 tailscale 客户端(带 DNS 兜底与 apt 源回退)"
# DNS 兜底:若 pkgs.tailscale.com 解析失败则改 DNS
if ! getent hosts pkgs.tailscale.com >/dev/null; then
IFACE=$(ip route get 1.1.1.1 | awk '{print $5; exit}')
if command -v resolvectl >/dev/null 2>&1; then
resolvectl dns "$IFACE" 1.1.1.1 8.8.8.8 || true
resolvectl domain "$IFACE" '~.' || true
resolvectl flush-caches || true
else
chattr -i /etc/resolv.conf 2>/dev/null || true
printf "nameserver 1.1.1.1\nnameserver 8.8.8.8\n" > /etc/resolv.conf
fi
fi

if ! command -v tailscale >/dev/null 2>&1; then
if ! curl -fsSL https://tailscale.com/install.sh | sh; then
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg \
| tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list \
| tee /etc/apt/sources.list.d/tailscale.list >/dev/null
apt update
apt install -y tailscale
fi
systemctl enable --now tailscaled || true
fi

echo "== 快速自检(端口与证书)"
echo "- 监听:"
ss -ltnup | egrep ":${DERP_PORT}\b" || true
ss -lunp | grep ':3478' || true
echo "- 证书校验(应为 Verification: OK):"
echo | openssl s_client -connect ${DERP_DOMAIN}:${DERP_PORT} -servername ${DERP_DOMAIN} -brief 2>/dev/null | egrep 'Verification|Protocol|Cipher' || true

echo
echo "✅ DERP 已使用 Let's Encrypt 证书运行:${DERP_DOMAIN}:${DERP_PORT}(HTTP端口 ${HTTP_PORT},0=关闭)"
echo "✅ tailscale 客户端:$(tailscale version 2>/dev/null || echo 已安装)"
echo
echo "下一步(如需):"
echo " 1) 确认 Headscale 的 derp.json 指向 HostName=${DERP_DOMAIN}、DERPPort=${DERP_PORT}"
echo " 2) 客户端上执行:tailscale netcheck(应不再出现 self-signed/relay 900 错误)"
echo " 3) 将设备登录 Headscale:tailscale up --login-server=https://<你的 Headscale 域名>"

保存并赋予执行权限,然后运行:

1
chmod +x tailscale.sh && ./tailscale.sh

在执行时按提示输入 derp.abc.xyz、邮箱、DERP 端口(例如 2222)、HTTP 端口(例如 3333)。如遇 Nginx 错误可重启并检查配置后重试。


第五步:搭建 Headscale 与 Headscale-UI

以 root 身份创建 headscale.sh 并运行(脚本会下载 headscale .deb、配置、部署 nginx 反代及 headscale-ui):

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/bin/bash
set -euo pipefail
[ "$(id -u)" -eq 0 ] || { echo "请用 root 运行"; exit 1; }
export DEBIAN_FRONTEND=noninteractive

# ===== 输入 =====
read -p "请输入 Headscale 域名(例如 hs.example.com): " HS_DOMAIN
read -p "请输入 DERP 域名(例如 derp.example.com): " DERP_DOMAIN
read -p "请输入 v4 网段(必须在 100.64.0.0/10,例如 100.100.1.0/24): " IP_CIDR
read -p "DERP 服务端口(默认443): " DERP_PORT
DERP_PORT=${DERP_PORT:-443}

# 按官方反代示例:对外监听
BIND_IP="0.0.0.0"

# 基础校验(必须 CIDR,且位于 100.64.0.0/10)
[[ "$IP_CIDR" == */* ]] || { echo "网段必须是 CIDR 形式,例如 100.100.1.0/24"; exit 1; }
_base="${IP_CIDR%/*}"; o1=${_base%%.*}; rest=${_base#*.}; o2=${rest%%.*}
if ! [[ "$o1" == "100" && "$o2" =~ ^(6[4-9]|7[0-9]|8[0-9]|9[0-9]|10[0-9]|11[0-9]|12[0-7])$ ]]; then
echo "v4 网段必须落在 100.64.0.0/10;当前 ${IP_CIDR} 不合法"; exit 1;
fi

echo "== 安装依赖和相关软件"
apt update && apt -y install wget unzip curl jq nginx certbot python3-certbot-nginx ca-certificates

echo "== 下载并安装 Headscale (.deb) =="
# 固定下载地址(根据实际进行调整)
HS_DEB_URL="https://github.com/juanfont/headscale/releases/download/v0.27.1/headscale_0.27.1_linux_amd64.deb"
TMP_DEB="/tmp/headscale_0.27.1_linux_amd64.deb"

echo "下载:${HS_DEB_URL}"
wget -q -O "${TMP_DEB}" "${HS_DEB_URL}"
if [ ! -f "${TMP_DEB}" ]; then
echo "❌ headscale .deb 下载失败"
exit 1
fi

dpkg -i "${TMP_DEB}" || apt -f install -y
systemctl enable --now headscale || true

echo "== 修正运行用户与目录权限"
id -u headscale >/dev/null 2>&1 || useradd --system --home /var/lib/headscale --shell /usr/sbin/nologin headscale
mkdir -p /var/lib/headscale /etc/headscale /var/www/letsencrypt /var/www/web
chown -R headscale:headscale /var/lib/headscale /etc/headscale
chmod 750 /var/lib/headscale

echo "== 写入 Headscale 配置(v0.26 兼容 + 关闭 MagicDNS + 对外监听)"
CFG="/etc/headscale/config.yaml"
cp -a "$CFG" "${CFG}.bak.$(date +%s)" 2>/dev/null || true
cat >"$CFG" <<EOF
server_url: https://${HS_DOMAIN}

# 对外监听给 Nginx 反代(官方反代示例)
listen_addr: ${BIND_IP}:8080
http_listen_addr: ${BIND_IP}:8080
metrics_listen_addr: ${BIND_IP}:9090
# gRPC 仅本机(供 CLI 使用)
grpc_listen_addr: 127.0.0.1:50443
grpc_tls_disable: true
grpc_allow_insecure: true

# 由反向代理终止 TLS
tls_cert_path: ""
tls_key_path: ""

# Tailscale v2 协议所需
noise:
private_key_path: /var/lib/headscale/noise_private.key

# DNS:关闭覆盖与 MagicDNS,避免 base_domain 必填
dns:
override_local_dns: false
magic_dns: false

# v0.26 使用 prefixes(不要再用 ip_prefixes)
prefixes:
v4: ${IP_CIDR}
# 如需 IPv6:
# v6: fd7a:115c:a1e0::/48

# 新版数据库写法(修复“invalid database type”)
database:
type: sqlite
sqlite:
path: /var/lib/headscale/db.sqlite

# 私钥(WireGuard)
private_key_path: /var/lib/headscale/private.key

# 通过站点下发自定义 DERP
derp:
urls:
- https://${HS_DOMAIN}/derp.json
EOF
chown headscale:headscale "$CFG"

# —— 数据库清理:避免迁移冲突(你不保留数据,安全可用)——
systemctl stop headscale || true
rm -f /var/lib/headscale/db.sqlite /var/lib/headscale/db.sqlite-wal /var/lib/headscale/db.sqlite-shm
chown -R headscale:headscale /var/lib/headscale
systemctl start headscale || true

echo "== [4/9] 配置 Nginx 正式站点(WebSocket + 反代 8080)"
grep -q "include /etc/nginx/conf.d/*.conf" /etc/nginx/nginx.conf || \
sed -i '/http[[:space:]]*{/a\ include /etc/nginx/conf.d/*.conf;' /etc/nginx/nginx.conf

cat /etc/nginx/conf.d/websocket_upgrade_map.conf <<'EOF'
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
EOF

cat >/etc/nginx/sites-available/headscale.conf <<EOF
server {
listen 80; listen [::]:80;
server_name ${HS_DOMAIN};
return 301 https://\$host\$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${HS_DOMAIN};
ssl_certificate /etc/letsencrypt/live/${HS_DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${HS_DOMAIN}/privkey.pem;

location / {
proxy_pass http://${BIND_IP}: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 http:// 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 \$scheme;
add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always;
}

# derp.json 静态
location = /derp.json { types { application/json json; } alias /var/www/derp.json; }

# Headscale UI(/web)
location /web { index index.html; alias /var/www/web; }
}
EOF
ln -sf /etc/nginx/sites-available/headscale.conf /etc/nginx/sites-enabled/headscale.conf
nginx -t && systemctl reload nginx

echo "== 安装 Headscale UI(显示详细过程)"
# 固定下载地址(根据实际修改)
UI_ZIP_URL="https://github.com/gurucomputing/headscale-ui/releases/download/2025.08.23/headscale-ui.zip"
TMP_UI_ZIP="/tmp/headscale-ui.zip"

echo "下载:${UI_ZIP_URL}"
wget -q -O "${TMP_UI_ZIP}" "${UI_ZIP_URL}"
if [ ! -f "${TMP_UI_ZIP}" ]; then
echo "❌ headscale-ui.zip 下载失败"
exit 1
fi

# 解压到 /var/www/web (脚本会覆盖)
mkdir -p /var/www/web
unzip -o "${TMP_UI_ZIP}" -d /var/www/_hs_ui_tmp >/dev/null
# 找到 index.html 所在目录并复制内容
UI_INDEX=$(find /var/www/_hs_ui_tmp -maxdepth 3 -type f -name index.html -print -quit || true)
if [ -n "${UI_INDEX}" ]; then
UI_SRC_DIR=$(dirname "${UI_INDEX}")
rm -rf /var/www/web/*
cp -a "${UI_SRC_DIR}"/. /var/www/web/
chown -R www-data:www-data /var/www/web
rm -rf /var/www/_hs_ui_tmp
echo "Headscale UI 部署完成 -> /var/www/web"
else
echo "⚠️ 没在 zip 中找到 index.html,UI 可能未正确部署"
fi

echo "== 下发 derp.json & 重启服务并等待就绪"
cat >/var/www/derp.json <<EOF
{ "Regions": { "900": { "RegionID": 900, "RegionCode": "myderp",
"Nodes": [ { "Name": "a", "RegionID": 900, "DERPPort": ${DERP_PORT},
"HostName": "${DERP_DOMAIN}", "IPv4": "", "InsecureForTests": false } ] } } }
EOF
chown -R www-data:www-data /var/www || true
find /var/www -type d -exec chmod 755 {} \; || true
find /var/www -type f -exec chmod 644 {} \; || true

systemctl restart headscale
nginx -t && systemctl reload nginx

# 等待 8080 就绪(最多 15 秒)
for i in $(seq 1 30); do
if bash -c "exec 3<>/dev/tcp/${BIND_IP}/8080" 2>/dev/null; then exec 3>&- 3<&-; break; fi
sleep 0.5
done

# ===== 只输出三段 =====
echo "# 生成API密钥"
headscale apikeys create --expiration 9999d
echo "安装完成!请使用生成的API密钥和以下链接登陆Headscale-ui面板:"
echo "https://${HS_DOMAIN}/web"
echo "使用以下命令将设备连接到Headscale服务器:"
echo "tailscale up --login-server=https://${HS_DOMAIN}"

保存并运行:

1
chmod +x headscale.sh && ./headscale.sh

部署完成后可访问 Headscale UI:https://head.abc.xyz/web


第六步:访问 Headscale UI 并设置(示例)

  1. 打开 https://head.abc.xyz/web → 设置:

    • Headscale URL:https://head.abc.xyz
    • Headscale API Key:使用脚本运行后 headscale apikeys create --expiration 9999d 输出的密钥(粘贴在 UI 上并保存)

    提示:如果更换浏览器或电脑需要重新配置 API Key,可在服务器上重新执行 headscale apikeys create --expiration 9999d 获取新密钥。

  2. 左侧菜单 → 用户 → 添加一个用户 → 确定。

  3. 在服务器上执行(示例):

1
tailscale up --login-server=https://head.abc.xyz

执行后会生成一个注册密钥链接,例如:

1
https://head.abc.xyz/register/a_ec5fM_OiwjzZxMXZ--4qsk

在 UI 中:左侧设备视图 → 添加新设备 → 粘贴密钥 a_ec5fM_OiwjzZxMXZ--4qsk → 选择用户 → 确认。

如果提示强制登录,可使用:

1
sudo tailscale up --login-server=https://head.abc.xyz --force-reauth
  1. 其他设备安装:访问 https://head.abc.xyz/windows(或对应平台页面)下载客户端并按提示连接。

连接原理简述:设备执行 tailscale up --login-server=https://head.abc.xyz 会生成一个临时注册密钥,管理员在 Headscale UI 将该密钥与用户绑定,就能完成设备注册与授权。


第七步:其他常用操作与注意事项

  1. 重载 Derp 配置并重启:
1
2
systemctl daemon-reload
systemctl restart derp
  1. 重启 Headscale 与 Nginx:
1
2
systemctl restart headscale
systemctl restart nginx
  1. 部分 Tailscale 常用命令:
1
2
3
4
5
6
7
8
9
10
11
# 退出 Tailscale(登出)
tailscale logout

# 停止 Tailscale(下线)
tailscale down

# 检查 Derp 服务器连接情况
tailscale netcheck

# 查看 Tailscale 状态
tailscale status
  1. 添加子网路由(示例):
1
2
3
4
tailscale up --login-server=https://head.abc.xyz --accept-routes=true

# 或者在重置并同时广播路由时
tailscale up --login-server=https://head.abc.xyz --accept-routes=true --accept-dns=false --advertise-routes=192.168.2.0/24 --reset
  1. 如果以前安装过旧版 Tailscale/Headache(Windows),删除以前的注册表值:
1
2
3
# 删除 64 位分支里的两个值
reg delete "HKLM\Software\Tailscale IPN" /v LoginURL /f
reg delete "HKLM\Software\Tailscale IPN" /v UnattendedMode /f
  1. Windows 服务重启:
1
2
net stop Tailscale
net start Tailscale
  1. 证书续期(自动化建议):
1
2
3
4
5
6
7
8
9
10
# 续签测试
sudo certbot renew --dry-run

# 使用 systemd 定时器
sudo systemctl enable certbot.timer

# 或使用 cron
sudo apt update
sudo apt install cron -y
sudo crontab -e

在 crontab 中加入:

1
0 3 * * * certbot renew --quiet && systemctl reload nginx && systemctl restart derp && systemctl restart headscale

常见问题提示

  • Cloudflare 一定要关闭代理(小黄云),否则外网 TLS 终止与端口映射会被 Cloudflare 干扰。
  • 端口选择:若你使用非标准端口(非 443/80),请确保 Cloudflare 不代理并且防火墙放行对应端口。
  • 备份:部署前备份重要配置与数据库文件(如 /etc/headscale/config.yaml/var/lib/headscale/db.sqlite)。
  • 安全:将 Cloudflare Token、API Key、Headscale 私钥等敏感信息保存在受限权限的文件里(如 /etc/cloudflare/cloudflare.ini,权限 600)。
  • 日志排查journalctl -u headscale -fjournalctl -u derp -fnginx -tsystemctl status 是常见排错起点。


Headscale 组网教程(域名版)
http://example.com/2025/11/13/headscale安装教程/
Author
John Doe
Posted on
November 13, 2025
Licensed under