Как установить Headscale на свой VPS
Разверните собственный сервер управления Tailscale с помощью Headscale. В статье приведена инструкция по установке deb пакета, настройке параметра server_url и подключению узлов.
Что такое headscale
Headscale — это self-hosted реализация сервера управления Tailscale. Это означает, что координацией вашей частной сети занимается VPS, который принадлежит вам. Проект является сообществом и не управляется компанией Tailscale Inc. Каждое устройство по-прежнему использует официальный клиент tailscale, который направляется на ваш сервер с помощью одного флага — --login-server.
Сервер управления — это компонент, который определяет состав сети. Он выдает каждому узлу адрес из диапазона 100.64.0.0/10, распространяет публичные ключи и сообщает узлам, где искать друг друга. Туннели остаются протоколом WireGuard, который устанавливается напрямую между узлами. Трафик между двумя вашими машинами не проходит через сервер headscale, если только прямое соединение установить не удается и узлы не переключаются на ретранслятор (relay). Выполнение этой координационной роли самостоятельно меняет лишь того, кто ею управляет, а не её функциональные возможности. Поэтому стоит изучить к чему сервер управления имеет и не имеет доступ в этой модели, прежде чем рассматривать этот переход как самостоятельное решение для повышения безопасности.
Headscale обслуживает одну tailnet (сеть Tailscale) на экземпляр, что, по описанию проекта, подходит для личного использования или небольшой организации. Если у вас три или четыре машины, обычный WireGuard VPN на собственном VPS потребует меньше программного обеспечения и будет надежнее. Headscale становится полезен, когда вы больше не хотите вручную писать блоки [Peer] для каждого нового ноутбука. Часто именно стоимость заставляет людей искать альтернативы, поэтому стоит ознакомиться с тем, что на самом деле покрывает бесплатный тарифный план, прежде чем разворачивать сервер, так как несколько личных устройств обычно укладываются в его лимиты. Если вы уже превысили этот порог, сравните затраты с стоимостью платных тарифов, которая рассчитывается на пользователя, а не на устройство, так как домашнее хозяйство с одной учетной записью может оставаться недорогим даже при большом количестве устройств. Если вам нужна self-hosted плоскость управления, но вы предпочитаете собственный клиент и веб-интерфейс для управления узлами, а не прямую замену Tailscale, NetBird на одном VPS — это альтернатива, которую стоит рассмотреть. Для более широкого сравнения двух моделей см. различия между 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. По состоянию на июль 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. Обратный прокси-сервер на том же узле выполняет TLS (transport layer security) termination и перенаправляет трафик, поэтому внешним системам не нужно иметь доступ к порту 8080.
base_domain — это суффикс MagicDNS, домен, в котором узлы получают имена. Это должно быть полное доменное имя (FQDN) без точки в конце. Оно должно отличаться от домена в 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 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 и всё выглядит исправно, — однако долгоживущее управляющее соединение не установится, и ваши узлы будут регистрироваться, но оставаться в состоянии offline. Если вы выбрали путь с 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 (automatic certificate management environment) и для редиректа на HTTPS; Caddy требует его для получения сертификата.
Порт 8080 должен оставаться закрытым. listen_addr — это 127.0.0.1:8080, поэтому прокси обращается к headscale через loopback-интерфейс, и правила межсетевого экрана здесь не задействуются. Открытие порта 8080 для доступа из Интернета предоставляет клиентам канал управления в открытом виде и не даёт никаких преимуществ. Учитывайте, что большинство провайдеров используют второй межсетевой экран в панели управления, отдельный от UFW, поэтому порт может быть открыт на сервере, но закрыт на периметре сети. В основах работы с UFW на VPS подробно описан синтаксис правил.
Create a user and a preauth key
sudo headscale users create alice
sudo headscale users listThe headscale command is a client. It talks to the running daemon over the unix socket at /var/run/headscale/headscale.sock, which is mode 0770 and owned by the headscale group. Two things follow from that. The command fails while the service is stopped, which is the other reason the ordering in this guide matters, and it needs sudo unless you add your own account to the headscale group.
users list prints an ID next to each name. You need that number, because the key command takes a numeric user ID and not a name.
sudo headscale preauthkeys create --user 1 --expiration 24hThe key is printed once. Copy it now. A preauth key is single use and valid for one hour unless you say otherwise, so --expiration 24h is worth setting while you are still testing. Add --reusable for a key that enrolls several machines, and treat that one like a password, because anyone holding it can join your network.
Подключение первого клиента с помощью --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, чтобы увидеть узел с его ID, пользователем и статусом подключения.
Значение --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Эта форма удобнее для личного ноутбука. Ключи preauth лучше подходят для любых скриптов, так как не требуют участия человека. Как только сам VPS становится узлом, он может передавать интернет-трафик других ваших машин. Это называется настройка exit node, с той лишь разницей, что вы подтверждаете анонсированный маршрут на сервере командой headscale, а не в панели управления облачного сервиса. Если вам нужен доступ к частной сети за этим VPS, а не выход в интернет, тот же этап подтверждения используется для анонсирования подсети для остальной части вашей tailnet. Публикация отдельного приложения с узла, в отличие от маршрутизации целых сетей, — это другая задача, и serve и funnel — два способа сделать это. Оба метода опираются на собственные механизмы сертификатов и входящего трафика Tailscale, поэтому рассматривайте их как функции облачной tailnet, а не как инструменты, предоставляемые headscale.
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, поэтому ваша плоскость управления (control plane) принадлежит вам, а ретрансляторы — Tailscale. Для большинства пользователей это приемлемый компромисс. Если это не так, разверните собственный ретранслятор.
Чтобы запустить свой ретранслятор, настройте enabled: true в разделе derp.server файла config.yaml, перезапустите headscale и откройте порт STUN (session traversal utilities for NAT) с помощью sudo ufw allow 3478/udp. В файле конфигурации чётко указано требование: server_url должен использовать https, так как DERP требует TLS. Очистка списка derp.urls удаляет ретрансляторы Tailscale из карты сети. Если вы сделаете это, не настроив работающий встроенный ретранслятор, любая пара узлов, не способная соединиться напрямую, не сможет установить связь вовсе.
С клиента tailscale netcheck выводит задержку до каждого известного ему региона ретрансляции, а tailscale status помечает каждого узла как direct с адресом или как relay с кодом региона. Узел, застрявший в состоянии relay, указывает на проблему NAT, а не headscale. Если узел находится в состоянии direct, но соединение по-прежнему медленное, это уже отдельный вопрос: обычно проблема связана с MTU, а не с самим туннелем.
Почему узел отображается как offline?
Прокси сбрасывает запрос на обновление. Это распространенная причина. Признак проблемы: всё остальное выглядит исправно, /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, межсетевой экран и прокси, прежде чем переходить к диагностике headscale.
Истечение срока действия ключей и узлы, которые перестают работать через несколько недель
Существует два отдельных срока действия, и их путаница приводит к потере времени.
Предварительные ключи (preauth keys) по своей природе имеют короткий срок действия. По умолчанию это один час и одно использование. Если 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 позволяет узнать 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Перенесите оба файла с сервера. Они содержат закрытые ключи и все регистрации, поэтому требуют такого же уровня защиты, как и сам сервер. В резервном копировании restic с VPS описано, как выполнять это по расписанию и с шифрованием.
Обновление повторяет процесс установки: загрузите новые .deb, sudo apt install ./headscale.deb, затем перезапустите службу и повторно выполните проверки is-active и /health. Начиная с версии 0.29, путь обновления строго регламентирован. Пропуск минорной версии заблокирован, как и откат к более старой минорной версии. Переходите на одну минорную версию за раз, делайте резервную копию перед каждым шагом и сначала читайте примечания к выпуску для этой версии, так как в одном из релизов изменилось поведение политики ACL и были перемещены некоторые ключи конфигурации.
FAQ
Почему headscale не запускается сразу после установки .deb-пакета?
Пакет устанавливает юнит, но оставляет сервис остановленным, а файл /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.
Почему мой узел остается в статусе offline после регистрации?
Узел, который отображается в headscale nodes list, но не переходит в онлайн, обычно теряет соединение с сервером управления на уровне reverse proxy. Это соединение представляет собой HTTP upgrade, отправляемый методом POST с заголовком Upgrade: tailscale-control-protocol; nginx сбрасывает его, если не добавить блок map $http_upgrade $connection_upgrade и соответствующие строки proxy_set_header. Caddy пересылает такие запросы без дополнительной настройки, что делает его удобным инструментом для проверки того, виноват ли прокси.
Нужны ли мне доменное имя и TLS для headscale?
На практике — да. Клиенты подключаются к строке, указанной в server_url, сертификаты выдаются для имен, а не для IP-адресов, а в конфигурационном файле указано, что для DERP требуется TLS. Настройка домена вместе с Caddy занимает около 5 минут и дает HTTPS-эндпоинт с автоматическим обновлением сертификатов. Использование сервера управления по обычному HTTP означает, что весь обмен данными с клиентами будет передаваться через интернет в открытом виде.