SSD Nodes Learn 8GB 記憶體 — 每年 $66
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-01

Headscale 自建 Tailscale 控制伺服器

在 VPS 上自建 Tailscale 控制伺服器。從官方 .deb 安裝 headscale,啟動前先設定 server_url,再加入第一個節點。本文以 Ubuntu 24.04 與 headscale 0.29.3 為例。

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

headscale 的用途

Headscale 是 Tailscale 控制伺服器的自託管實作。因此,協調您私人網路的機器是您擁有的 VPS。這是社群專案,不由 Tailscale Inc. 維護。每台機器仍執行官方 tailscale 用戶端,並透過一個旗標 --login-server 指向您的伺服器。

控制伺服器負責管理網路成員。它會從 100.64.0.0/10 指派位址給每個節點、散發公開金鑰,並告知節點彼此的位置。通道仍使用 WireGuard,並在節點之間建立。兩台機器之間的網路流量不會經過 headscale 伺服器,除非無法建立直接路徑,節點才會改用中繼。

每個 headscale 執行個體提供一個 tailnet(即一個 Tailscale 網路)。專案將其定位為適合個人使用或小型組織。若只有三或四台機器,在您擁有的 VPS 上使用一般 WireGuard VPN 需要執行的軟體較少,也較少故障點。當您不想再為每台新筆記型電腦手動撰寫 [Peer] 區塊時,Headscale 才能發揮效益。兩種模型的完整比較,請參閱 WireGuard 與 Tailscale 的差異

安裝前的必要條件

  • 執行 Ubuntu 24.04 的 VPS,具備公開 IPv4 位址和 sudo 存取權限。如果伺服器是新建的,請先完成 新 VPS 的前 10 分鐘
  • 指向該位址的 DNS A 記錄。本指南使用 headscale.example.com
  • 用於 MagicDNS 的第二個網域或子網域。本指南使用 tailnet.example.net。它不得與 server_url 中的網域相同。
  • 一台要加入的用戶端機器,作業系統可為 Linux、macOS、Windows、Android 或 iOS。

從官方 .deb 安裝 headscale

此專案會在 GitHub releases 頁面發布 .deb 套件。截至 2026 年 7 月,目前版本為 0.29.3。請先檢查架構,因為檔案名稱會包含架構資訊。

sudo apt update
sudo apt install -y wget
dpkg --print-architecture

在一般 x86 VPS 上,此命令會輸出 amd64;在 Ampere 或 Graviton 類型的方案上,則會輸出 arm64。請將結果放入下方的變數。

HEADSCALE_VERSION="0.29.3"
HEADSCALE_ARCH="amd64"
wget --output-document=headscale.deb \\
  "https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_${HEADSCALE_ARCH}.deb"
sudo apt install -y ./headscale.deb
headscale version

檔案名稱前的 ./ 為必要項目。若省略此項目,apt 會在套件儲存庫中尋找名為 headscale.deb 的套件,並因此失敗。

此套件會建立 headscale 系統使用者、寫入預設的 /etc/headscale/config.yaml,並安裝 systemd 單元。它不會啟動服務,這也是正確的順序。隨套件提供的設定會將 server_url 指向 http://127.0.0.1:8080。這不是任何用戶端都能連線的位址,因此此時啟動服務會導致設定錯誤,即使服務成功啟動也一樣。在此時執行 sudo systemctl is-active headscale 會輸出 inactive。這是預期結果,不是故障。

啟動服務前設定 server_url

使用 sudo nano /etc/headscale/config.yaml 編輯 /etc/headscale/config.yaml,或使用 sed 套用相同的 3 項變更。請保留原始檔案副本,因為該檔案內容很長且包含大量註解,是後續設定最重要的參考資料。

sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.orig
sudo sed -i 's|^server_url:.*|server_url: https://headscale.example.com|' /etc/headscale/config.yaml
sudo sed -i 's|^listen_addr:.*|listen_addr: 127.0.0.1:8080|' /etc/headscale/config.yaml
sudo sed -i 's|^  base_domain:.*|  base_domain: tailnet.example.net|' /etc/headscale/config.yaml
sudo grep -E '^(server_url|listen_addr):|^  base_domain:' /etc/headscale/config.yaml

server_url 是 headscale 寫入每個用戶端註冊資料的位址。之後用戶端會永遠連線至這個確切字串,因此必須使用包含 https:// 的公開名稱,絕不能使用 127.0.0.1

listen_addr 是程序繫結的位址。請維持在 loopback。相同主機上的反向代理會終止 TLS(傳輸層安全性)並將要求轉送至此處,因此伺服器外部不需要存取 port 8080。

