Ir al contenido
  1. Posts/

Headscale: monta tu propio servidor de Tailscale

·2119 palabras·10 mins

Tailscale es probablemente la herramienta que más ha cambiado cómo gestiono mi homelab. La idea es simple: crea una red mesh privada entre todos tus dispositivos usando WireGuard por debajo, sin que tengas que configurar ningún firewall ni abrir puertos. Funciona detrás de NAT, funciona con CGNAT, funciona en casi cualquier sitio.

El problema es que Tailscale, la empresa, controla el servidor de coordinación. Ese servidor no ve tu tráfico (está cifrado end-to-end), pero sí gestiona la autenticación y el intercambio de claves públicas. Si Tailscale cierra, cambia precios o decide que tu caso de uso no les interesa, tienes un problema.

Headscale es la alternativa open source: un servidor de coordinación compatible con los clientes de Tailscale, que puedes montar en tu propio servidor. Llevo meses usándolo y te cuento qué tal va.

Qué hace Headscale exactamente
#

Cuando instalas el cliente de Tailscale en un dispositivo, ese cliente necesita conectarse a un servidor de coordinación para obtener su clave pública, registrarse en la red y descubrir otros nodos. Normalmente ese servidor es controlplane.tailscale.com.

Headscale implementa la misma API que usa el cliente oficial de Tailscale, así que puedes apuntar el cliente a tu servidor y funciona igual. El tráfico entre dispositivos sigue siendo WireGuard directo, punto a punto, sin pasar por Headscale. Solo la señalización y autenticación pasa por tu servidor.

Lo que ganas con esto es control total. Sin límites de nodos (Tailscale gratis permite 100 dispositivos, de pago más). Sin depender de su infraestructura. Sin que una empresa externa sepa qué dispositivos tienes y cuándo se conectan.

Lo que pierdes, principalmente, es la integración con el ecosistema de Tailscale: Funnel (exponer servicios públicamente), algunas funciones de Magic DNS avanzadas, y la interfaz web de tailscale.com. Para la mayoría de uso de homelab no hace falta ninguna de esas cosas.

Antes de empezar
#

Necesitas un servidor con IP pública accesible desde internet. No tiene que ser potente: Headscale consume muy pocos recursos. Un VPS barato de 1 CPU y 512 MB RAM sirve perfectamente. Yo lo tengo en una VM con 1 núcleo y 1 GB RAM y no he visto la CPU por encima del 2% nunca.

El servidor necesita tener el puerto 8080 (o el que configures) accesible desde fuera, y opcionalmente el 443 si vas a poner HTTPS. También necesita el puerto UDP para STUN si quieres soporte para traversal NAT avanzado.

En cuanto a dominio, necesitas un hostname al que puedan conectarse tus clientes. Puede ser una IP pública directamente, pero HTTPS con un dominio real es lo recomendable para producción.

Instalación de Headscale
#

Hay varias formas de instalar Headscale. Yo usé el binario directamente en una VM Debian porque quería control total, pero también puedes usar Docker.

Con el binario (Debian/Ubuntu):

Primero descarga el binario de la última release:

