Headscale: власний сервер керування Tailscale
Розгорніть власний сервер керування Tailscale на VPS: встановіть headscale з офіційного .deb, задайте server_url до запуску та підключіть перший вузол.
Що таке headscale
Headscale — це реалізація сервера керування Tailscale, яку ви розгортаєте самостійно. Тому машиною, яка координує вашу приватну мережу, є VPS, що належить вам. Це проєкт спільноти, і ним не керує Tailscale Inc. На кожній машині все одно працює офіційний клієнт tailscale. Щоб указати ваш сервер, клієнту потрібен один прапорець: --login-server.
Сервер керування визначає, хто входить до мережі. Він призначає кожному вузлу адресу з діапазону 100.64.0.0/10, розповсюджує відкриті ключі та повідомляє вузлам, де знайти один одного. Тунелі залишаються тунелями WireGuard і створюються безпосередньо між вузлами. Трафік між двома вашими машинами не проходить через сервер headscale, якщо неможливо створити прямий шлях і вузли не переходять на ретранслятор.
Один екземпляр headscale обслуговує одну tailnet (одну мережу Tailscale). Проєкт описує таку конфігурацію як придатну для особистого використання або невеликої організації. Якщо у вас три або чотири машини, звичайна VPN на базі WireGuard на власному VPS потребує менше програмного забезпечення для обслуговування та створює менше можливостей для збоїв. Headscale стає корисним, коли ви більше не хочете вручну створювати блок [Peer] для кожного нового ноутбука. Розширене порівняння цих двох моделей наведено в матеріалі про відмінності між WireGuard і Tailscale.
Що потрібно підготувати перед встановленням
- VPS з Ubuntu 24.04, публічною IPv4-адресою та доступом через sudo. Якщо сервер новий, спочатку виконайте перші десять хвилин на новому VPS.
- DNS-запис A, що вказує на цю адресу. У цьому посібнику використовується
headscale.example.com. - Другий домен або піддомен для MagicDNS. У цьому посібнику використовується
tailnet.example.net. Він не має збігатися з доменом уserver_url. - Один клієнтський комп’ютер для підключення з Linux, macOS, Windows, Android або iOS.
Встановлення headscale з офіційного пакета .deb
Проєкт публікує пакети .deb на сторінці релізів GitHub. Станом на July 2026 поточний реліз — 0.29.3. Спочатку перевірте архітектуру, оскільки вона вказана в імені файлу.
sudo apt update
sudo apt install -y wget
dpkg --print-architectureЦя команда виводить amd64 на звичайному x86 VPS і arm64 на плані типу Ampere або Graviton. Запишіть результат у змінну нижче.
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 перед запуском служби
Відредагуйте /etc/headscale/config.yaml за допомогою sudo nano /etc/headscale/config.yaml або застосуйте ті самі три зміни за допомогою sed. Збережіть копію оригіналу, оскільки файл довгий і містить багато коментарів. Це найкраще довідкове джерело для решти параметрів.
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.yamlserver_url — це адреса, яку headscale записує в реєстраційні дані кожного клієнта. Після цього клієнти завжди підключаються саме до цього рядка, тому тут має бути публічне ім’я з https:// на початку, а не 127.0.0.1.
listen_addr — це адреса, на якій процес приймає підключення. Залиште її на loopback. Reverse proxy на тому самому сервері завершує TLS (захист транспортного рівня) і пересилає запити до цього процесу, тому порт 8080 не має бути доступним ззовні.
base_domain — це суфікс MagicDNS, домен, у якому вузли отримують імена. Це має бути повне доменне ім’я без крапки в кінці. Воно також має відрізнятися від домену в server_url, інакше простори імен конфліктуватимуть.
Не змінюйте розділ бази даних. Типово використовується SQLite у /var/lib/headscale/db.sqlite, у каталозі, створеному та керованому пакетом. SQLite достатньо для tailnet такого розміру.
Запустіть 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/healthis-active виводить active, а curl виводить 200. enable --now виконує обидві дії: запускає службу та налаштовує її автоматичний запуск після перезавантаження.
Якщо is-active виводить failed, перегляньте журнал за допомогою sudo journalctl -u headscale -n 50 --no-pager. На цьому етапі збій майже завжди спричинений файлом конфігурації, оскільки headscale аналізує весь файл до відкриття сокета. Тому неправильний відступ або невідомий ключ зупиняє процес до початку прослуховування будь-якого порту. Виправте файл, а потім виконайте sudo systemctl restart headscale. Кожна подальша зміна конфігурації потребує такого самого перезапуску. Після цього клієнти перепідключаються автоматично. Якщо ви ще не працювали з блоками systemd, у матеріалі про запуск власних служб і таймерів за допомогою systemd описано команди, використані тут.
Перевірте файли стану, поки ви працюєте в оболонці:
stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.keyОбидва рядки починаються з headscale — непривілейованого користувача, якого створив пакет. noise_private.key — це ідентифікатор сервера для його клієнтів. Не видаляйте його. Якщо його видалити, headscale створить новий і кожен вузол доведеться зареєструвати повторно.
Налаштування TLS перед headscale
Клієнти повинні підключатися до server_url через HTTPS. 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 caddyvalidate виводить adapted config to JSON, якщо файл має правильний синтаксичний формат. Попередження про те, що файл не відформатований, не впливає на роботу. На вашому ноутбуці curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health також має вивести 200. Ця перевірка підтверджує, що DNS, брандмауер, сертифікат і проксі працюють разом.
Нижче наведено деталь проксі, яка часто забирає багато часу. Керувальне з’єднання Tailscale використовує оновлення HTTP, встановлюється за допомогою POST, а значення заголовка Upgrade дорівнює tailscale-control-protocol. Caddy передає це без додаткового налаштування. nginx цього не робить, тому для front end на nginx потрібна карта оновлення:
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, у статті Certbot на Ubuntu 24.04 з nginx описано налаштування сертифіката.
Які порти відкрити в UFW
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseЧерез порт 443 проходить увесь обмін даними з клієнтами. Порт 80 потрібен лише для HTTP-виклику ACME (середовища автоматичного керування сертифікатами) і перенаправлення на HTTPS. Крім того, Caddy потребує його для отримання сертифіката.
Порт 8080 залишається закритим. listen_addr — це 127.0.0.1:8080, тому проксі підключається до headscale через loopback-інтерфейс, і правило брандмауера не потрібне. Відкриття порту 8080 для інтернету надає клієнтам незашифрований канал керування, але не дає жодних переваг. Пам’ятайте, що більшість провайдерів мають окремий брандмауер у панелі керування, незалежний від UFW. Тому порт може бути відкритим на сервері, але закритим на периферії мережі. У розділі Основи брандмауера UFW на VPS докладніше розглянуто синтаксис правил.
Створення користувача та ключа попередньої автентифікації
sudo headscale users create alice
sudo headscale users listКоманда headscale є клієнтом. Вона взаємодіє із запущеною службою через unix-сокет /var/run/headscale/headscale.sock, який має режим 0770 і належить групі headscale. З цього випливають два наслідки. Команда завершується помилкою, якщо службу зупинено. Це ще одна причина дотримуватися порядку дій у цьому посібнику. Також потрібен sudo, якщо не додати власний обліковий запис до групи headscale.
users list виводить ідентифікатор поруч із кожним ім’ям. Вам потрібен цей номер, оскільки команда створення ключа приймає числовий ідентифікатор користувача, а не ім’я.
sudo headscale preauthkeys create --user 1 --expiration 24hКлюч виводиться лише один раз. Скопіюйте його зараз. Ключ попередньої автентифікації можна використати лише один раз, і за замовчуванням він дійсний протягом однієї години. Тому під час тестування варто налаштувати --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 -4tailscale ip -4 виводить адресу, призначену headscale, наприклад 100.64.0.1. На сервері команда sudo headscale nodes list показує вузол із його ідентифікатором, користувачем і станом підключення.
Значення --login-server має точно збігатися зі значенням server_url, включно зі схемою та без кінцевої косої риски. Їх порівнюють як рядки. Невідповідність означає, що клієнт реєструється за однією адресою, а потім отримує вказівку звертатися до іншої.
Машина, на якій раніше виконували вхід до розміщеної служби Tailscale, зберігає цей обліковий запис. Спочатку виконайте на ній sudo tailscale logout, а потім tailscale up із параметром --login-server.
Якщо не вказати --auth-key, клієнт натомість виведе URL. Відкрийте його. На сторінці буде показано ідентифікатор цієї спроби реєстрації. Підтвердьте її на сервері:
sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGEЦя форма зручніша для власного ноутбука. Для сценаріїв автоматизації краще використовувати попередньо авторизовані ключі, оскільки присутність користувача не потрібна.
DERP і ретрансляція трафіку, коли прямий шлях недоступний
DERP (designated encrypted relay for packets — визначений зашифрований ретранслятор пакетів) — це резервний шлях. Коли два вузли не можуть встановити пряме з’єднання WireGuard, зазвичай через те, що обидва перебувають за строгим NAT (network address translation — трансляція мережевих адрес), вони надсилають пакети через ретранслятор. Ретранслятор не має ключів, тому не може читати ваш трафік. Він бачить, які вузли обмінюються даними та який обсяг даних передається.
Важливо розуміти, що робить конфігурація за замовчуванням. Headscale постачається з налаштуваннями, які вказують на https://controlplane.tailscale.com/derpmap/default, із параметрами auto_update_enabled: true і update_frequency: 3h. Тому площина керування належить вам, а ретранслятори — Tailscale. Для більшості користувачів це прийнятний компроміс. Якщо ні, запустіть власний ретранслятор.
Щоб запустити власний ретранслятор, задайте enabled: true у розділі derp.server файлу config.yaml, перезапустіть headscale та відкрийте порт STUN (session traversal utilities for NAT — утиліти проходження сеансів через NAT) за допомогою sudo ufw allow 3478/udp. У конфігураційному файлі ця вимога зазначена однозначно: server_url має використовувати https, оскільки DERP потребує TLS. Очищення списку derp.urls вилучає ретранслятори Tailscale з карти. Якщо зробити це без працюючого вбудованого ретранслятора, будь-яка пара вузлів, яка не може встановити пряме з’єднання, взагалі не зможе з’єднатися.
На клієнті команда tailscale netcheck виводить затримку до кожного відомого йому регіону ретрансляції. Команда tailscale status позначає кожного однорангового вузла як direct з адресою або relay з кодом регіону. Одноранговий вузол, що завис у стані relay, свідчить про проблему з NAT, а не з headscale.
Чому вузол має статус offline?
Проксі відкидає upgrade. Це найпоширеніша причина. Її ознака: все інше працює нормально: /health повертає 200, headscale nodes list показує вузол, але вузол не переходить у стан online. Керувальне з'єднання — це POST-запит із Upgrade: tailscale-control-protocol. Проксі, який не пересилає цей запит, закриває єдиний канал передавання стану вузла. Порівняйте конфігурацію nginx із наведеним вище блоком map або перейдіть на Caddy, щоб виключити проксі як причину.
Значення server_url змінилося після реєстрації вузлів. Вузли продовжують підключатися за значенням, яке отримали під час реєстрації. Якщо ви змінили це значення, виконайте sudo tailscale up --login-server https://headscale.example.com --force-reauth на кожному вузлі.
Клієнт не запущений. На вузлі виконайте sudo systemctl is-active tailscaled і sudo journalctl -u tailscaled -n 50 --no-pager. Клієнт, який не може визначити адресу вашого домену або підключитися до нього, записує повторні спроби саме там.
Термін дії ключа завершився. Це описано в наступному розділі.
Щоб під час перевірки відстежувати роботу сервера, виконайте sudo journalctl -u headscale -f на VPS і перезапустіть tailscaled на клієнті. Вузол, який досягає headscale, одразу створює записи в журналі. Якщо записів немає, запит не надходить. Спочатку перевірте DNS, firewall і проксі, а вже потім headscale.
Завершення терміну дії ключів і вузол, який перестає працювати через кілька тижнів
Існують два окремі терміни дії. Якщо їх плутати, це марнує час.
Термін дії ключів попередньої авторизації навмисно короткий. Типове значення — одна година та одне використання. Якщо tailscale up відхиляє ключ, створіть новий ключ на сервері, а не редагуйте щось на клієнті.
Ключі вузлів мають тривалий термін дії. Розділ node у config.yaml задає expiry: 0, а 0 означає відсутність типового терміну дії: зареєстрований вузол залишається дійсним, доки ви не завершите його дію. Для вузлів із тегами термін дії не завершується за жодних умов. Установіть expiry: 180d, якщо потрібно автоматично видаляти застарілі реєстрації. Врахуйте наслідки: кожен вузол без тегів потребуватиме sudo tailscale up --login-server https://headscale.example.com --force-reauth за цим розкладом, а сервер без графічного інтерфейсу, на якому ніхто повторно не проходить автентифікацію, самостійно втратить доступ до мережі.
Виконайте це вручну, якщо хтось загубив ноутбук. sudo headscale nodes list повертає ідентифікатор, потім 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 описано, як виконувати це регулярно та в зашифрованому вигляді.
Оновлення повторює процес інсталяції: завантажте нові .deb і sudo apt install ./headscale.deb, потім перезапустіть службу та повторно виконайте перевірки is-active і /health. Починаючи з версії 0.29, шлях оновлення суворо обмежений. Пропуск мінорної версії заблоковано, як і повернення до старішої мінорної версії. Переходьте на одну мінорну версію за раз, створюйте резервну копію перед кожним кроком і спочатку прочитайте примітки до випуску цієї версії, оскільки в цьому випуску також змінено поведінку політики ACL і переміщено кілька ключів конфігурації.
FAQ
Чому headscale не запускається одразу після встановлення .deb?
Пакет встановлює unit, але залишає службу зупиненою, а типовий /etc/headscale/config.yaml є шаблоном, а не робочою конфігурацією. Спочатку відредагуйте server_url, listen_addr і base_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, але ніколи не переходить в онлайн, зазвичай втрачає керувальне з’єднання на зворотному проксі. Це з’єднання є оновленням HTTP, надісланим методом POST із заголовком Upgrade: tailscale-control-protocol. nginx відкидає його, якщо не додати блок map $http_upgrade $connection_upgrade і відповідні рядки proxy_set_header. Caddy пересилає таке з’єднання без додаткової конфігурації, тому його зручно використовувати для перевірки, чи є проксі причиною проблеми.
Чи потрібні для headscale доменне ім’я та TLS?
На практиці так. Клієнти підключаються до рядка, указаного в server_url, сертифікати видаються для імен, а не для самих IP-адрес, і у файлі конфігурації зазначено, що DERP потребує TLS. Домен у поєднанні з Caddy налаштовується приблизно за п’ять хвилин і надає кінцеву точку HTTPS, для якої сертифікат поновлюється автоматично. Якщо запускати сервер керування через звичайний HTTP, усі з’єднання клієнтів із ним передаються через інтернет у незашифрованому вигляді.