base_domain 是 MagicDNS 後綴,也就是節點取得名稱所使用的網域。它必須是沒有結尾句點的完整網域名稱,且必須與 server_url 中的網域不同,否則兩個名稱空間會發生衝突。

請勿修改資料庫區段。預設值是位於 /var/lib/headscale/db.sqlite 的 SQLite;該目錄由套件建立並擁有。對此規模的 tailnet 而言,SQLite 已經足夠。

啟動 headscale 並確認其正在執行

sudo systemctl enable --now headscale
sudo systemctl is-active headscale
curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:8080/health

is-active 會輸出 active,而 curl 會輸出 200enable --now 會完成這兩項工作:啟動服務,並設定服務在重新啟動後自動啟動。

如果 is-active 輸出 failed,請使用 sudo journalctl -u headscale -n 50 --no-pager 讀取日誌。此階段的失敗幾乎一律是設定檔造成的,因為 headscale 會在開啟 socket 前解析完整檔案。因此,錯誤的縮排或未知的 key 會在任何項目開始監聽前停止程序。修正檔案後,執行 sudo systemctl restart headscale。之後每次變更設定都需要相同的重新啟動操作。用戶端之後會自行重新連線。如果您不熟悉 systemd unit,使用 systemd 執行自己的服務與計時器說明了此處使用的命令。

在 shell 中檢查狀態檔案:

stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.key

兩行都以 headscale 開頭,這是套件建立的非特權使用者。noise_private.key 是伺服器向用戶端識別自身的身分。請保留此檔案。若刪除它,headscale 會產生新的身分,所有節點都必須重新註冊。

在 headscale 前方配置 TLS

用戶端必須透過 HTTPS 連線至 server_url。Caddy 是最簡便的方式,因為它會自行申請及續期憑證。

sudo apt install -y caddy

/etc/caddy/Caddyfile 替換為 headscale 文件中的區塊:

headscale.example.com {
    reverse_proxy 127.0.0.1:8080 {
        header_up True-Client-IP {remote_host}
        header_up X-Real-IP {remote_host}
    }
}
sudo caddy validate --adapter caddyfile --config /etc/caddy/Caddyfile
sudo systemctl restart caddy
sudo systemctl is-active caddy

檔案解析成功時,validate 會輸出 adapted config to JSON。即使顯示檔案尚未格式化的警告,也只是外觀問題。在您的筆記型電腦上,curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health 也應輸出 200。這項單一檢查即可確認 DNS、防火牆、憑證和 Proxy 已正常協同運作。

以下是容易讓人花費一晚排查的 Proxy 細節。Tailscale 控制連線使用 HTTP upgrade,透過 POST 而非 GET 啟動,且 Upgrade 標頭的值為 tailscale-control-protocol。Caddy 不需要額外設定即可轉送這些內容。nginx 則不會,因此使用 nginx 作為前端時需要設定 upgrade map:

map $http_upgrade $connection_upgrade {
    default keep-alive;
    ''      close;
}

server {
    listen 443 ssl;
    server_name headscale.example.com;
    location / {
        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_buffering off;
        proxy_pass http://127.0.0.1:8080;
    }
}

若省略這些行,一般請求仍會成功。因此 /health 會回傳 200,看起來一切正常,但長時間維持的控制連線永遠無法建立,您的節點會完成註冊後便保持離線。若選擇 nginx,在 Ubuntu 24.04 上使用 nginx 設定 Certbot 說明憑證相關步驟。

UFW 中應開放的連接埠

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

連接埠 443 承載所有用戶端通訊。連接埠 80 僅用於 ACME(自動憑證管理環境)的 HTTP challenge,以及重新導向至 HTTPS;Caddy 也需要它才能取得憑證。

連接埠 8080 維持關閉。listen_addr127.0.0.1:8080,因此 proxy 會透過 loopback 介面連線至 headscale,不涉及任何防火牆規則。將 8080 開放至網際網路會讓用戶端取得未加密的控制通道,沒有任何好處。請注意,大多數供應商會在控制面板中另外設定一層防火牆,與 UFW 分開;因此,連接埠可能在主機上開放,但在網路邊界仍然關閉。VPS 上的 UFW 防火牆基礎會更詳細說明規則語法。

建立使用者與預先授權金鑰

sudo headscale users create alice
sudo headscale users list

headscale 命令是用戶端。它會透過 /var/run/headscale/headscale.sock unix socket 與執行中的 daemon 通訊;該 socket 的模式為 0770,且由 headscale 群組擁有。由此可知兩點。服務停止時,命令會失敗;這也是本指南必須依此順序操作的另一個原因。此外,除非將您自己的帳戶加入 headscale 群組,否則需要 sudo