1
2
3
4
5
HEADSCALE_VERSION=$(curl -s https://api.github.com/repos/juanfont/headscale/releases/latest | grep tag_name | cut -d '"' -f 4 | tr -d 'v')

curl -LO https://github.com/juanfont/headscale/releases/download/v${HEADSCALE_VERSION}/headscale_${HEADSCALE_VERSION}_linux_amd64.deb

dpkg -i headscale_${HEADSCALE_VERSION}_linux_amd64.deb

El paquete .deb crea el usuario del sistema, el directorio de datos en /var/lib/headscale/ y el archivo de configuración en /etc/headscale/config.yaml.

Con Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mkdir -p /etc/headscale /var/lib/headscale

docker run -d \
  --name headscale \
  --restart unless-stopped \
  -p 8080:8080 \
  -p 9090:9090 \
  -v /etc/headscale:/etc/headscale \
  -v /var/lib/headscale:/var/lib/headscale \
  headscale/headscale:latest serve

Configuración básica
#

El archivo de configuración /etc/headscale/config.yaml tiene muchas opciones pero las esenciales son pocas. Edítalo con algo parecido a esto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Tu dominio o IP pública donde escucha Headscale
server_url: https://headscale.tu-dominio.com

# Donde escucha el servidor
listen_addr: 0.0.0.0:8080

# Rango de IPs que se asignarán a los dispositivos en tu red
ip_prefixes:
  - 100.64.0.0/10

# Configuración de DNS (Magic DNS)
dns_config:
  nameservers:
    - 1.1.1.1
  domains: []
  magic_dns: true
  base_domain: tu-red.local

# Base de datos (SQLite para instalaciones pequeñas)
db_type: sqlite
db_path: /var/lib/headscale/db.sqlite

# Rutas de certificados TLS (si usas HTTPS directo)
# Si vas a poner un reverse proxy delante, déjalo vacío
tls_cert_path: ""
tls_key_path: ""

# OIDC (opcional, para autenticación externa)
oidc:
  only_start_if_oidc_is_available: false

El campo server_url es importante: es la URL a la que se conectarán los clientes de Tailscale. Tiene que ser accesible desde internet.

Si vas a poner Nginx o Caddy delante de Headscale para manejar TLS, configura server_url con HTTPS pero deja vacíos los campos tls_cert_path y tls_key_path. Headscale escuchará en HTTP y el proxy se encargará del TLS.

Arranca el servicio:

1
2
systemctl enable headscale
systemctl start headscale

Verifica que funciona:

1
2
headscale version
headscale users list

Reverse proxy con Nginx
#

Para no exponer Headscale directamente con HTTP, lo pongo detrás de Nginx con certificado de Let’s Encrypt.

Configuración de Nginx para Headscale:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
server {
    listen 80;
    server_name headscale.tu-dominio.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name headscale.tu-dominio.com;

    ssl_certificate /etc/letsencrypt/live/headscale.tu-dominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/headscale.tu-dominio.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $server_name;
        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_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

El proxy_read_timeout alto es importante porque Headscale usa long polling para mantener las conexiones de los clientes.

Crear usuarios y registrar dispositivos
#

Headscale usa el concepto de “users” para agrupar dispositivos. Puedes tener un usuario por persona, o un usuario por entorno (homelab, trabajo, etc.).

Crea tu primer usuario:

1
headscale users create luis

Para registrar un dispositivo, primero instala el cliente de Tailscale en él. Después, en vez de hacer tailscale up normal, especifica tu servidor:

1
2
# En el dispositivo que quieres conectar
tailscale up --login-server https://headscale.tu-dominio.com

Esto mostrará una URL y un código. Copia la clave que aparece al final de la URL (el parámetro key=...).

En el servidor Headscale, registra el dispositivo:

1
2
# Registrar el dispositivo en el usuario "luis"
headscale nodes register --user luis --key CLAVE_DEL_DISPOSITIVO

A partir de este momento el dispositivo está en tu red. Verifica que aparece:

1
headscale nodes list

Deberías ver algo como:

1
2
ID | Hostname      | Name  | MachineKey | NodeKey | User | IP            | Ephemeral | Online | Expiry
1  | mi-servidor   | luis  | [...]      | [...]   | luis | 100.64.0.1    | false     | yes    | -

Conectar más dispositivos
#

El proceso es el mismo para cada dispositivo nuevo. En Linux y macOS:

1
tailscale up --login-server https://headscale.tu-dominio.com

En iOS y Android, al abrir la app de Tailscale hay una opción para usar un servidor de login personalizado antes de hacer login. Buscas esa opción, introduces la URL de tu Headscale, y el proceso sigue igual.

Una cosa que me costó encontrar: en iOS, la opción de servidor personalizado está en la pantalla de login, abajo del todo. No es obvia.

Headscale UI: interfaz web
#

Headscale en sí no tiene interfaz web. Se gestiona completamente por línea de comandos, que para mí es suficiente. Pero si prefieres algo visual, existe un proyecto separado llamado Headscale-UI que puedes desplegar como contenedor Docker:

1
2
3
4
5
6
docker run -d \
  --name headscale-ui \
  --restart unless-stopped \
  -p 8081:80 \
  -e HS_SERVER=https://headscale.tu-dominio.com \
  goodieshq/headscale-ui:latest

La interfaz es sencilla pero permite ver los nodos conectados, crear usuarios y gestionar las claves de preautorización sin tocar la terminal. Para equipos o si no quieres acordarte de los comandos, está bien.

Pre-auth keys: conectar dispositivos sin intervención manual
#

Cuando registras dispositivos uno a uno, el proceso requiere copiar la clave del dispositivo y ejecutar el comando en el servidor. Para automatizar esto, Headscale tiene claves de preautorización.

Crea una pre-auth key para un usuario:

1
2
3
4
5
# Clave de un solo uso que expira en 24h
headscale preauthkeys create --user luis --expiration 24h

# Clave reutilizable (útil para dispositivos que se regeneran)
headscale preauthkeys create --user luis --reusable --expiration 7d

Con la clave generada, el dispositivo puede unirse automáticamente:

1
tailscale up --login-server https://headscale.tu-dominio.com --authkey tskey-auth-XXXXXXXXXX

Esto es útil en scripts de provisioning o cuando despliegas muchas VMs. En mi caso lo uso para automatizar la conexión de VMs nuevas al homelab: en el cloud-init incluyo el comando de Tailscale con la pre-auth key y la VM queda conectada al arrancar.

Rutas y subnets
#

Una de las funciones más útiles de Tailscale/Headscale es la posibilidad de anunciar subnets. Esto permite que un nodo haga de “puerta de entrada” a una red local entera.

Por ejemplo, si tienes un servidor en casa con acceso a tu red 192.168.1.0/24, puedes hacer que ese servidor anuncie esa subnet a toda la red de Headscale. Entonces desde cualquier otro dispositivo en la red mesh puedes acceder a cualquier IP de tu red local, aunque ese dispositivo específico no tenga Tailscale instalado.

En el servidor que quieres usar como subnet router:

1
2
3
tailscale up \
  --login-server https://headscale.tu-dominio.com \
  --advertise-routes=192.168.1.0/24

En Headscale, acepta la ruta anunciada:

1
2
headscale routes list
headscale routes enable --route ID_DE_LA_RUTA

Yo uso esto para acceder a todos los dispositivos del homelab desde el trabajo, pasando solo por un único nodo que tiene Tailscale.

Gestión de ACLs
#

Headscale soporta ACLs (listas de control de acceso) en formato HuJSON, igual que Tailscale. Puedes definir qué usuarios o dispositivos pueden comunicarse entre sí y en qué puertos.

El archivo de política va en /etc/headscale/acls.yaml o referenciado desde el config. Un ejemplo básico que permite todo entre nodos del mismo usuario:

1
2
3
4
5
6
7
8
9
{
  "acls": [
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["*:*"]
    }
  ]
}

Para algo más restrictivo, por ejemplo que solo los dispositivos del usuario “admin” puedan acceder al puerto SSH de los servidores:

1
2
3
4
5
6
7
8
9
{
  "acls": [
    {
      "action": "accept",
      "src": ["admin"],
      "dst": ["servidores:22"]
    }
  ]
}

Las ACLs son opcionales pero muy útiles cuando tienes varios usuarios en el mismo servidor Headscale, por ejemplo si lo compartes con familia o equipo.

Consumo de recursos
#

Como decía antes, Headscale consume muy poco. En mi servidor con unos 20 nodos activos:

  • CPU: prácticamente 0% en reposo. Solo sube cuando un dispositivo se conecta o desconecta
  • RAM: menos de 50 MB con SQLite
  • Ancho de banda: casi nada, porque el tráfico real pasa directamente entre dispositivos vía WireGuard, no por el servidor de Headscale
  • Almacenamiento: la base de datos SQLite con 20 nodos ocupa menos de 5 MB

En serio, cualquier VPS de 5 euros al mes aguanta Headscale sin problemas. Los requisitos son mínimos.

Actualizaciones
#

Headscale se actualiza con bastante frecuencia. El proceso es simple con el paquete .deb:

1
2
3
4
NUEVA_VERSION=0.XX.0
curl -LO https://github.com/juanfont/headscale/releases/download/v${NUEVA_VERSION}/headscale_${NUEVA_VERSION}_linux_amd64.deb
dpkg -i headscale_${NUEVA_VERSION}_linux_amd64.deb
systemctl restart headscale

El paquete incluye migraciones de base de datos automáticas. Hasta ahora no he tenido ningún problema en las actualizaciones. Eso sí, siempre hago un backup de /var/lib/headscale/db.sqlite antes de actualizar, por si acaso.

¿Cuándo usar Headscale y cuándo Tailscale cloud?
#

Headscale no es mejor que Tailscale cloud en todo. Hay situaciones donde Tailscale cloud tiene más sentido.

Usa Headscale si:

  • Tienes un servidor con IP pública disponible
  • Necesitas más de 100 dispositivos
  • Quieres control total y no depender de servicios de terceros
  • Quieres privacidad completa (que ni el servidor de coordinación sepa qué nodos tienes)
  • Disfrutas del self-hosting

Usa Tailscale cloud si:

  • No tienes servidor con IP pública
  • Necesitas Tailscale Funnel (exponer servicios públicamente)
  • Quieres cero mantenimiento
  • El plan gratuito de 100 dispositivos te es suficiente
  • Empiezas y quieres simplicidad antes que control

Lo que no tiene ningún sentido es quedarte sin ninguna de las dos. WireGuard manual funciona pero la experiencia de configurar y mantener WireGuard tradicional comparada con Tailscale/Headscale es como la diferencia entre escribir HTML a mano o usar un editor.

Conclusión
#

Llevo meses con Headscale en producción y no he tenido ningún incidente. El sistema va, los clientes de Tailscale funcionan igual que antes, y tengo control total sobre quién está en mi red y cuándo.

El tiempo de montaje fue de una tarde. El mantenimiento es mínimo: una actualización cada pocas semanas cuando sale nueva versión, y listo.

Si ya usas Tailscale y tienes un servidor disponible, la migración merece la pena. Si estás empezando con VPNs mesh para el homelab, Headscale es un punto de partida tan bueno como cualquier otro.

Recursos
#