Tailscale ha popularizado las VPN de malla: en lugar de un servidor central por el que pasa todo el tráfico, cada dispositivo habla directamente con los demás sobre WireGuard. El truco es que Tailscale necesita un servidor de coordinación en la nube (el "control server") que sabe qué nodos existen y les entrega las claves. Headscale reimplementa ese servidor para que puedas alojarlo tú, sin cuentas en la nube de Tailscale ni sus límites de plan. En esta guía lo levantas con Docker Compose, preparas el archivo config.yaml, registras tu primer nodo y, si quieres, le añades un panel web. La misma explicación está disponible en inglés.

Puntos clave

  • Headscale es el servidor de control de Tailscale reescrito como software libre y autoalojado. Su versión estable es la 0.29.2, publicada el 1 de julio de 2026, y acumula unas 41.800 estrellas en GitHub con licencia BSD-3-Clause.
  • Sustituye únicamente al plano de control (coordinación y claves); los túneles siguen siendo WireGuard punto a punto entre tus dispositivos, que instalan el cliente oficial de Tailscale apuntando a tu servidor.
  • El contenedor escucha en el puerto 8080 (API y registro), expone métricas en el 9090 y gRPC en el 50443. Reparte direcciones del rango 100.64.0.0/10 (IPv4) y fd7a:115c:a1e0::/48 (IPv6).
  • Toda la configuración vive en config.yaml y el estado en una base de datos SQLite dentro de /var/lib/headscale; fija la imagen en headscale/headscale:0.29.2 y nunca en :latest.
  • Headscale no trae interfaz gráfica de serie: la administración es por línea de comandos, pero puedes añadir un panel de terceros como headscale-ui (unas 2.700 estrellas) montado en la misma máquina.

¿Qué es Headscale y qué relación tiene con Tailscale?

Tailscale es una VPN de malla construida sobre WireGuard: instalas su cliente en cada dispositivo y todos se ven entre sí como si estuvieran en la misma red local, sin abrir puertos ni configurar túneles a mano. Para orquestar esa malla, Tailscale usa un servidor de coordinación (el plano de control) alojado en su nube: es quien conoce la lista de nodos, reparte las direcciones y distribuye las claves públicas para que cada par pueda abrir su túnel directo. El tráfico de datos no pasa por él; solo la coordinación.

Ese plano de control es la única pieza propietaria de Tailscale, y ahí encaja Headscale. El propio proyecto se describe como "una implementación libre y autoalojada del servidor de control de Tailscale". En la práctica, Headscale ocupa el lugar de ese servidor en la nube: tus dispositivos siguen usando el cliente oficial de Tailscale, pero registrado contra tu propio Headscale en vez de contra login.tailscale.com. Ganas control y privacidad, y te quitas los límites del plan gratuito de Tailscale, que restringe a 3 usuarios y 100 dispositivos.

El coste es que asumes la administración: no hay consola web oficial, las políticas de acceso (ACL) se editan a mano y eres tú quien mantiene y respalda el servidor. Por eso Headscale encaja mejor en el escenario del autoalojador que ya gestiona otros servicios. Si vienes del modelo cliente-servidor clásico de WireGuard con wg-easy, Headscale resuelve el caso contrario: una malla entre muchos nodos que se conectan directamente entre sí.

¿Cómo es el docker-compose.yml de Headscale?

Headscale se distribuye como una imagen oficial en Docker Hub y arranca con el subcomando serve. Necesita dos cosas montadas desde el anfitrión: una carpeta de configuración de solo lectura con tu config.yaml, y una carpeta de datos donde guardará la base SQLite y las claves. Crea un directorio para el proyecto y guarda dentro este docker-compose.yml, adaptado del ejemplo oficial:

