背景

我家只有动态公网IP,并且做了DDNS,家里云虚拟出一台服务器(Ubuntu)部署了L2TP服务,所以在此之前,我一直通过L2TP/IPSec的方式回家,以便于我访问家里的资源。上述方式固然可行,但是慢慢显出弊端:时而链接不上、流量全回家绕一圈,不能灵活调整,速度受宽带上行限制等问题。于是推翻之前的建设,重新部署基于Headscale的异地组网服务。

准备

Headscale可以部署在有公网IP的VPS上,也可以部署在有动态公网IP的家宽上,但是不建议部署在没有公网IP的服务器上,本文采用部署在有动态公网IP的家宽中的服务器上。

本文Headscale的拓扑:

控制链路:外网设备(HTTPS) -> 家里公网IP:12345 -> 路由转发 -> Ubuntu Nginx 12345 (解密) -> Ubuntu Headscale 8080 (HTTP)
数据链路:外网设备 -> 建立P2P或DERP中转 -> Ubuntu (Tailscale Node) -> OpenWrt旁路由 -> 互联网

正文

第一步:主路由端口映射 (Port Forwarding)

在你的主路由(10.0.0.1)后台进行设置:

外部端口:12345

内部IP:10.0.0.99 (Ubuntu服务器)

内部端口:12345

协议:TCP (如果Tailscale流量大,建议同时开启UDP映射,虽然Headscale控制流主要是TCP,但为了打洞顺畅,建议TCP+UDP)。

第二步:申请 SSL 证书 (适配 Nginx)

使用 acme.sh 申请证书,并配置它在证书更新后自动重载 Nginx

1.安装 acme.sh:

curl https://get.acme.sh | sh
source ~/.bashrc

2.使用 DNS API 申请证书 (以 Cloudflare 为例,其他服务商类似):

export CF_Key="你的API Key"
export CF_Email="你的邮箱"
~/.acme.sh/acme.sh --issue --dns dns_cf -d home.example.com

3.安装证书到 Nginx 目录:

先创建存放目录:

mkdir -p /etc/nginx/ssl

执行安装命令(注意:这里的 reloadcmd 是关键,确保证书自动更新后 Nginx 会重载):

~/.acme.sh/acme.sh --install-cert -d home.example.com \
--key-file       /etc/nginx/ssl/key.pem  \
--fullchain-file /etc/nginx/ssl/cert.pem \
--reloadcmd     "systemctl reload nginx"

第三步:安装并配置 Nginx

1.安装 Nginx:

apt update
apt install nginx -y

2.创建 Headscale 的 Nginx 配置:

创建文件 /etc/nginx/sites-available/headscale

# 定义上游 Headscale 地址(Websocket 需要)
map $http_upgrade $connection_upgrade {
    default      keep-alive;
    'websocket'  upgrade;
    ''           close;
}

server {
    # 监听 12345 端口,开启 SSL
    listen 12345 ssl;
    http2 on;
    server_name home.example.com;

    # SSL 证书路径
    ssl_certificate     /etc/nginx/ssl/cert.pem;
    ssl_certificate_key /etc/nginx/ssl/key.pem;

    # SSL 优化配置(可选,增强安全性)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        # 代理到本地 Headscale
        proxy_pass http://127.0.0.1:8080;

        proxy_http_version 1.1;
        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-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }
}

3.启用配置并验证:

# 建立软链接启用站点
ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/

# 删除默认配置(如果占用了端口或导致冲突)
rm /etc/nginx/sites-enabled/default

# 检查语法是否正确
nginx -t

如果看到 syntax is ok 和 test is successful,继续下一步。

4.重启 Nginx:

systemctl restart nginx

第四步:验证

ss -tulnp | grep 55555
# 应该看到 nginx 在监听,而不是 headscale

第五步:部署与配置 Headscale (Ubuntu)

1.下载并安装:

wget --output-document=/usr/local/bin/headscale \
https://github.com/juanfont/headscale/releases/download/v0.28.0/headscale_0.28.0_linux_amd64

chmod +x /usr/local/bin/headscale

2.配置 Headscale:

mkdir -p /etc/headscale
mkdir -p /var/lib/headscale
touch /var/lib/headscale/db.sqlite
nano /etc/headscale/config.yaml

config.yaml

# 外部访问地址
server_url: https://home.example.com:12345

# 监听地址 (配合 Nginx,只监听本地)
listen_addr: 127.0.0.1:8080

# 监控与GRPC地址 (保持默认即可)
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false

# 噪声协议私钥
noise:
  private_key_path: /var/lib/headscale/noise_private.key

# IP 前缀
prefixes:
  v4: 100.64.0.0/10
  v6: fd7a:115c:a1e0::/48

# 数据库配置
database:
  type: sqlite3
  sqlite:
    path: /var/lib/headscale/db.sqlite
    write_ahead_log: true

# 日志配置
log:
  level: info
  format: text

# DNS 配置 (新版格式)
dns:
  magic_dns: true
  base_domain: example.com
  nameservers:
    global:
      - 10.0.0.254  # DNS Server
      - 223.5.5.5
  search_domains: []
  extra_records: []

# Unix Socket
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"

# OIDC 登录
oidc:
  issuer: ""
  client_id: ""
  client_secret: ""

# 必须禁用内置 TLS
tls_letsencrypt_hostname: ""
tls_client_auth_mode: disabled
tls_cert_path: ""
tls_key_path: ""

derp:
  server:
    enabled: false

  urls:
    - https://controlplane.tailscale.com/derpmap/default

  paths: []

  auto_update_enabled: true
  update_frequency: 24h

3.创建 Systemd 服务:

vim /etc/systemd/system/headscale.service

[Unit]
Description=headscale controller
After=network.target

[Service]
WorkingDirectory=/etc/headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=3
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

4.启动服务:

systemctl enable headscale
systemctl start headscale
systemctl status headscale
# 验证端口监听
ss -tulnp | grep 12345

5.创建用户:

headscale users create myhome

第六步:配置 Ubuntu 为出口节点 (Exit Node)

现在 Headscale 已经运行,我们需要在同一台机器 (10.0.0.99) 上安装 Tailscale 客户端,并让它把自己变成一个路由器。

1.开启内核转发 (IP Forwarding):

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

2.安装 Tailscale:

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

3.接入 Headscale 并广播路由:

这是最关键的一步。需要告诉 Headscale:这台机器是一个出口节点,并且可以访问 10.0.0.0/24 网段。

tailscale up \
  --login-server=https://home.example.com:12345 \
  --advertise-routes=10.0.0.0/24 \
  --advertise-exit-node \
  --accept-routes

此时终端会显示一个认证链接,访问链接,根据内容在服务器中批准。

4.在 Headscale 端查看已批准机器与路由:

headscale nodes list

查看可用路由列表

headscale nodes list-routes

开启可用路由

headscale nodes approve-routes -i 1 -r "0.0.0.0/0"
headscale nodes approve-routes -i 1 -r "::/0"

当可用路由列表中Approved中有刚刚添加的,说明已生效

第七步:客户端连接

  • iOS/Android:
    在 App 的设置里找到 Use Alternate ServerLogin URL,填入: https://home.example.com:12345
  • Windows:
    管理员权限运行 CMD
    tailscale login --login-server https://home.scxu.cc:55555
  • macOS:
    defaults write io.tailscale.ipn.macsys ControlURL https://home.example.com:12345