给 Headscale 配个 Web 界面:headscale-ui 安装折腾记

J
Joy
2026年06月16日 · 2 分钟阅读

Headscale 原生只有 CLI,每次加节点、批用户、看路由都得 SSH 敲命令。headscale-ui 是个纯静态 Web 前端,调 Headscale API 把这些操作搬到浏览器里。这篇记录它的安装、反代配置和登录踩坑。

系列:Tailscale 折腾 2 / 4
  1. 1 Tailscale 是什么:一篇看懂这套 P2P 内网穿透神器(折腾系列·序章)
  2. 2 给 Headscale 配个 Web 界面:headscale-ui 安装折腾记 当前
  3. 3 自建 DERP 中继:让 Headscale 打洞失败也能稳连国内 20ms
  4. 4 把设备接进自建 Headscale:从 tailscale up 登录到 headscale-ui 管理

折腾背景:序章里我把 Tailscale / 自建 Headscale 的原理讲过了,假设你已经有一套 Headscale 跑起来。但 Headscale 原生只有命令行——加节点、批准设备、建用户、配子网路由,全得 SSH 上去敲 headscale 命令。设备一多就烦。headscale-ui 是社区做的一个纯静态 Web 前端,直接调 Headscale 的 API,把这些操作搬进浏览器。这篇记一下它的安装和两个反代/登录的坑。

它是怎么工作的

headscale-ui 本身没有后端,就是一堆静态 HTML/JS。它在你浏览器里,拿着你填的 API Key 直接去敲 Headscale 的 HTTP API。所以部署的核心其实是两件事:

  1. 把这堆静态文件托管起来(Docker 或塞进 nginx 目录)。
  2. 反向代理把"静态 UI"和"Headscale API"放到同一个域名下,避免浏览器跨域(CORS)。
flowchart LR
    U["浏览器
headscale-ui"] P["反向代理
nginx / Caddy"] UI["静态文件
/web"] API["Headscale API
:8080"] U -->|"访问 /web"| P P -->|"① 静态页面"| UI U -->|"带 API Key 调接口"| P P -->|"② 转发 API"| API style P fill:#dbe9ff,stroke:#2563eb,stroke-width:2px

全流程总览

flowchart TD
    A["① 跑起 headscale-ui
Docker / 静态文件"] --> B["② 生成 API Key
headscale apikeys create"] B --> C["③ 反代:同域暴露
/web + API"] C --> D["④ 浏览器开 /web
填 URL + API Key"] D --> E["⑤ 验证:能看到节点列表"] style C fill:#ffe9d5,stroke:#b71d18

标橙的第③步(反代同域)是最容易卡住的——跨域和 HTTPS 没配对,UI 会一直转圈连不上 API。

前置

  • 一台已经在跑的 Headscale(本文假设它监听在本机 :8080,对外域名 vpn.e7coding.com)。
  • 一个能签证书的反代(nginx 或 Caddy),UI 强烈建议走 HTTPS。

第 1 步:把 headscale-ui 跑起来

两种方式选一种。

方式 A:Docker(推荐,省事)

docker run -d \
  --name headscale-ui \
  --restart unless-stopped \
  -p 9443:443 \
  ghcr.io/gurucomputing/headscale-ui:latest

容器里就是个装好静态文件的小 web server,监听 443(映射到宿主 9443)。后面反代把 /web 指过来即可。

方式 B:静态文件 + nginx(想完全自己掌控)

Releases 下最新的发布包,解压到 nginx 的站点目录:

mkdir -p /var/www/headscale-ui
unzip headscale-ui.zip -d /var/www/headscale-ui

然后让 nginx 把 /web 映射到这个目录(见下一步的配置)。

第 2 步:生成 API Key

headscale-ui 靠 API Key 鉴权,在 Headscale 服务器上生成一个:

# 创建一个有效期较长的 key(按需调整)
headscale apikeys create --expiration 999d
# 输出形如:
# xxxxxxxx.yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy

把这串 key 完整复制下来,等会儿要填进 UI。它只在创建时完整显示一次。

# 顺手可以列出 / 过期废弃旧 key
headscale apikeys list

第 3 步:反向代理,同域暴露 UI 和 API(关键)

核心是把 /web(UI)和 Headscale 的 API 路径放在同一个域名下。下面是 nginx 的示意配置(用方式 A 的容器为例):

server {
    listen 443 ssl;
    server_name vpn.e7coding.com;

    ssl_certificate     /etc/nginx/certs/vpn.e7coding.com.crt;
    ssl_certificate_key /etc/nginx/certs/vpn.e7coding.com.key;

    # ① Web 界面:转发到 headscale-ui 容器
    location /web {
        proxy_pass https://127.0.0.1:9443;
        proxy_set_header Host $host;
    }

    # ② Headscale API / 控制面:转发到 headscale
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

用方式 B(静态文件)的话,把 location /web 换成 alias /var/www/headscale-ui; 直接由 nginx 出静态文件即可,其余不变。重载:nginx -t && systemctl reload nginx

第 4 步:打开 UI,填入配置

浏览器访问:

https://vpn.e7coding.com/web/

进设置页,填两样东西:

  • Headscale URL / API URLhttps://vpn.e7coding.com(你的对外域名,不要带 /web
  • API Key:第 2 步生成的那串

保存后,正常就能看到节点(devices)列表了。我自己最终落在 https://vpn.e7coding.com/web/devices.html 这个页面管设备。

第 5 步:验证

能看到节点列表、能批准/删除设备、能建用户,就算通了。常用的几件事现在都能点鼠标搞定:

  • 批准新设备入网(配合 DERP 那篇里 tailscale up 后生成的 register 链接)
  • 建 / 删用户
  • 给节点配子网路由、打 tag
  • 管理、废弃 API Key

踩坑小结

  • 跨域(CORS):UI 和 API 必须同域。如果 UI 在 a.com、API 在 b.com,浏览器会拦截请求,UI 一直连不上。用反代把它们收到一个域名下是最稳的做法。
  • HTTPS 几乎是必须:很多浏览器对混合内容(HTTPS 页面请求 HTTP 接口)会直接拦。UI 走 https,API 也走 https,省一堆事。
  • API Key 只显示一次:创建时没存下来,就只能废弃重建。
  • URL 别填成 /web:设置里填的是 Headscale 的根域名,不是 UI 自己的 /web 路径,填错就一直 401 / 连接失败。
  • API Key 会过期--expiration 到期后 UI 突然全部失效、报鉴权错,记得提前 headscale apikeys create 续一个再去 UI 里换上。

有了 Web 界面,下一篇就能在 headscale-ui 里点几下,把自建 DERP 节点批准入网了 👉 自建 DERP 中继

分享

评论

相关文章

3 分钟阅读
自建 DERP 中继:让 Headscale 打洞失败也能稳连国内 20ms

Tailscale/Headscale 靠 WireGuard P2P 直连,但很多 NAT 环境打洞会失败、流量被迫走官方境外 DERP,延迟动辄 100ms+。这篇记录我在自建 Headscale 上加一台国内自建 DERP 的全过程,含改源码、自签证书、防白嫖三处踩坑。

文章 折腾