users list 會在每個名稱旁列印 ID。您需要這個數字,因為 key 命令接受數值使用者 ID,不接受名稱。

sudo headscale preauthkeys create --user 1 --expiration 24h

金鑰只會列印一次。請立即複製。預先授權金鑰只能使用一次,且有效期為 one hour,除非另行指定,因此在仍處於測試階段時設定 --expiration 24h 很有用。加入 --reusable 可建立能讓多部機器註冊的金鑰;請像管理密碼一樣保護該金鑰,因為持有它的任何人都能加入您的網路。

使用 --login-server 連線第一個用戶端

在要加入的機器上:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.example.com --auth-key 'hskey-auth-PASTE-YOUR-KEY-HERE'
tailscale status
tailscale ip -4

tailscale ip -4 會列印 headscale 指派的位址,例如 100.64.0.1。回到伺服器後,sudo headscale nodes list 會顯示該節點的 ID、使用者和連線狀態。

--login-server 的值必須與 server_url 完全相同,包括配置標頭,且結尾不得有斜線。系統會以字串比較兩者。若不相符,用戶端會先向一個位址註冊,接著被要求與另一個位址通訊。

先前登入過 Tailscale 託管服務的機器會保留該登入狀態。先在該機器上執行 sudo tailscale logout,再使用 --login-server 執行 tailscale up

如果省略 --auth-key,用戶端會改為列印 URL。開啟該 URL 後,頁面會顯示該次註冊嘗試的識別碼。接著在伺服器上核准:

sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGE

這種方式較適合自己的筆記型電腦。對於任何需要以指令碼處理的項目,預先授權金鑰更合適,因為不需要人員持續監看。

DERP,以及直接路徑失敗時轉送網路流量的方式

DERP(封包的指定加密中繼)是備援路徑。當兩個節點無法建立直接的 WireGuard 連線時,通常是因為兩者都位於嚴格的 NAT(網路位址轉譯)後方,便會改由中繼轉送封包。中繼不持有任何金鑰,因此無法讀取您的網路流量。但它可以看到哪些節點正在通訊,以及傳輸了多少資料。

請清楚了解預設設定的行為。Headscale 預設指向 https://controlplane.tailscale.com/derpmap/default,並使用 auto_update_enabled: trueupdate_frequency: 3h,因此控制平面由您管理,但中繼由 Tailscale 管理。對大多數人而言,這是合理的取捨。如果您不接受,請自行執行中繼。

若要執行自己的中繼,請在 config.yamlderp.server 下設定 enabled: true,重新啟動 headscale,並使用 sudo ufw allow 3478/udp 開放 STUN(NAT 的工作階段穿越工具)連接埠。設定檔明確指出此項要求:server_url 必須使用 https,因為 DERP 需要 TLS。清空 derp.urls 清單會從對映中移除 Tailscale 的中繼。如果在沒有可運作的內嵌中繼時執行此操作,任何無法直接連線的節點配對都將完全無法連線。

在用戶端上,tailscale netcheck 會顯示其已知各中繼區域的延遲,而 tailscale status 會將每個對等節點標示為帶有位址的 direct,或帶有區域代碼的 relay。停留在 relay 的對等節點表示 NAT 問題,而不是 headscale 問題。

為什麼節點顯示為離線?

Proxy 捨棄了 upgrade。 這是最常見的原因,其特徵是其他項目都正常:/health 回傳 200,headscale nodes list 顯示該節點,但節點始終無法上線。控制連線是攜帶 Upgrade: tailscale-control-protocol 的 POST,而未轉送該請求的 proxy 會中斷唯一能回報節點狀態的通道。請將 nginx 設定與上方的 map 區塊比對,或改用 Caddy 以排除 proxy 問題。

節點註冊後,server_url 已變更。 節點會持續連線到註冊時取得的值。如果您編輯了該值,請在每個節點上執行 sudo tailscale up --login-server https://headscale.example.com --force-reauth

Client 未執行。 在節點上執行 sudo systemctl is-active tailscaledsudo journalctl -u tailscaled -n 50 --no-pager。無法解析或連線至您網域的 client,會在該處記錄重試資訊。

Key 已過期。 下一節會說明此問題。

測試時,請在 VPS 上執行 sudo journalctl -u headscale -f 以監看伺服器端,並在 client 上重新啟動 tailscaled。連線至 headscale 的節點會立即產生日誌行。若沒有任何輸出,表示請求未抵達,因此請先檢查 DNS、防火牆和 proxy,再檢查 headscale。

金鑰到期,以及數週後停止運作的節點

