Как установить 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). Проект считает такую конфигурацию подходящей для личного использования или небольшой организации. Если у вас 3 или 4 машины, обычный VPN WireGuard на принадлежащем вам VPS требует меньше программного обеспечения для обслуживания и предоставляет меньше возможностей для сбоев. Headscale становится полезен, когда вы больше не хотите вручную создавать блок [Peer] для каждого нового ноутбука. Общее сравнение этих двух моделей приведено в статье о различиях между WireGuard и Tailscale.
Что необходимо подготовить перед установкой
- VPS под управлением Ubuntu 24.04 с публичным IPv4-адресом и доступом через sudo. Если сервер новый, сначала выполните инструкции из раздела первые 10 минут на новом 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/healthКоманда is-active выводит active, а команда curl выводит 200. Команда enable --now выполняет обе части: запускает службу и настраивает ее запуск после перезагрузки.
Если команда is-active выводит failed, прочитайте журнал с помощью sudo journalctl -u headscale -n 50 --no-pager. На этом этапе почти всегда проблема связана с файлом конфигурации. headscale анализирует весь файл до открытия сокета, поэтому неправильный отступ или неизвестный ключ останавливает процесс до того, как он начинает прослушивать порт. Исправьте файл, затем выполните sudo systemctl restart headscale. После каждого последующего изменения конфигурации требуется такой же перезапуск. После этого клиенты переподключаются автоматически. Если systemd units для вас в новинку, в материале запуск собственных служб и таймеров с помощью systemd описаны используемые здесь команды.
Пока вы работаете в shell, проверьте файлы состояния:
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 этого не делает, поэтому для интерфейса 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, поэтому proxy подключается к 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 выводит идентификатор рядом с каждым именем. Вам потребуется этот номер, поскольку команда создания ключа принимает числовой ID пользователя, а не имя.
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 -4Команда tailscale 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 (назначенный зашифрованный ретранслятор пакетов) — это резервный путь. Если два узла не могут установить прямое соединение WireGuard, обычно потому, что оба находятся за строгим NAT (преобразованием сетевых адресов), они вместо этого отправляют пакеты через ретранслятор. Ретранслятор не хранит ключи, поэтому не может прочитать ваш трафик. Однако он видит, какие узлы обмениваются данными и какой объем данных передается.
Важно понимать, что делает конфигурация по умолчанию. Headscale поставляется с настройками, указывающими на https://controlplane.tailscale.com/derpmap/default, с auto_update_enabled: true и update_frequency: 3h, поэтому плоскость управления принадлежит вам, а ретрансляторы — Tailscale. Для большинства пользователей это приемлемый компромисс. Если это вам не подходит, запустите собственный ретранслятор.
Чтобы запустить собственный ретранслятор, задайте enabled: true в разделе derp.server файла config.yaml, перезапустите headscale и откройте порт STUN (вспомогательные средства обхода NAT для установления сеанса) с помощью sudo ufw allow 3478/udp. В конфигурационном файле это требование указано явно: server_url должен использовать https, поскольку DERP требует TLS. Очистка списка derp.urls удаляет ретрансляторы Tailscale из карты. Если сделать это без работающего встроенного ретранслятора, любая пара узлов, которая не может подключиться напрямую, не сможет подключиться вообще.
На клиенте команда tailscale netcheck выводит задержку до каждого известного ему региона ретрансляции, а tailscale status помечает каждого узла как direct с адресом или как relay с кодом региона. Узел, зависший в состоянии relay, указывает на проблему с NAT, а не на проблему с headscale.
Почему узел отображается как отключенный?
Прокси не передает upgrade. Это наиболее распространенная причина. Ее признак заключается в том, что все остальное работает нормально: /health возвращает 200, headscale nodes list показывает узел, но узел не подключается. Управляющее соединение представляет собой 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.
Срок действия ключей и узел, который перестает работать через несколько недель
Существуют два разных срока действия. Если их перепутать, вы потратите время впустую.
Срок действия ключей Preauth намеренно небольшой. По умолчанию он составляет один час и одну активацию. Если 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 upgrade с помощью запроса 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, весь обмен каждого клиента с ним будет проходить через интернет в незашифрованном виде.