Headscale: hospede seu próprio servidor Tailscale
Execute seu próprio servidor de controle Tailscale em um VPS. Instale o headscale pelo .deb oficial, defina server_url antes de iniciar e conecte o primeiro nó.
O que é o headscale
O headscale é uma implementação autoalojada do servidor de controlo do Tailscale. Assim, a máquina que coordena a sua rede privada é um VPS que lhe pertence. É um projeto da comunidade e não é operado pela Tailscale Inc. Cada máquina continua a executar o cliente oficial tailscale, apontado para o seu servidor com uma opção, --login-server.
O servidor de controlo é a parte que sabe quem pertence à rede. Atribui a cada nó um endereço de 100.64.0.0/10, distribui chaves públicas e informa os nós sobre como se podem encontrar. Os túneis continuam a usar WireGuard e são estabelecidos diretamente entre os nós. O tráfego entre duas das suas máquinas não passa pelo servidor headscale, exceto quando não é possível criar um caminho direto e os nós recorrem a um relay. Assumir essa função de coordenação altera quem a controla, mas não o que ela consegue fazer. Por isso, é importante compreender o que um servidor de controlo consegue e não consegue alcançar neste modelo antes de considerar a mudança uma melhoria de segurança por si só.
Cada instância do headscale disponibiliza uma tailnet (uma rede Tailscale), que o projeto considera adequada para uso pessoal ou para uma organização pequena. Com três ou quatro máquinas, uma VPN WireGuard simples num VPS que lhe pertença exige menos software para executar e tem menos componentes que podem falhar. O headscale é útil quando já não quer escrever manualmente um bloco [Peer] para cada portátil novo. O custo é frequentemente o motivo que leva as pessoas a procurar uma alternativa, por isso vale a pena ler o que o plano gratuito alojado realmente inclui antes de assumir a gestão de um servidor, porque algumas máquinas pessoais normalmente cabem nesse plano. Se já ultrapassou esse limite, faça as contas com base em quanto custam os planos pagos, que são cobrados por utilizador e não por dispositivo, porque uma família com uma única conta pode continuar a pagar pouco mesmo quando o número de dispositivos deixa de ser relevante. Se pretende um plano de controlo autoalojado, mas prefere ter o seu próprio cliente e uma interface web para gerir peers em vez de uma substituição direta do Tailscale, o NetBird num único VPS é a alternativa que vale a pena avaliar. Para uma comparação mais ampla dos dois modelos, consulte as diferenças entre WireGuard e Tailscale.
O que é necessário antes da instalação
- Uma VPS com Ubuntu 24.04, um endereço IPv4 público e acesso sudo. Se o servidor for novo, siga primeiro os primeiros dez minutos numa VPS nova.
- Um registo DNS A apontado para esse endereço. Este guia usa
headscale.example.com. - Um segundo domínio ou subdomínio para o MagicDNS. Este guia usa
tailnet.example.net. Não pode ser o mesmo domínio usado emserver_url. - Um computador cliente para associar, com Linux, macOS, Windows, Android ou iOS.
Instalar o headscale a partir do pacote .deb oficial
O projeto publica pacotes .deb na página de releases do GitHub. Em julho de 2026, a versão atual é 0.29.3. Verifique primeiro a arquitetura, porque o nome do ficheiro inclui essa informação.
sudo apt update
sudo apt install -y wget
dpkg --print-architectureEsse comando apresenta amd64 numa VPS x86 comum e arm64 num plano do tipo Ampere ou Graviton. Coloque o resultado na variável abaixo.
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 versionO ./ antes do nome do ficheiro é obrigatório. Sem ele, apt procura um pacote chamado headscale.deb nos seus repositórios e falha.
O pacote cria um utilizador de sistema headscale, escreve um ficheiro de configuração predefinido /etc/headscale/config.yaml e instala uma unidade systemd. Ele não inicia o serviço, e essa é a ordem correta. A configuração fornecida aponta server_url para http://127.0.0.1:8080, que não é um endereço acessível por nenhum dos seus clientes. Por isso, iniciar o serviço agora estaria errado, mesmo que ele arrancasse. Executar sudo systemctl is-active headscale neste momento apresenta inactive. Isso é esperado, não é uma falha.
Configure server_url antes de iniciar o serviço
Edite /etc/headscale/config.yaml com sudo nano /etc/headscale/config.yaml ou aplique as mesmas três alterações com sed. Mantenha uma cópia do original, porque o ficheiro é longo e contém muitos comentários, além de ser a melhor referência para as restantes definições.
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 é o endereço que o headscale grava em cada registo de cliente. Depois disso, os clientes ligam-se sempre a essa cadeia exata. Por isso, deve ser o nome público com https:// no início, nunca 127.0.0.1.
listen_addr é o endereço onde o processo fica à escuta. Mantenha-o na interface de loopback. Um reverse proxy no mesmo servidor termina o TLS (transport layer security) e encaminha as ligações para esse endereço. Assim, nada fora do servidor precisa de aceder à porta 8080.
base_domain é o sufixo do MagicDNS, o domínio sob o qual os seus nós recebem nomes. Deve ser um nome de domínio totalmente qualificado, sem ponto final, e diferente do domínio definido em server_url. Caso contrário, os dois espaços de nomes entram em conflito.
Não altere a secção da base de dados. A predefinição é SQLite em /var/lib/headscale/db.sqlite, num diretório criado e gerido pelo pacote. SQLite é suficiente para uma tailnet deste tamanho.
Inicie o headscale e confirme que está em execução
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 mostra active e curl mostra 200. enable --now executa as duas partes: inicia o serviço e configura o arranque após um reboot.
Se is-active mostrar failed, consulte o journal com sudo journalctl -u headscale -n 50 --no-pager. Nesta fase, a falha deve-se quase sempre ao ficheiro de configuração, porque o headscale analisa o ficheiro inteiro antes de abrir um socket. Por isso, uma indentação incorreta ou uma chave desconhecida interrompe o processo antes de qualquer socket ficar em escuta. Corrija o ficheiro e execute sudo systemctl restart headscale. Todas as alterações posteriores à configuração exigem o mesmo restart. Os clientes voltam a ligar-se automaticamente. Se as unidades systemd forem novas para si, execute os seus próprios serviços e timers com systemd apresenta os comandos usados aqui.
Enquanto estiver na shell, verifique os ficheiros de estado:
stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.keyAs duas linhas começam com headscale, o utilizador sem privilégios criado pelo pacote. noise_private.key é a identidade do servidor perante os clientes. Não o apague. Se o apagar, o headscale gera uma nova identidade e todos os nós terão de se registar novamente.
Coloque o TLS à frente do headscale
Os clientes têm de alcançar server_url através de HTTPS. O Caddy é a opção mais simples, porque solicita e renova o certificado automaticamente.
sudo apt install -y caddySubstitua /etc/caddy/Caddyfile pelo bloco da documentação do 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 apresenta adapted config to JSON quando o ficheiro é analisado corretamente. Um aviso a indicar que o ficheiro não está formatado é apenas cosmético. A partir do seu portátil, curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health também deve apresentar 200. Esta verificação confirma que o DNS, a firewall, o certificado e o proxy estão a funcionar em conjunto.
Existe um detalhe do proxy que pode consumir uma tarde inteira. A ligação de controlo do Tailscale é uma atualização HTTP. É iniciada com POST, e o valor do cabeçalho Upgrade é tailscale-control-protocol. O Caddy encaminha isto sem configuração adicional. O nginx não o faz. Por isso, um frontend nginx precisa do mapa de atualização:
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;
}
}Se omitir essas linhas, os pedidos normais continuam a funcionar. Por isso, /health devolve 200 e tudo parece correto, mas a ligação de controlo de longa duração nunca é estabelecida. Os seus nós registam-se e ficam offline. Se optar pelo nginx, Certbot no Ubuntu 24.04 com nginx explica a parte do certificado.
Quais portas abrir no UFW
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseA porta 443 transporta toda a comunicação dos clientes. A porta 80 é usada apenas pelo desafio HTTP do ACME (ambiente de gestão automática de certificados) e pelo redirecionamento para HTTPS. O Caddy precisa dela para obter um certificado.
A porta 8080 permanece fechada. listen_addr é 127.0.0.1:8080, por isso o proxy acede ao headscale através da interface de loopback e não é necessária nenhuma regra de firewall. Abrir a porta 8080 para a Internet disponibiliza aos clientes um canal de controlo em texto simples e não traz qualquer benefício. Tenha em atenção que a maioria dos fornecedores executa uma segunda firewall no respetivo painel de controlo, separada do UFW. Por isso, uma porta pode estar aberta no servidor e continuar fechada na periferia da rede. Noções básicas do firewall UFW numa VPS explica a sintaxe das regras com mais detalhe.
Crie um utilizador e uma chave de preautenticação
sudo headscale users create alice
sudo headscale users listO comando headscale é um cliente. Comunica com o daemon em execução através do socket Unix em /var/run/headscale/headscale.sock, que tem o modo 0770 e pertence ao grupo headscale. Isto tem duas consequências. O comando falha quando o serviço está parado, que é outra razão para a ordem deste guia ser importante, e requer sudo, a menos que adicione a sua própria conta ao grupo headscale.
users list apresenta um ID junto de cada nome. Precisa desse número porque o comando da chave aceita um ID numérico de utilizador, não um nome.
sudo headscale preauthkeys create --user 1 --expiration 24hA chave é apresentada apenas uma vez. Copie-a agora. Uma chave de preautenticação só pode ser utilizada uma vez e é válida durante uma hora, a menos que especifique o contrário. Por isso, vale a pena definir --expiration 24h enquanto ainda está a testar. Adicione --reusable para criar uma chave que permita inscrever várias máquinas. Trate essa chave como uma palavra-passe, porque qualquer pessoa que a possua pode aderir à sua rede.
Ligue o primeiro cliente com --login-server
Na máquina que você quer adicionar:
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 exibe o endereço atribuído pelo headscale, algo como 100.64.0.1. No servidor, sudo headscale nodes list exibe o nó com o respetivo ID, utilizador e estado online.
O valor de --login-server tem de corresponder exatamente a server_url, incluindo o esquema e sem uma barra no final. Os valores são comparados como strings. Uma divergência faz o cliente registar-se num endereço e depois receber instruções para comunicar com outro.
Uma máquina que tenha iniciado sessão anteriormente no serviço alojado Tailscale mantém essa sessão. Execute primeiro sudo tailscale logout nessa máquina e depois execute tailscale up com --login-server.
Se omitir --auth-key, o cliente imprime um URL. Abra-o. A página mostra o identificador dessa tentativa de registo, que deve aprovar no servidor:
sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGEEste formulário é mais prático no seu próprio portátil. As chaves de pré-autorização são melhores para qualquer processo automatizado, porque não é necessária a intervenção de uma pessoa. Depois de o próprio VPS ser um nó, também pode transportar o tráfego de Internet das suas outras máquinas. Essa é a configuração do nó de saída. A única diferença é que aprova a rota anunciada no servidor com o comando headscale, em vez de o fazer numa consola de administração alojada. Se pretende aceder a uma rede privada situada atrás desse VPS, e não usá-lo como saída para a Internet, o mesmo passo de aprovação permite anunciar essa sub-rede ao restante tailnet. Publicar uma aplicação a partir de um nó, em vez de encaminhar redes inteiras através dele, é outra tarefa. serve e funnel são as duas formas de o fazer, embora ambos dependam da infraestrutura de certificados e ingresso do próprio Tailscale. Considere-os funcionalidades de um tailnet alojado, e não algo fornecido pelo headscale.
DERP e o que retransmite o tráfego quando o caminho direto falha
DERP (designated encrypted relay for packets) é o caminho de fallback. Quando dois nós não conseguem estabelecer uma ligação WireGuard direta, normalmente porque ambos estão atrás de NAT (network address translation) restrito, enviam os pacotes através de um relay. O relay não possui chaves, portanto não consegue ler o seu tráfego. No entanto, consegue ver quais nós estão a comunicar e a quantidade de dados transferida.
É importante compreender o comportamento da configuração predefinida. O Headscale é distribuído apontando para https://controlplane.tailscale.com/derpmap/default, com auto_update_enabled: true e update_frequency: 3h, por isso o seu plano de controlo é seu, mas os relays são da Tailscale. Para a maioria das pessoas, é uma troca aceitável. Se não for para si, execute o seu próprio relay.
Para executar o seu próprio relay, defina enabled: true em derp.server dentro de config.yaml, reinicie o Headscale e abra a porta STUN (session traversal utilities for NAT) com sudo ufw allow 3478/udp. O ficheiro de configuração declara claramente este requisito: server_url deve usar https, porque o DERP requer TLS. Esvaziar a lista derp.urls remove os relays da Tailscale do mapa. Se fizer isso sem um relay incorporado funcional, qualquer par de nós que não consiga estabelecer uma ligação direta não conseguirá ligar-se de todo.
A partir de um cliente, tailscale netcheck apresenta a latência para cada região de relay que conhece, e tailscale status identifica cada peer como direct com um endereço ou relay com um código de região. Um peer bloqueado em relay indica um problema de NAT, não do headscale. Um peer que está direct e continua lento é outra questão, e a resposta habitual é MTU, não o próprio túnel.
Por que um node aparece como offline?
O proxy está a descartar o upgrade. Este é o caso mais comum. O sinal é que tudo o resto parece normal: /health devolve 200, headscale nodes list mostra o node e o node nunca fica online. A ligação de controlo é um POST que transporta Upgrade: tailscale-control-protocol. Um proxy que não encaminhe esse pedido elimina o único canal que comunica o estado do node. Compare a configuração do nginx com o bloco map acima ou mude para Caddy para excluir o proxy como causa.
server_url mudou depois de os nodes serem registados. Os nodes continuam a ligar-se ao valor que receberam durante o registo. Se o alterou, execute sudo tailscale up --login-server https://headscale.example.com --force-reauth em cada node.
O cliente não está em execução. No node, execute sudo systemctl is-active tailscaled e sudo journalctl -u tailscaled -n 50 --no-pager. Um cliente que não consiga resolver ou alcançar o seu domínio regista as tentativas nesses logs.
A chave expirou. Este caso é explicado na secção seguinte.
Para monitorizar o lado do servidor durante o teste, execute sudo journalctl -u headscale -f no VPS e reinicie tailscaled no cliente. Um node que consiga alcançar o headscale produz linhas de log imediatamente. A ausência de linhas significa que o pedido não está a chegar. Nesse caso, verifique o DNS, a firewall e o proxy antes de verificar o headscale.
Expiração das chaves e o nó que deixa de funcionar semanas depois
Existem duas expirações separadas. Confundi-las faz perder tempo.
As chaves de preautenticação expiram rapidamente por definição. O padrão é uma hora e uma utilização. Se tailscale up recusar a chave, gere uma nova no servidor em vez de editar alguma coisa no cliente.
As chaves dos nós são a parte de longa duração. A secção node de config.yaml define expiry: 0, e 0 significa que não existe expiração predefinida: um nó registado continua válido até que o expire. Os nós etiquetados nunca expiram. Defina expiry: 180d se quiser que os registos expirem automaticamente, mas compreenda o que está a configurar: todos os nós que não tenham etiquetas precisarão de sudo tailscale up --login-server https://headscale.example.com --force-reauth de acordo com esse período, e um servidor sem interface que ninguém volte a autenticar será removido da rede por si próprio.
Faça isto manualmente quando alguém perder um portátil. sudo headscale nodes list mostra o ID, depois sudo headscale nodes expire -i 3 termina a sessão desse nó e sudo headscale nodes delete -i 3 remove-o totalmente da rede.
Backups e atualizações
/var/lib/headscale e /etc/headscale juntos constituem o servidor completo. Pare o serviço antes de os copiar, porque o SQLite pode ter operações de escrita em curso e uma base de dados copiada sob carga pode ficar inconsistente.
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-*.tgzTransfira os dois ficheiros para fora do servidor. Contêm as chaves privadas e todos os registos, por isso exigem o mesmo cuidado que o próprio servidor. backups restic a partir de um VPS explica como fazer isto de forma agendada e encriptada.
As atualizações repetem o processo de instalação: descarregue o novo .deb, sudo apt install ./headscale.deb, reinicie e volte a executar as verificações is-active e /health. Desde a versão 0.29, o processo de atualização é rigoroso. Não é possível saltar uma versão minor, nem fazer downgrade para uma versão minor mais antiga. Avance uma versão minor de cada vez, faça um backup antes de cada passo e leia primeiro as notas de versão dessa versão, porque essa mesma versão alterou o comportamento das políticas de ACL e mudou várias chaves de configuração.
FAQ
Por que o headscale não inicia logo depois de eu instalar o .deb?
O pacote instala a unidade, mas deixa o serviço parado, e o /etc/headscale/config.yaml predefinido é um modelo, não uma configuração funcional. Edite primeiro server_url, listen_addr e base_domain. Em seguida, execute sudo systemctl enable --now headscale e confirme com sudo systemctl is-active headscale. Se ainda falhar, sudo journalctl -u headscale -n 50 --no-pager identifica o problema. Nesta fase, quase sempre é um erro de YAML, porque o headscale analisa o ficheiro inteiro antes de abrir uma porta.
Ainda preciso de instalar o cliente Tailscale normal nas minhas máquinas?
Sim. O headscale substitui apenas o servidor de controlo. Cada nó executa o cliente oficial da Tailscale, que deve ser apontado para o seu servidor com sudo tailscale up --login-server https://headscale.example.com. Essa flag existe no cliente padrão. Não é necessário aplicar patches nem recompilar o cliente.
O meu tráfego passa pelo servidor headscale?
Normalmente, não. O headscale coordena a rede e distribui chaves e endereços. O caminho dos dados usa WireGuard diretamente entre os seus nós. O tráfego só faz um desvio quando dois nós não conseguem comunicar diretamente e recorrem a um relay DERP. Com a configuração fornecida, esses relays são os relays públicos da Tailscale. Execute tailscale status num nó para ver se um determinado peer está direct ou num relay.
Por que o meu nó permanece offline depois de se registar?
Um nó que aparece em headscale nodes list, mas nunca fica online, normalmente perdeu a ligação de controlo no reverse proxy. Essa ligação é uma atualização HTTP enviada como POST com o cabeçalho Upgrade: tailscale-control-protocol. O nginx elimina essa atualização se não adicionar o bloco map $http_upgrade $connection_upgrade e as linhas proxy_set_header correspondentes. O Caddy encaminha essa ligação sem configuração adicional. Por isso, é uma forma rápida de testar se o problema está no proxy.
Preciso de um nome de domínio e de TLS para o headscale?
Na prática, sim. Os clientes ligam-se à string definida em server_url. Os certificados são emitidos para nomes, não para endereços IP sem nome. Além disso, o ficheiro de configuração indica que o DERP requer TLS. Um domínio com Caddy demora cerca de cinco minutos a configurar e fornece um endpoint HTTPS com renovação automática. Executar o servidor de controlo através de HTTP simples faz com que toda a comunicação dos clientes com ele atravesse a Internet sem encriptação.