存在兩種不同的到期設定。混淆兩者會浪費時間。

Preauth 金鑰依設計會快速到期。預設值為一小時且只能使用一次。如果 tailscale up 拒絕該金鑰,請在伺服器上產生新的金鑰,不要在用戶端編輯任何內容。

節點金鑰的有效期間較長。config.yamlnode 區段會設定 expiry: 0,而 0 表示沒有預設到期時間:已註冊的節點會持續有效,直到您將其設為到期。已加上標籤的節點永遠不會到期。若要讓註冊隨時間失效,請設定 expiry: 180d,並了解這項設定的影響:之後每個未加上標籤的節點都需要依該週期執行 sudo tailscale up --login-server https://headscale.example.com --force-reauth,而沒有人重新驗證的無頭伺服器會自行離開網路。

如果有人遺失筆記型電腦,請手動處理。sudo headscale nodes list 會提供該節點的 ID,接著 sudo headscale nodes expire -i 3 會將該節點登出,sudo headscale nodes delete -i 3 則會將其從網路中完全移除。

備份與升級

/var/lib/headscale/etc/headscale 共同構成完整的伺服器。複製前請先停止服務,因為 SQLite 可能仍有寫入作業,而在負載期間複製資料庫可能導致資料不一致。

sudo systemctl stop headscale
sudo tar czf /root/headscale-state.tgz -C /var/lib headscale
sudo tar czf /root/headscale-config.tgz -C /etc headscale
sudo systemctl start headscale
sudo chmod 600 /root/headscale-*.tgz

請將這兩個檔案移出該主機。檔案包含私密金鑰和所有註冊資料,因此應比照伺服器本身妥善保護。來自 VPS 的 restic 備份說明如何排程執行加密備份。

升級程序與安裝程序相同:下載新的 .debsudo apt install ./headscale.deb,然後重新啟動,再次執行 is-active/health 檢查。自 0.29 起,升級路徑受到嚴格限制。系統會阻止略過次要版本,也會阻止降級至較舊的次要版本。請一次升級一個次要版本,並在每個步驟前先備份;此外,請先閱讀該版本的發行說明,因為該版本同時變更了 ACL 原則行為,並移動了數個組態金鑰。

FAQ

為什麼安裝 .deb 後,headscale 會立即啟動失敗?

套件會安裝 unit,但會讓服務保持停止狀態,而且預設的 /etc/headscale/config.yaml 是範本,不是可直接使用的設定。請先編輯 server_urllisten_addrbase_domain,再執行 sudo systemctl enable --now headscale,並使用 sudo systemctl is-active headscale 確認。如果仍然失敗,sudo journalctl -u headscale -n 50 --no-pager 會指出問題所在。在這個階段,問題幾乎總是 YAML 錯誤,因為 headscale 會先解析整個檔案,之後才繫結連接埠。

我仍然需要在各台機器上安裝一般的 Tailscale 用戶端嗎?

需要。Headscale 只取代控制伺服器。每個節點都執行 Tailscale 提供的官方用戶端,並使用 sudo tailscale up --login-server https://headscale.example.com 將其指向您的伺服器。該旗標存在於標準用戶端中,因此不需要修補或重新建置。

我的網路流量會經過 headscale 伺服器嗎?

通常不會。Headscale 負責協調網路並分發金鑰與位址,而資料路徑則是節點之間直接透過 WireGuard 建立。只有在兩個節點無法直接連線,並改用 DERP 中繼時,流量才會繞行;使用隨附設定時,這些中繼是 Tailscale 的公開中繼。請在節點上執行 tailscale status,以查看指定對等節點是否為 direct,或位於 relay

為什麼我的節點完成註冊後仍然保持離線?

如果節點出現在 headscale nodes list 中,卻始終不上線,通常是因為它在反向 Proxy 失去了控制連線。該連線是以 POST 傳送、且包含 Upgrade: tailscale-control-protocol 標頭的 HTTP upgrade;除非加入 map $http_upgrade $connection_upgrade 區塊及相符的 proxy_set_header 行,否則 nginx 會將其捨棄。Caddy 不需額外設定即可轉送該連線,因此可快速用來測試問題是否出在 Proxy。

我需要為 headscale 設定網域名稱和 TLS 嗎?

實務上需要。用戶端會連線至您在 server_url 中設定的字串;憑證是針對名稱簽發,而不是針對裸 IP 位址簽發;設定檔也指出 DERP 需要 TLS。網域加上 Caddy 約需五分鐘即可完成,並能提供會自動續期的 HTTPS 端點。若透過純 HTTP 執行控制伺服器,所有用戶端與其之間的通訊都會以明文穿越網際網路。