Cómo instalar Headscale para alojar tu propio Tailscale
Instala Headscale en un VPS desde el paquete oficial .deb, configura server_url, reinicia el servicio y conecta tu primer nodo a tu tailnet.
Qué es headscale
Headscale es una implementación autohospedada del servidor de control de Tailscale. Por tanto, el equipo que coordina la red privada es un VPS que usted controla. Es un proyecto de la comunidad y Tailscale Inc. no lo administra. Cada equipo sigue ejecutando el cliente oficial de tailscale, configurado para usar su servidor mediante un indicador: --login-server.
El servidor de control es el componente que sabe quién pertenece a la red. Asigna a cada nodo una dirección del rango 100.64.0.0/10, distribuye claves públicas e indica a los nodos dónde encontrarse. Los túneles siguen usando WireGuard y se establecen directamente entre los nodos. El tráfico entre dos de sus equipos no pasa por el servidor de headscale, salvo que no se pueda establecer una ruta directa y los nodos tengan que recurrir a un relay. Administrar usted mismo esa función de coordinación cambia quién la controla, pero no sus capacidades. Por eso conviene entender qué puede alcanzar y qué no puede alcanzar un servidor de control en este modelo antes de considerar el cambio una mejora de seguridad por sí solo.
Cada instancia de headscale sirve un tailnet (una red de Tailscale). El proyecto lo considera adecuado para uso personal o para una organización pequeña. Con tres o cuatro equipos, una VPN WireGuard simple en un VPS que usted controle requiere menos software y ofrece menos elementos que puedan fallar. Headscale resulta útil cuando ya no quiere escribir manualmente un bloque [Peer] para cada portátil nuevo. El coste suele ser el motivo inicial para buscar alternativas, por lo que conviene leer qué cubre realmente el plan gratuito alojado antes de asumir la administración de un servidor. Unos pocos equipos personales suelen caber en ese plan. Si ya ha superado ese límite, compare el cálculo con el coste de los planes de pago, que se cobra por usuario y no por dispositivo, porque una familia con una sola cuenta puede mantener un coste bajo aunque aumente el número de dispositivos. Si quiere un plano de control autohospedado, pero prefiere usar su propio cliente y una interfaz web para administrar los peers en lugar de un reemplazo directo de Tailscale, NetBird en un solo VPS es una alternativa que conviene valorar. Para una comparación más amplia de ambos modelos, consulte en qué se diferencian WireGuard y Tailscale.
Qué necesita antes de la instalación
- Un VPS con Ubuntu 24.04, una dirección IPv4 pública y acceso mediante sudo. Si el servidor es nuevo, siga primero los primeros diez minutos en un VPS nuevo.
- Un registro DNS A que apunte a esa dirección. Esta guía usa
headscale.example.com. - Un segundo dominio o subdominio para MagicDNS. Esta guía usa
tailnet.example.net. No debe ser el mismo dominio que el deserver_url. - Un equipo cliente para conectar, con Linux, macOS, Windows, Android o iOS.
Instalar headscale desde el paquete .deb oficial
El proyecto publica paquetes .deb en su página de versiones de GitHub. En octubre de 2026, la versión actual es 0.29.4. Compruebe primero la arquitectura, porque el nombre del archivo la incluye.
sudo apt update
sudo apt install -y wget
dpkg --print-architectureEn un VPS x86 normal, este comando muestra amd64; en un plan basado en Ampere o Graviton, muestra arm64. Introduzca el resultado en la variable siguiente.
HEADSCALE_VERSION="0.29.4"
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 versionEl ./ delante del nombre del archivo es obligatorio. Sin él, apt busca en sus repositorios un paquete llamado headscale.deb y falla.
El paquete crea un usuario del sistema headscale, escribe una configuración predeterminada /etc/headscale/config.yaml, instala una unidad de systemd y habilita e inicia el servicio. Por tanto, headscale ya está en ejecución cuando apt devuelve el control, y sudo systemctl is-active headscale muestra active. Sin embargo, se ejecuta con la configuración incluida, que apunta server_url a http://127.0.0.1:8080. Ningún cliente suyo puede acceder a esa dirección. Todavía no hay nada expuesto, porque el listen_addr incluido también está enlazado a loopback, pero no conecte ningún cliente hasta establecer server_url y reiniciar el servicio. Los dos pasos siguientes hacen ambas cosas.
Configure server_url antes de que se registre ningún cliente
Edite /etc/headscale/config.yaml con sudo nano /etc/headscale/config.yaml, o aplique los mismos tres cambios con sed. Conserve una copia del original, porque el archivo es largo y contiene muchos comentarios, y es la mejor referencia disponible para el resto de la configuración.
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 es la dirección que headscale escribe en el registro de cada cliente. A partir de entonces, los clientes se conectan siempre a esa cadena exacta, por lo que debe ser el nombre público con https:// delante, nunca 127.0.0.1. La documentación de Headscale indica que es posible usar HTTP sin cifrar o HTTPS en un puerto distinto de 443, pero recomienda encarecidamente HTTPS en el puerto 443 para producción, y el resto de esta guía presupone esa configuración.
listen_addr indica dónde se enlaza el proceso. Déjelo en loopback. Un proxy inverso en el mismo servidor termina TLS (seguridad de la capa de transporte) y reenvía las conexiones a ese proceso, por lo que nada externo al servidor necesita acceder al puerto 8080.
base_domain es el sufijo de MagicDNS, el dominio bajo el que los nodos obtienen sus nombres. Debe ser un nombre de dominio completo sin un punto final y debe ser distinto del dominio de server_url, porque, de lo contrario, los dos espacios de nombres entrarían en conflicto.
No modifique la sección de la base de datos. El valor predeterminado es SQLite en /var/lib/headscale/db.sqlite, dentro de un directorio que creó y administra el paquete, y SQLite es suficiente para un tailnet de este tamaño.
Reinicie headscale y confirme que está en ejecución
sudo systemctl restart headscale
sudo systemctl is-active headscale
curl -sS -o /dev/null -w '%{http_code}\\n' http://127.0.0.1:8080/healthis-active muestra active y curl muestra 200. El reinicio es el paso que carga sus cambios. headscale lee su configuración una sola vez, cuando se inicia, por lo que el proceso iniciado por el paquete sigue entregando el server_url proporcionado con el paquete hasta que lo reinicie. sudo systemctl enable --now headscale no realiza esta tarea: el paquete ya ha habilitado e iniciado el servicio, por lo que ese comando no cambia nada y el valor anterior sigue activo. El inicio durante el arranque ya está configurado, y sudo systemctl is-enabled headscale muestra enabled para confirmarlo.
Si is-active muestra algo distinto de active, o curl no muestra 200, consulte el journal con sudo journalctl -u headscale -n 50 --no-pager. Un fallo en esta fase casi siempre se debe al archivo de configuración, porque headscale analiza el archivo completo antes de abrir un socket. Por tanto, una sangría incorrecta o una clave desconocida detiene el proceso antes de que empiece a escuchar. Corrija el archivo y después ejecute sudo systemctl restart headscale. Cualquier cambio posterior en la configuración requiere el mismo reinicio. Los clientes se vuelven a conectar automáticamente después. Si no está familiarizado con las unidades de systemd, ejecutar sus propios servicios y temporizadores con systemd explica los comandos utilizados aquí.
Compruebe los archivos de estado mientras está en el shell:
stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.keyAmbas líneas comienzan con headscale, el usuario sin privilegios que creó el paquete. noise_private.key es la identidad del servidor para sus clientes. Consérvelo. Si lo elimina, headscale genera una nueva identidad y todos los nodos deben registrarse de nuevo.
Configurar TLS delante de headscale
Los clientes deben acceder a server_url mediante HTTPS. Caddy es la opción más sencilla porque solicita y renueva el certificado por sí solo.
sudo apt install -y caddySustituya /etc/caddy/Caddyfile por el bloque de la documentación de 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 muestra adapted config to JSON cuando el archivo se analiza correctamente. Un aviso que indique que el archivo no tiene el formato correcto es sólo estético. Desde su portátil, curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health también debería mostrar 200. Esta comprobación confirma que DNS, el firewall, el certificado y el proxy funcionan conjuntamente.
Este es el detalle del proxy que puede consumir toda una tarde. La conexión de control de Tailscale es una actualización HTTP, se inicia con un POST en lugar de un GET y el valor de la cabecera Upgrade es tailscale-control-protocol. Caddy la reenvía sin configuración adicional. nginx no lo hace, por lo que un front end de nginx debe reenviar explícitamente las cabeceras Upgrade y Connection. La documentación de headscale lo configura mediante un mapa de actualización:
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;
}
}Si omite esas líneas, las peticiones normales siguen funcionando. Por eso /health devuelve 200 y todo parece correcto, mientras la conexión de control persistente nunca se establece y los nodos se registran y después permanecen desconectados. Si elige nginx, Certbot en Ubuntu 24.04 con nginx cubre la parte del certificado.
Qué puertos abrir en UFW
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verboseEl puerto 443 transporta todas las conexiones de control de los clientes a headscale. Si más adelante habilita el relay DERP integrado, también necesitará 3478/udp, que se describe más abajo. El puerto 80 se utiliza para el desafío HTTP de ACME (entorno de gestión automática de certificados) y para la redirección a HTTPS. Caddy puede obtener un certificado aunque el puerto 80 esté cerrado, porque, cuando falla el desafío HTTP, cambia al desafío TLS-ALPN en el puerto 443. Sin embargo, con el puerto 80 abierto están disponibles ambos desafíos y las solicitudes HTTP simples de http:// se redirigen.
El puerto 8080 permanece cerrado. listen_addr es 127.0.0.1:8080, por lo que el proxy llega a headscale mediante la interfaz de loopback y no interviene ninguna regla del firewall. Abrir el puerto 8080 en UFW no expondría nada, porque ningún servicio escucha en él desde el exterior, y tampoco aportaría ninguna ventaja. Tenga en cuenta que la mayoría de los proveedores ejecutan un segundo firewall en su panel de control, independiente de UFW. Por tanto, un puerto puede estar abierto en el servidor y seguir cerrado en el perímetro. Conceptos básicos del firewall UFW en un VPS explica con más detalle la sintaxis de las reglas.
Crear un usuario y una clave de preautenticación
sudo headscale users create alice
sudo headscale users listEl comando headscale es un cliente. Se comunica con el daemon en ejecución mediante el socket Unix ubicado en /var/run/headscale/headscale.sock, que tiene el modo 0770 y pertenece al grupo headscale. Esto implica dos cosas. El comando falla si el servicio está detenido, que es otra razón por la que importa el orden de esta guía, y necesita sudo, a menos que añada su propia cuenta al grupo headscale.
users list muestra un ID junto a cada nombre. Necesita ese número porque el comando de claves acepta un ID de usuario numérico, no un nombre.
sudo headscale preauthkeys create --user 1 --expiration 24hLa clave se muestra una sola vez. Cópiela ahora. Una clave de preautenticación se puede usar una sola vez y es válida durante una hora, a menos que indique lo contrario, por lo que conviene configurar --expiration 24h mientras aún está realizando pruebas. Añada --reusable para crear una clave que inscriba varias máquinas y trátela como una contraseña, porque cualquiera que la tenga puede unirse a su red.
Conecta tu primer cliente con --login-server
En la máquina que quieres incorporar:
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 muestra la dirección que headscale asignó, por ejemplo, 100.64.0.1. De vuelta en el servidor, sudo headscale nodes list muestra el nodo con su ID, su usuario y su estado en línea.
El valor de --login-server debe coincidir exactamente con server_url, incluido el esquema y sin una barra final. Se comparan como cadenas. Si no coinciden, el cliente se registra contra una dirección y después recibe instrucciones para comunicarse con otra.
Una máquina que ya había iniciado sesión en el servicio alojado de Tailscale conserva esa sesión. Ejecuta primero sudo tailscale logout y después tailscale up con --login-server.
Si omites --auth-key, el cliente muestra una URL. Ábrela. La página muestra el identificador de ese intento de registro, que debes aprobar en el servidor:
sudo headscale auth register --user alice --auth-id PASTE-THE-ID-FROM-THE-PAGEEste formulario resulta más cómodo para tu propio portátil. Las claves de preautorización son mejores para cualquier proceso automatizado, porque no requieren que una persona esté pendiente. Cuando el propio VPS ya es un nodo, también puede transportar el tráfico de Internet de tus otras máquinas. Esto corresponde a la configuración del nodo de salida, con la diferencia de que debes aprobar la ruta anunciada en el servidor mediante el comando headscale, en lugar de hacerlo en una consola de administración alojada. Si quieres acceder a una red privada situada detrás de ese VPS, en vez de usarlo como salida a Internet, el mismo paso de aprobación permite anunciar esa subred al resto de tu tailnet. Publicar una aplicación desde un nodo, en lugar de enrutar redes completas a través de él, es otra tarea. serve y funnel son las dos formas de hacerlo, aunque ambos dependen de los certificados y del mecanismo de entrada propios de Tailscale. Por tanto, considéralos funciones de una tailnet alojada, no algo que headscale proporcione directamente.
DERP y retransmisión del tráfico cuando falla la ruta directa
DERP (designated encrypted relay for packets) es la ruta de respaldo. Cuando dos nodos no pueden abrir una conexión directa de WireGuard, normalmente porque ambos están detrás de NAT estricta (network address translation), envían los paquetes a través de un relay. El relay no tiene claves, por lo que no puede leer el tráfico. Sí puede ver qué nodos se comunican y cuántos datos transfieren.
Debe quedar claro qué hace la configuración predeterminada. Headscale se distribuye apuntando a https://controlplane.tailscale.com/derpmap/default con auto_update_enabled: true y update_frequency: 3h, por lo que el plano de control es suyo, mientras que los relays son de Tailscale. Para la mayoría de los usuarios, es una compensación razonable. Si no lo es, ejecute uno propio.
Para ejecutar su propio relay, configure enabled: true dentro de derp.server en config.yaml, reinicie headscale y abra el puerto STUN (session traversal utilities for NAT) con sudo ufw allow 3478/udp. El archivo de configuración indica claramente el requisito: server_url debe usar https, porque DERP requiere TLS. Si vacía la lista derp.urls, elimina los relays de Tailscale del mapa. Si lo hace sin un relay integrado operativo, cualquier par de nodos que no pueda conectarse directamente no podrá conectarse en absoluto.
Desde un cliente, tailscale netcheck muestra la latencia de cada región de relay que conoce, y tailscale status marca cada par como direct con una dirección o relay con un código de región. Un par atascado en relay tiene un problema de NAT, no de headscale. Un par que está direct y sigue siendo lento plantea otra cuestión, y la respuesta habitual en ese caso es MTU, no el túnel en sí.
¿Por qué un nodo aparece sin conexión?
El proxy está descartando la actualización. Esta es la causa más habitual. Su característica es que todo lo demás parece correcto: /health devuelve 200, headscale nodes list muestra el nodo y el nodo nunca pasa a estar conectado. La conexión de control es un POST que contiene Upgrade: tailscale-control-protocol. Un proxy que no lo reenvía elimina el único canal que informa del estado del nodo. Compare la configuración de nginx con las líneas de actualización anteriores o cambie a Caddy para descartar el proxy.
server_url cambió después de registrar los nodos. Los nodos siguen conectándose al valor que recibieron durante el registro. Si lo modificó, ejecute sudo tailscale up --login-server https://headscale.example.com --force-reauth en cada nodo.
El cliente no se está ejecutando. En el nodo, ejecute sudo systemctl is-active tailscaled y sudo journalctl -u tailscaled -n 50 --no-pager. Un cliente que no puede resolver su dominio o conectarse a él registra allí sus reintentos.
La clave caducó. Se explica en la sección siguiente.
Para supervisar el servidor mientras realiza las pruebas, ejecute sudo journalctl -u headscale -f en el VPS y reinicie tailscaled en el cliente. Un nodo que llega a headscale genera líneas de registro inmediatamente. Si no aparece ninguna, la solicitud no está llegando. Revise DNS, el firewall y el proxy antes de revisar headscale.
Expiración de claves y el nodo que deja de funcionar semanas después
Existen dos tipos de expiración independientes. Confundirlos hace perder tiempo.
Las claves de preautenticación caducan rápidamente por diseño. El valor predeterminado es una hora y un uso. Si tailscale up rechaza la clave, genere una nueva en el servidor en lugar de editar nada en el cliente.
Las claves de nodo son la parte de larga duración. La sección node de config.yaml establece expiry: 0, y 0 significa que no hay una expiración predeterminada: un nodo registrado sigue siendo válido hasta que lo expire. Los nodos etiquetados nunca caducan. Establezca expiry: 180d si quiere que los registros caduquen automáticamente y tenga claro lo que implica: todos los nodos no etiquetados necesitarán sudo tailscale up --login-server https://headscale.example.com --force-reauth con esa periodicidad, y un servidor sin interfaz que nadie vuelva a autenticar desaparecerá de la red por sí solo.
Hágalo manualmente cuando alguien pierda un portátil. sudo headscale nodes list le proporciona el ID, después sudo headscale nodes expire -i 3 cierra la sesión de ese nodo y sudo headscale nodes delete -i 3 lo elimina por completo de la red.
Copias de seguridad y actualizaciones
/var/lib/headscale y /etc/headscale forman todo el servidor. Detenga el servicio antes de copiarlos, porque SQLite puede tener escrituras en curso y una base de datos copiada bajo carga puede quedar en un estado incoherente.
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-*.tgzCopie ambos archivos fuera del servidor. Contienen las claves privadas y todos los registros, por lo que requieren el mismo cuidado que el propio servidor. copias de seguridad con restic desde un VPS explica cómo hacerlo de forma programada y cifrada.
Las actualizaciones repiten la instalación: descargue el nuevo .deb, sudo apt install ./headscale.deb, reinicie y vuelva a ejecutar las comprobaciones is-active y /health. Desde 0.29, la ruta de actualización es estricta. No se permite omitir una versión menor ni volver a una versión menor anterior. Actualice una versión menor cada vez, haga una copia de seguridad antes de cada paso y lea primero las notas de la versión correspondiente, porque esa misma versión modificó el comportamiento de la política ACL y cambió varias claves de configuración.
FAQ
¿headscale ya está en ejecución justo después de instalar el archivo .deb?
Sí. El paquete habilita e inicia el servicio en cuanto se instala, con el /etc/headscale/config.yaml incluido, que sólo escucha en loopback y establece server_url en http://127.0.0.1:8080. Esa dirección no sirve para los clientes, por lo que debe editar server_url, listen_addr y base_domain, ejecutar sudo systemctl restart headscale y confirmarlo con sudo systemctl is-active headscale. Tiene que ser restart: headscale sólo lee el archivo cuando se inicia, y ejecutar enable --now en un servicio que ya está en ejecución no cambia nada. Si falla después del reinicio, sudo journalctl -u headscale -n 50 --no-pager indica el problema. En esta fase casi siempre se trata de un error de YAML, porque headscale analiza todo el archivo antes de enlazar un puerto.
¿Tengo que instalar también el cliente normal de Tailscale en mis equipos?
Sí. Headscale sólo sustituye al servidor de control. Cada nodo ejecuta el cliente oficial de Tailscale y se configura para usar el servidor mediante sudo tailscale up --login-server https://headscale.example.com. Esa opción existe en el cliente estándar, por lo que no es necesario modificarlo ni volver a compilarlo.
¿Mi tráfico pasa por el servidor de headscale?
Normalmente no. Headscale coordina la red y distribuye claves y direcciones, mientras que la ruta de datos usa WireGuard directamente entre los nodos. El tráfico sólo toma un desvío cuando dos nodos no pueden comunicarse directamente y recurren a un relay DERP. Con la configuración incluida, esos relays son los públicos de Tailscale. Ejecute tailscale status en un nodo para comprobar si un peer determinado está direct o en un relay.
¿Por qué mi nodo sigue sin conexión después de registrarse?
Un nodo que aparece en headscale nodes list pero nunca pasa a estar en línea normalmente ha perdido la conexión de control en el reverse proxy. Esa conexión es una actualización HTTP enviada como POST con la cabecera Upgrade: tailscale-control-protocol. nginx la descarta si no se reenvían explícitamente las cabeceras Upgrade y Connection mediante proxy_set_header, que la configuración anterior realiza en su bloque map $http_upgrade $connection_upgrade. Caddy la reenvía sin configuración adicional, por lo que permite comprobar rápidamente si el problema está en el proxy.
¿Necesito un nombre de dominio y TLS para headscale?
En la práctica, sí. Los clientes se conectan a la cadena que se indica en server_url, los certificados se emiten para nombres y no para direcciones IP sin nombre, y el archivo de configuración establece que DERP requiere TLS. Un dominio y Caddy se configuran en unos cinco minutos y proporcionan un endpoint HTTPS que se renueva automáticamente. Es posible usar HTTP sin cifrar, pero la documentación de headscale recomienda encarecidamente HTTPS en el puerto 443 para producción y señala que el cliente de Tailscale da por supuesto HTTPS en el puerto 443 en determinadas situaciones.