Por qué auto-alojar el servidor de control
Tailscale es un software excepcional: el rendimiento de WireGuard, clientes multiplataforma y un magic DNS que funciona a la primera. Sin embargo, no transporta su tráfico: los paquetes WireGuard circulan directamente de un nodo a otro, cifrados de extremo a extremo. Lo que se ejecuta en la nube de Tailscale es el plano de control, el servidor de coordinación que intercambia las claves públicas, hace que los pares se descubran, asigna las direcciones 100.x.y.z, publica los nombres MagicDNS y distribuye las ACL y el mapa de relés DERP. Sin él, los nodos no se encuentran. Headscale reimplementa ese servidor de coordinación en Go de código abierto, con el mismo protocolo, para que usted pueda ejecutarlo en su propia infraestructura: sus clientes Tailscale solo cambian de URL de conexión.
Más allá del tope de 6 usuarios del plan gratuito, la principal razón para auto-alojarlo es la residencia de los datos: el plano de control sabe qué máquinas están en su red, sus IP, sus nombres y sus políticas de ACL. Ejecutar Headscale significa que esa información se queda en su VPS, y no en la nube de otra persona, y que su red ya no depende de una decisión de precios ni de una caída en un tercero.
Lo que le aporta Headscale auto-alojado
- Usuarios y dispositivos sin tope: ningún límite impuesto por el software ni tarifas por puesto; su red mallada crece con su equipo, limitada solo por los recursos del VPS.
- Los clientes oficiales de Tailscale en todas partes: Linux, macOS, Windows, iOS, Android; no hace falta ningún fork propio ni cliente alternativo.
- Túneles WireGuard punto a punto: Headscale solo gestiona el intercambio de claves y la distribución de las ACL; todo el tráfico real circula directamente por WireGuard entre los pares.
- Magic DNS: resuelva cada nodo de su red mallada por su nombre de host (
portatil.mesh.su-dominio.com) sin tocar su DNS público. - Políticas HuJSON: el mismo formato de archivo de política que Tailscale, con ACL y Grants; defina qué usuarios y qué dispositivos pueden acceder a qué servicios.
- Compatibilidad con los relés DERP: use el mapa DERP público de Tailscale para atravesar el NAT, o active el servidor DERP integrado en Headscale para mantener también los relés en casa.
- API y CLI headscale: cree usuarios, emita claves de preautenticación y gestione los nodos mediante programación.
- Registro sin cuenta en tailscale.com: sus colaboradores se unen a la red con una clave que usted genera, o a través del proveedor OpenID Connect que ya utiliza (Keycloak, Authelia, Google Workspace…).
- Subredes, exit nodes, Taildrop y Tailscale SSH: el servidor admite las funciones básicas de Tailscale.
- Menos de 50 MB de RAM en reposo: funciona con holgura en un VPS de 1 GB junto a otras cargas de trabajo.
Requisitos previos
Un VPS con al menos 1 vCPU y 512 MB de RAM (1 GB deja margen al sistema) bajo Ubuntu 22.04 o posterior, o Debian 12 o posterior: son las distribuciones a las que apuntan los paquetes oficiales. Para la vía Docker, instale Docker y Docker Compose v2. Hace falta una IP pública; se recomienda una configuración de doble pila IPv4 + IPv6.
Un nombre de dominio que apunte al VPS es obligatorio: los clientes Tailscale se conectan al servidor de control por URL, y el proyecto recomienda encarecidamente servir Headscale en HTTPS por el puerto 443, ya que el cliente Tailscale da por hecho ese puerto en ciertas situaciones. Prevea un subdominio para el servidor (vpn.su-dominio.com) y otro distinto para los nombres MagicDNS (mesh.su-dominio.com): Headscale rechaza que el dominio MagicDNS sea el de server_url.
Los puertos que hay que abrir en el VPS
- TCP 443: obligatorio. Por HTTPS se registran los clientes y reciben el mapa de la red; es el único puerto imprescindible para el servidor de coordinación.
- TCP 80: solo para la validación Let's Encrypt HTTP-01 y la redirección a HTTPS.
- UDP 3478: solo si activa el servidor DERP integrado, que lo necesita para STUN. Con el mapa DERP público de Tailscale no hace falta abrirlo.
- TCP 8080 (Headscale), 9090 (métricas) y 50443 (gRPC): déjelos en
127.0.0.1. El reverse proxy habla con 8080 en local; no exponga nunca la interfaz de administración gRPC si no la necesita.
Desplegar Headscale con Docker Compose
Redactar el archivo Compose
La stack solo tiene un servicio: la imagen oficial
docker.io/headscale/headscale:0.29.7, última versión estable a 7 de noviembre de 2026, lanzada con el comandoserve. Headscale guarda todo (claves, base de datos, registros de los nodos) en dos carpetas del host: la configuración, montada en solo lectura en/etc/headscale, y los datos, en/var/lib/headscale. No hace falta ningún servicio externo de base de datos ni de caché; SQLite viene integrado.mkdir -p /opt/headscale/config /opt/headscale/lib cat > /opt/headscale/compose.yaml <<'EOF' services: headscale: image: docker.io/headscale/headscale:0.29.7 container_name: headscale restart: unless-stopped read_only: true tmpfs: - /var/run/headscale ports: - "127.0.0.1:8080:8080" volumes: - /opt/headscale/config:/etc/headscale:ro - /opt/headscale/lib:/var/lib/headscale command: serve healthcheck: test: ["CMD", "headscale", "health"] EOFEl puerto solo se publica en
127.0.0.1: es el reverse proxy el que da la cara a Internet.Generar la configuración
Parta del archivo de ejemplo de la versión exacta que despliega y modifique solo las claves siguientes.
server_urles la URL HTTPS a la que se conectarán sus clientes Tailscale;listen_addrdebe escuchar en0.0.0.0dentro del contenedor. La base de datos se declara bajodatabase(type: sqlite). Solo queda indicar la ruta de la clave noise: Headscale la genera en el primer arranque si el archivo no existe.curl -Lo /opt/headscale/config/config.yaml \ https://raw.githubusercontent.com/juanfont/headscale/v0.29.7/config-example.yamlserver_url: https://vpn.su-dominio.com listen_addr: 0.0.0.0:8080 noise: private_key_path: /var/lib/headscale/noise_private.key database: type: sqlite sqlite: path: /var/lib/headscale/db.sqlite dns: magic_dns: true base_domain: mesh.su-dominio.comEn ServOrbit, el job de aprovisionamiento escribe
config.yamla partir de su dominio automáticamente.Arrancar el contenedor y verificar
Ejecute
docker compose up -ddesde/opt/headscale. Headscale genera su clave noise y luego se pone a escuchar en el puerto 8080. Compruebe el endpoint de salud:curl -s http://127.0.0.1:8080/healthdebe devolver{"status":"pass"}. La imagen se basa en una base distroless, sin shell, pero la CLI headscale se invoca directamente:docker exec headscale headscale versionconfirma la versión en ejecución ydocker logs --follow headscalemuestra los registros.Crear un usuario y generar una clave de autenticación
Headscale organiza los dispositivos en «usuarios» (antes llamados namespaces). Cree uno y anote su identificador numérico: la opción
--userdepreauthkeys createespera ese ID, no el nombre.docker exec headscale headscale users create myteam docker exec headscale headscale users list docker exec headscale headscale preauthkeys create --user 1 --reusable --expiration 24hSin
--reusable, la clave solo sirve una vez, y sin--expirationcaduca al cabo de una hora. Copie la clave que se muestra: la usará para registrar cada dispositivo.Registrar un dispositivo
En cualquier dispositivo que tenga el cliente oficial de Tailscale, apúntelo a su instancia de Headscale:
tailscale up --login-server https://vpn.su-dominio.com --authkey <su-clave-preauth>. El dispositivo aparece en la lista de nodos de Headscale en unos segundos:docker exec headscale headscale nodes list. Sin clave, el cliente abre una página que muestra un identificador de autenticación; apruébelo en el servidor condocker exec headscale headscale auth register --user myteam --auth-id <AUTH_ID>. Una vez registrados, los dispositivos se comunican por su IP de malla (100.64.x.y) o por su nombre de host de magic DNS.Definir las políticas de control de acceso
Mientras no se cargue ninguna política, Headscale deja pasar todo el tráfico entre los nodos: aceptable para un equipo pequeño de confianza, no cuando conviven portátiles de desarrolladores, proveedores externos y servidores de producción. Cree
/opt/headscale/config/policy.hujsony declárelo enconfig.yamlconpolicy.path: /etc/headscale/policy.hujson. Headscale usa el mismo formato HuJSON que Tailscale: defina grupos de usuarios, etiquete los dispositivos como servidores o estaciones de trabajo y escriba reglas que precisen qué grupos pueden alcanzar qué puertos. El proyecto recomienda los Grants antes que las ACL, que Tailscale considera heredadas. Valide el archivo conheadscale policy check --file <archivo>y luego recargue: la política solo se relee al recargar (docker kill --signal=HUP headscale, osystemctl reload headscaleen una instalación nativa). Versione este archivo en un repositorio Git privado para conservar el historial de accesos.Conectarse por primera vez
Headscale no tiene interfaz web ni cuenta: es el servidor de control de su red privada, y se pilota desde la línea de comandos. Por SSH:
docker exec -it headscale headscale users create monuser, después la clave conpreauthkeys create --user <ID> --reusable --expiration 24h, y conecte sus máquinas contailscale up --login-server https://<su-dominio> --authkey <clave>. Las aplicaciones gráficas también pueden apuntar a un servidor personalizado: en macOS, Opción + clic en el icono de Tailscale, menú Debug y luegoCustom Login Server; en iOS,Use custom coordination serveren el menú de inicio de sesión; en Android,Use an alternate server; en Windows,tailscale login --login-serveren PowerShell.
Sin Docker: el paquete .deb oficial
El proyecto publica paquetes .deb para Debian y Ubuntu, y los recomienda en esas distribuciones: crean el usuario de sistema headscale, incluyen una configuración por defecto e instalan la unidad systemd. Es la vía más sencilla si Headscale está solo en su VPS o si prefiere un servicio nativo a un contenedor. Los comandos de gestión son los mismos que arriba, sin el prefijo docker exec headscale.
Instalar Headscale con el paquete .deb
Descargar e instalar el paquete
Descargue el paquete de la versión deseada desde la página de releases de GitHub, para su arquitectura (
amd64oarm64), e instálelo conapt, que resuelve las dependencias:HEADSCALE_VERSION="0.29.7" 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 ./headscale.debCompruebe después:
headscale versiondebe mostrar0.29.7.Adaptar la configuración
El paquete deja la configuración en
/etc/headscale/config.yamly un ejemplo actualizado en/usr/share/doc/headscale/examples/config-example.yaml. Modifique las mismas claves que para Docker (server_url,database,dns.base_domain), pero mantengalisten_addr: 127.0.0.1:8080: el servicio se ejecuta en el host y solo el reverse proxy debe alcanzarlo. Los datos residen en/var/lib/headscale.Reiniciar y verificar el servicio
Reinicie el servicio para que relea la configuración y compruebe su estado y sus registros:
sudo systemctl restart headscale sudo systemctl status headscale sudo journalctl -u headscale -fLa salida debe mostrar
Active: active (running). En el primer arranque, los errores más frecuentes son unaserver_urlsin el esquemahttps://y un dominio MagicDNS idéntico al del servidor.Usar la CLI sin ser root
La CLI habla con el servicio a través del socket Unix
/var/run/headscale/headscale.sock, al que por defecto solo acceden el usuarioheadscaley root. Usesudo, ejecute los comandos como usuarioheadscaleo añada su cuenta al grupoheadscale. Compruebe el acceso conheadscale users list.
Servir Headscale en HTTPS detrás de Nginx
Los clientes Tailscale hablan con Headscale con su propio protocolo, transportado por una conexión WebSocket abierta con POST, cuya cabecera Upgrade vale tailscale-control-protocol. Por eso el reverse proxy debe transmitir las cabeceras Upgrade y Connection tal cual y no almacenar las respuestas en búfer. La configuración siguiente sigue el ejemplo de la documentación del proyecto; obtenga antes el certificado con certbot (certbot certonly --nginx -d vpn.su-dominio.com).
map $http_upgrade $connection_upgrade {
default keep-alive;
'' close;
}
server {
listen 80;
server_name vpn.su-dominio.com;
location = /generate_204 { return 204; }
location / { return 301 https://$server_name$request_uri; }
}
server {
listen 443 ssl;
http2 on;
server_name vpn.su-dominio.com;
ssl_certificate /etc/letsencrypt/live/vpn.su-dominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vpn.su-dominio.com/privkey.pem;
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_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_pass http://127.0.0.1:8080;
}
}Deje vacíos tls_cert_path y tls_key_path en config.yaml: Nginx termina TLS, así que el aviso listening without TLS al arrancar es lo esperado. Para que los registros de Headscale muestren la IP real de los clientes, declare el proxy como origen de confianza. En una instalación nativa basta con el bucle local; en Docker, añada también la red del puente Docker (docker network inspect), porque el contenedor ve llegar el proxy desde su pasarela:
trusted_proxies:
- 127.0.0.1/32
- ::1/128Caddy también funciona, con un simple reverse_proxy 127.0.0.1:8080. En cambio, el proxy de Cloudflare y Cloudflare Tunnel no son compatibles, porque no gestionan los WebSocket en POST: el registro DNS del servidor debe quedarse en modo solo DNS.
Combinar Headscale con Authelia para un acceso de red protegido con MFA
Headscale gestiona el control de acceso a nivel de red (qué IP pueden comunicarse con qué pares), pero no aplica autenticación a nivel de aplicación. Combínelo con Authelia en el mismo VPS: haga pasar sus servicios internos (accesibles únicamente a través de la malla) por la capa forward-auth de Authelia. Los usuarios deben validar un MFA para acceder a los servicios, incluso una vez dentro de la malla: defensa en profundidad para las cargas sensibles.
Comprobar que los nodos se ven
Un nodo que aparece en headscale nodes list solo demuestra que ha llegado al servidor de coordinación. Antes de hacer pasar tráfico real por la malla, compruebe el camino de extremo a extremo desde los propios clientes.
Controlar la malla desde un cliente
Leer el estado de la red
En un nodo registrado, ejecute
tailscale status. El comando lista los pares con su IP de malla, su nombre y el tipo de enlace:directsignifica que el túnel WireGuard está establecido sin intermediario,relayque pasa por un relé DERP, funcional pero con una latencia mayor.Probar la conectividad
Tome la IP de un par en
tailscale statusy luegoping 100.64.0.2, o mejortailscale ping <nombre-del-nodo>, que indica si la respuesta llega en directo o por un relé. Si el par aparece pero no responde,tailscale netcheckmide la latencia hacia cada región DERP y señala un UDP saliente filtrado.Probar la resolución MagicDNS
Con
dns.magic_dns: true, cada nodo responde a su nombre corto y a su nombre completo:ping nombre-del-nodooping nombre-del-nodo.mesh.su-dominio.com. No hace falta ningún registro DNS público para estos nombres internos. Si la resolución falla,tailscale dns statusmuestra la configuración DNS que el cliente ha recibido del servidor.
Caso de uso: SSH entre VPS sin exponer el puerto 22
Una vez registrados sus VPS de desarrollo y de producción en la misma malla, SSH ya no necesita escuchar en la interfaz pública: la conexión usa la dirección de malla, dentro del túnel WireGuard. Mantenga abierto un acceso de consola en su proveedor antes de tocar el cortafuegos, por si una regla le deja fuera.
Restringir SSH a la red mallada
Localizar la interfaz y la dirección de malla
En cada VPS, el cliente Tailscale crea la interfaz
tailscale0.tailscale ip -4muestra la dirección de malla de la máquina; anote la del servidor de producción, por ejemplo100.64.0.3.Permitir SSH solo por tailscale0
En el VPS de producción, permita SSH en la interfaz de malla antes de denegarlo en el resto: UFW aplica la primera regla que coincide.
ufw allow in on tailscale0 to any port 22 proto tcp ufw deny 22/tcp ufw enable ufw status verboseEl puerto 22 ya no es accesible desde Internet, pero sigue abierto para todos los nodos de la malla que su política autorice.
Conectarse a través de la malla
Desde el VPS de desarrollo o su equipo registrado, conéctese por la IP o el nombre MagicDNS:
ssh [email protected]ossh [email protected]. Todo transita dentro del túnel cifrado; después, restrinja en la política de Headscale qué grupos pueden alcanzar el puerto 22 de este servidor.
Migrar un equipo que ya usa Tailscale
El cambio no requiere ninguna reinstalación: el cliente oficial acepta la opción --login-server, así que basta con desconectarlo de la red antigua y volver a conectarlo a su servidor. Cada máquina se corta unos segundos durante la operación. Como su servidor asigna él mismo las direcciones 100.x y los nombres MagicDNS, prevea actualizar los scripts, las reglas de cortafuegos y los archivos de configuración que citan los antiguos.
Pasar los clientes a Headscale
Recuperar la política de acceso
Copie el archivo de política de su tailnet desde el editor Access controls de la consola de Tailscale y parta de él: el formato HuJSON es el mismo. Revíselo con
headscale policy checkantes del cambio: algunas funciones, como los IP sets, no son compatibles, y solo existe un subconjunto de los autogroups.Desconectar y volver a conectar cada cliente
En Linux o desde la línea de comandos, encadene la desconexión y la reconexión con una clave de preautenticación:
tailscale logout tailscale up --login-server https://vpn.su-dominio.com --authkey <PREAUTHKEY>En macOS, iOS, Android y Windows, cierre la sesión de la cuenta en la aplicación y añada después el servidor personalizado como se ha descrito más arriba.
Empezar por las máquinas de prueba
Pase primero un equipo y un servidor de prueba, compruebe
tailscale statusy la resolución de nombres, luego las máquinas de desarrollo y, en último lugar, la producción. Mientras queden máquinas al otro lado, las dos redes no se ven: planifique el cambio de un servicio y de sus clientes en la misma ventana.Cerrar o rebajar la cuenta de Tailscale
Una vez migrados y verificados todos los nodos, cierre el tailnet o vuelva al plan Personal si mantiene un uso personal. Genere una clave de preautenticación nueva para los próximos registros en lugar de conservar indefinidamente la de la migración.
Headscale frente a Tailscale Cloud
Tailscale Cloud sigue siendo la vía más sencilla para los equipos pequeños: ningún servidor que mantener, actualizaciones automáticas y facturación integrada, y el plan Personal sigue siendo gratuito hasta 6 usuarios. Más allá, el plan Standard figura a 8 $ por usuario y mes en la tabla de precios vigente desde el 8 de abril de 2026: 40 $ al mes para cinco personas, 80 $ para diez. Headscale cuesta el precio del VPS, sea cual sea la plantilla (en ServOrbit, el VPS Solo (2 vCPU / 4 GB) a 99 DH/mes basta para decenas de nodos), más el tiempo de explotación: actualizaciones, copias de seguridad, supervisión.
Headscale cobra, por tanto, todo su sentido por encima de 6 usuarios, cuando la residencia de los datos obliga a mantener la topología de su red fuera de servidores de terceros, o cuando quiere una auditabilidad completa de sus logs de coordinación. Los clientes son idénticos: pasar de Tailscale Cloud a Headscale se reduce a volver a ejecutar tailscale up --login-server en cada dispositivo.
Tailscale Personal, Tailscale Standard y Headscale
Desplace la tabla
| Criterio | Tailscale Personal | Tailscale Standard | Headscale en un VPS |
|---|---|---|---|
| Precio | Gratuito | 8 $ por usuario y mes | El coste del VPS |
| Usuarios | Hasta 6 | Sin tope | Sin límite de software |
| Dispositivos de los usuarios | Sin tope | Sin tope | Sin límite de software |
| Servidor de coordinación | Nube de Tailscale | Nube de Tailscale | Su VPS, bajo su control |
| Cliente | Cliente oficial de Tailscale | Cliente oficial de Tailscale | Cliente oficial de Tailscale (`--login-server`) |
| MagicDNS, ACL, Grants | Sí | Sí | Sí, en su propio dominio |
| Funnel y Serve | Ofrecidos por Tailscale | Ofrecidos por Tailscale | No compatibles |
| Mantenimiento | Ninguno (servicio gestionado) | Ninguno (servicio gestionado) | Actualizaciones, copias de seguridad, supervisión |
Lo que Headscale no sustituye
Headscale implementa el servidor de coordinación, no toda la plataforma comercial. Su propia documentación enumera lo que falta: Tailscale Funnel (publicar un servicio local en Internet), Tailscale Serve (compartirlo en el tailnet mediante un proxy local) y los registros de flujos de red no son compatibles; los grupos OIDC no pueden usarse en las ACL. Tampoco hay consola web oficial: todo pasa por la CLI o la API. Si su uso se limita a la conectividad en malla, a MagicDNS, a las políticas de acceso, a las subredes y a los exit nodes, Headscale cubre la necesidad; si usa activamente Funnel o Serve, sopese esas funciones frente al ahorro antes de migrar, o eche un vistazo a NetBird, que también auto-aloja su interfaz de administración.
Copias de seguridad y actualizaciones
- Haga una copia antes de cada actualización: Headscale aplica sus migraciones de base de datos al arrancar la nueva versión. Detenga el servicio, copie la configuración y los datos y vuelva a arrancarlo:
cd /opt/headscale && docker compose stop,cp -aR /opt/headscale /opt/headscale.backup-$(date +%Y%m%d%H%M%S),docker compose up -d. - La carpeta de datos contiene
db.sqliteynoise_private.key: perder esa clave cambia la identidad del servidor y obliga a todos los nodos a registrarse de nuevo. Cópiela también fuera del VPS, por ejemplo en un almacenamiento de objetos. - Suba de versión en versión: el proyecto exige pasar por cada versión menor estable (por ejemplo
0.27, luego0.28, luego0.29), siempre en su último parche, sin saltarse ninguna. - Lea la nota de versión en GitHub antes de cada actualización: señala los cambios de configuración incompatibles. Después compare su
config.yamlcon el archivo de ejemplo de la nueva versión.
Resolución de problemas: los errores más frecuentes
- El cliente no consigue registrarse: compruebe que
https://vpn.su-dominio.com/healthresponde desde fuera, queserver_urlcoincide exactamente con el dominio del certificado y que el proxy transmite bien las cabecerasUpgradeyConnection. - Conexiones en
relayen lugar dedirect: un NAT estricto o un cortafuegos bloquea el UDP entre los dos nodos. Ejecutetailscale netchecken ambos lados para ver qué se filtra; el tráfico pasa igualmente, por los relés DERP. - Nodo listado como
offline: el cliente llegó al servidor pero no mantiene la sesión. Revisesystemctl status tailscaledyjournalctl -u tailscaleden el nodo; un cortafuegos local que corta las conexiones salientes es la causa más habitual. - Headscale se niega a arrancar tras una actualización: se ha renombrado o retirado una clave de configuración. El registro la nombra; consulte el archivo de ejemplo de la versión instalada.
- MagicDNS no resuelve: compruebe que
dns.magic_dnsestá entruey quedns.base_domaindifiere del dominio deserver_url, y vuelva a conectar el cliente para que recupere la nueva configuración DNS.