services:
  headscale:
    image: headscale/headscale:0.29.2
    container_name: headscale
    restart: unless-stopped
    command: serve
    read_only: true
    tmpfs:
      - /var/run
    ports:
      - "127.0.0.1:8080:8080"
      - "127.0.0.1:9090:9090"
    volumes:
      - ./config:/etc/headscale:ro
      - ./lib:/var/lib/headscale
    healthcheck:
      test: ["CMD", "headscale", "health"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  headscale_data:

Conviene entender cada bloque. La imagen se fija en headscale/headscale:0.29.2, una versión real y reciente; evita :latest, porque una actualización de esquema de base de datos entre versiones puede romperte el arranque sin avisar. El subcomando serve es el que levanta el servidor; sin él, el contenedor no hace nada. Los puertos se publican atados a 127.0.0.1 a propósito: el 8080 sirve la API y el registro, y el 9090 las métricas de Prometheus, y ninguno debe quedar expuesto directo a Internet. En su lugar, se accede a Headscale por un proxy inverso con HTTPS, como veremos.

Los dos volúmenes son la clave de la persistencia. ./config monta tu config.yaml en modo solo lectura (:ro), así el contenedor no puede alterarlo. ./lib guarda db.sqlite y las claves privadas del servidor, de modo que sobreviven a reinicios y actualizaciones. Los ajustes read_only: true y tmpfs endurecen el contenedor: el sistema de archivos es inmutable salvo los volúmenes y una zona temporal en memoria. El healthcheck usa el propio binario (headscale health) para que Docker sepa cuándo el servicio está sano. Si es tu primer contenedor en la máquina, repasa antes cómo instalar Docker en Ubuntu 24.04 para tener el motor y el plugin de Compose listos.

¿Qué hay que configurar en el archivo config.yaml?

Antes de arrancar necesitas el config.yaml, porque sin él Headscale se niega a servir. Lo más cómodo es partir del ejemplo oficial y cambiar solo lo imprescindible. Crea las carpetas y descárgalo:

mkdir -p config lib
curl -o config/config.yaml https://raw.githubusercontent.com/juanfont/headscale/v0.29.2/config-example.yaml

Abre config/config.yaml y ajusta estos valores; el resto de los ajustes por defecto sirven para empezar:

server_url: https://vpn.midominio.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443

prefixes:
  v4: 100.64.0.0/10
  v6: fd7a:115c:a1e0::/48
  allocation: sequential

database:
  type: sqlite
  sqlite:
    path: /var/lib/headscale/db.sqlite

dns:
  magic_dns: true
  base_domain: ts.midominio.com

El valor más importante es server_url: es la dirección pública por la que tus nodos alcanzarán al servidor, y debe coincidir con el dominio que sirva tu proxy inverso por HTTPS. El listen_addr va a 0.0.0.0:8080 para que Docker pueda mapear el puerto dentro del contenedor. Los prefixes definen de qué rangos se reparten las IP de la malla: el 100.64.0.0/10 es el espacio CGNAT reservado que también usa la propia Tailscale, así que no chocará con tu red doméstica. Un detalle que suele dar errores: base_domain (el dominio interno de MagicDNS) no puede ser el mismo nombre completo que el de server_url, por eso aquí se usa un subdominio distinto (ts.midominio.com).

Con el archivo listo, levanta el servicio y comprueba los registros:

docker compose up -d
docker compose logs -f headscale

Detrás conviene poner un proxy inverso que termine el TLS y reenvíe al 8080 local; para ello, sigue cómo instalar Traefik con Docker Compose y publica el server_url con un certificado de Let’s Encrypt.

¿Cómo registrar nodos y crear claves preautorizadas?

Headscale organiza los dispositivos por usuarios (antes llamados "namespaces"). El primer paso es crear un usuario, y a partir de la versión 0.29 cada usuario tiene un identificador numérico que verás al listarlos:

docker compose exec headscale headscale users create javier
docker compose exec headscale headscale users list

Hay dos maneras de dar de alta un nodo. La interactiva es la habitual para tus equipos personales. En la máquina cliente, con el cliente de Tailscale ya instalado, ejecutas:

tailscale up --login-server https://vpn.midominio.com

El cliente abre una página con instrucciones y muestra un identificador de autenticación. Copia ese auth-id y apruébalo en el servidor asociándolo a tu usuario:

docker compose exec headscale headscale auth register --user javier --auth-id AUTH_ID

La segunda vía son las claves preautorizadas, pensadas para automatizar (servidores, contenedores, despliegues) sin intervención manual. Se generan indicando el identificador del usuario, y puedes hacerlas reutilizables y con caducidad:

docker compose exec headscale headscale preauthkeys create --user 1 --reusable --expiration 24h

Con esa clave, el nodo se registra solo, sin pasar por el navegador:

tailscale up --login-server https://vpn.midominio.com --authkey TU_CLAVE

A partir de aquí, cada nodo obtiene una IP del rango 100.64.0.0/10 y ve a los demás por su nombre gracias a MagicDNS. Como Headscale solo coordina, los túneles se abren directos entre pares siempre que la red lo permita; cuando dos nodos están tras NAT estrictos y no logran verse, el tráfico cae a un relé DERP. Si quieres entender por qué a veces la conexión es directa y otras pasa por un relé, ayuda tener claros los tipos de red en Docker y cómo funciona el NAT.

¿Cómo añadir un panel web con headscale-ui?

Headscale se administra por línea de comandos, pero si prefieres una interfaz gráfica para ver los nodos, sus IP y su estado, el complemento comunitario más usado es headscale-ui. Se ejecuta como un contenedor aparte y habla con Headscale a través de una clave de API. Primero genera esa clave en el servidor:

docker compose exec headscale headscale apikeys create --expiration 90d

Después añade el servicio a tu docker-compose.yml. headscale-ui solo publica una etiqueta latest de tipo rolling, así que aquí es la única opción práctica; fija el digest de la imagen si quieres reproducibilidad estricta:

  headscale-ui:
    image: ghcr.io/gurucomputing/headscale-ui:latest
    container_name: headscale-ui
    restart: unless-stopped
    ports:
      - "127.0.0.1:9443:8443"

El detalle importante es que headscale-ui debe servirse en la ruta /web bajo el mismo dominio que Headscale para evitar problemas de CORS. Es decir, tu proxy inverso enruta https://vpn.midominio.com/ hacia el 8080 de Headscale y https://vpn.midominio.com/web hacia headscale-ui. Al entrar por primera vez, la interfaz te pedirá la clave de API que generaste; a partir de ahí gestionas usuarios y nodos desde el navegador, aunque las tareas avanzadas (ACL, rutas anunciadas) siguen siendo más cómodas por CLI.

Preguntas frecuentes

¿Necesito el cliente de Tailscale o Headscale trae el suyo?

Necesitas el cliente oficial de Tailscale en cada dispositivo. Headscale solo reemplaza el servidor de coordinación, no el software de los nodos: la magia del túnel WireGuard, el descubrimiento de pares y el reintento por DERP los sigue aportando el cliente de Tailscale. Lo único que cambia es que lo arrancas con tailscale up --login-server apuntando a tu servidor Headscale en lugar de a la nube de Tailscale.

¿Puedo migrar de la nube de Tailscale a Headscale sin reinstalar?

No hay una migración automática de una tailnet de Tailscale a Headscale: son planos de control distintos, con sus propias claves y su propio estado. Lo que sí puedes es reutilizar los mismos dispositivos: basta con hacer tailscale logout en cada nodo y volver a registrarlo contra tu Headscale. Los datos de la tailnet original (ACL, usuarios) no se importan, así que tendrás que reconstruir esa configuración en tu servidor.

¿Es Headscale adecuado para una empresa grande?

Headscale está pensado, según sus propios responsables, para autoalojadores y equipos pequeños o medianos, no para grandes despliegues corporativos. Funciona bien para redes de decenas o pocos cientos de nodos, pero carece de las garantías de soporte, alta disponibilidad y funciones de gobierno de la oferta comercial de Tailscale. Para un homelab, un equipo técnico o una pyme que quiere control total y coste cero de licencia, encaja perfectamente; para infraestructura crítica de gran escala, valora el producto de pago.

Conclusión

Headscale te devuelve la pieza que Tailscale se reserva en su nube: el servidor de control. Con un docker-compose.yml corto, un config.yaml con cuatro valores propios y un par de comandos para crear usuarios y registrar nodos, tienes una malla WireGuard privada, sin límites de plan y bajo tu control. Recuerda lo esencial: fija la imagen en 0.29.2 en vez de :latest, sirve el 8080 siempre detrás de HTTPS con un proxy inverso, y usa claves preautorizadas para automatizar el alta de servidores. El siguiente paso natural es asegurar el acceso con Traefik y, si buscas el modelo cliente-servidor más sencillo para unos pocos dispositivos, comparar Headscale con WireGuard y wg-easy.

Fuentes

  1. Headscale: repositorio oficial en GitHub
  2. Headscale: documentación y primeros pasos
  3. Headscale: instalación en contenedor
  4. headscale-ui: panel web para Headscale
  5. Tailscale: precios y límites de los planes

Ruta: Redes y acceso remoto seguro con Docker