SSD Nodes Learn 8GB de RAM — $66/año
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-02

Headscale: aloja tu propio servidor de Tailscale

Configura headscale en un VPS con el paquete .deb oficial. Define server_url antes de iniciar el servicio y conecta tu primer nodo Tailscale.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Qué es headscale

Headscale es una implementación autohospedada del servidor de control de Tailscale. Por tanto, la máquina que coordina tu red privada es un VPS que administras. Es un proyecto comunitario y no está gestionado por Tailscale Inc. Cada máquina sigue ejecutando el cliente oficial tailscale, configurado para usar tu servidor mediante la opción --login-server.

El servidor de control es el componente que sabe quién pertenece a la red. Asigna a cada nodo una dirección de 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 de nodo a nodo. El tráfico entre dos de tus máquinas no pasa por el servidor de headscale, a menos que no se pueda establecer una ruta directa y los nodos tengan que recurrir a un relay.

Cada instancia de headscale proporciona un tailnet (una red de Tailscale), que el proyecto considera adecuado para uso personal o para una organización pequeña. Con tres o cuatro máquinas, una VPN de WireGuard sencilla en un VPS que administras requiere ejecutar menos software y ofrece menos puntos de fallo. Headscale resulta útil cuando ya no quieres escribir manualmente un bloque [Peer] para cada portátil nuevo. Para una comparación más amplia de ambos modelos, consulta las diferencias entre WireGuard y Tailscale.

Requisitos previos a la instalación

  • Un VPS con Ubuntu 24.04, una dirección IPv4 pública y acceso mediante sudo. Si el servidor es nuevo, complete primero los primeros diez minutos en un VPS nuevo.
  • Un registro A de DNS 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 de server_url.
  • Un equipo cliente para incorporarlo, 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 julio de 2026, la versión actual es 0.29.3. Comprueba primero la arquitectura, porque el nombre del archivo la incluye.

sudo apt update
sudo apt install -y wget
dpkg --print-architecture

Ese comando muestra amd64 en un VPS x86 normal y arm64 en un plan de tipo Ampere o Graviton. Introduce la respuesta en la variable siguiente.

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 version

El prefijo ./ delante del nombre del archivo es obligatorio. Sin él, apt busca en tus repositorios un paquete llamado headscale.deb y falla.

El paquete crea el usuario de sistema headscale, escribe un /etc/headscale/config.yaml predeterminado e instala una unidad de systemd. No inicia el servicio, y ese es el orden correcto. La configuración incluida apunta server_url a http://127.0.0.1:8080, que no es una dirección accesible para ningún cliente tuyo. Por tanto, si el servicio se iniciara ahora, la configuración sería incorrecta aunque llegara a iniciarse. Ejecutar sudo systemctl is-active headscale en este punto muestra inactive. Es el resultado esperado, no un error.

Configure server_url antes de iniciar el servicio

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 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.yaml

server_url es la dirección que headscale escribe en cada registro de cliente. Después, 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.

listen_addr indica dónde se enlaza el proceso. Déjelo en la interfaz de loopback. Un proxy inverso en el mismo equipo termina TLS (seguridad de la capa de transporte) y reenvía las solicitudes a ese proceso, por lo que nada externo debe acceder al puerto 8080.

base_domain es el sufijo de MagicDNS, el dominio bajo el que los nodos reciben sus nombres. Debe ser un nombre de dominio completamente cualificado sin 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.

Inicie headscale y compruebe que se está ejecutando

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/health

is-active muestra active y curl muestra 200. enable --now hace ambas partes: inicia el servicio y lo configura para iniciarse después de un reinicio.

Si is-active muestra failed, consulte el registro con sudo journalctl -u headscale -n 50 --no-pager. Un error en esta etapa casi siempre se debe al archivo de configuración, porque headscale analiza todo el archivo antes de abrir un socket. Una sangría incorrecta o una clave desconocida detiene el proceso antes de que escuche en cualquier puerto. 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 conoce las unidades de systemd, ejecutar sus propios servicios y temporizadores con systemd explica los comandos usados aquí.

Compruebe los archivos de estado mientras está en la shell:

stat -c '%U %n' /var/lib/headscale/db.sqlite /var/lib/headscale/noise_private.key

Ambas 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 uno nuevo y todos los nodos deben registrarse de nuevo.

Colocar 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 automáticamente.

sudo apt install -y caddy

Sustituye /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 caddy

validate muestra adapted config to JSON cuando el archivo se analiza correctamente. Una advertencia que indique que el archivo no tiene el formato esperado es meramente estética. Desde tu portátil, curl -sS -o /dev/null -w '%{http_code}\\n' https://headscale.example.com/health también debería mostrar 200. Esa única comprobación demuestra que DNS, el firewall, el certificado y el proxy funcionan conjuntamente.

Este es el detalle del proxy que puede hacer perder una tarde. La conexión de control de Tailscale es una actualización HTTP, se inicia con POST en lugar de GET y el valor de la cabecera Upgrade es tailscale-control-protocol. Caddy lo reenvía sin configuración adicional. nginx no lo hace, por lo que un frontal nginx necesita el 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 omites esas líneas, las solicitudes normales siguen funcionando. Por eso /health devuelve 200 y todo parece correcto, mientras la conexión de control de larga duración nunca se establece y los nodos se registran y después permanecen desconectados. Si eliges nginx, Certbot en Ubuntu 24.04 con nginx explica 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 verbose

El puerto 443 transporta toda la comunicación de los clientes. El puerto 80 solo se usa para el desafío HTTP de ACME (entorno de gestión automática de certificados) y para la redirección a HTTPS. Caddy lo necesita para obtener un certificado.

El puerto 8080 permanece cerrado. listen_addr es 127.0.0.1:8080, por lo que el proxy accede a headscale mediante la interfaz de loopback y no se necesita ninguna regla del firewall. Abrir el puerto 8080 a Internet proporciona a los clientes un canal de control sin cifrado y no aporta ninguna ventaja. Ten en cuenta que la mayoría de los proveedores ejecutan un segundo firewall en su panel de control, separado 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 list

El comando headscale es un cliente. Se comunica con el daemon en ejecución a través del socket Unix ubicado en /var/run/headscale/headscale.sock, que tiene el modo 0770 y pertenece al grupo headscale. Esto tiene dos consecuencias. El comando falla si el servicio está detenido, que es otra razón por la que importa el orden de esta guía, y requiere sudo, a menos que agregue 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 24h

La 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 eso conviene establecer --expiration 24h mientras todavía realiza pruebas. Agregue --reusable para crear una clave que inscriba varios equipos 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 unir:

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 -4

tailscale 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 con una dirección y después recibe instrucciones para comunicarse con otra.

Una máquina que inició sesión anteriormente en el servicio alojado de Tailscale conserva esa sesión. Ejecuta primero sudo tailscale logout en ella y después ejecuta 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-PAGE

Este método es más práctico para tu propio portátil. Las claves de preautorización son mejores para cualquier proceso con scripts, porque no es necesario que una persona lo supervise.

DERP y qué retransmite el tráfico cuando falla la ruta directa

DERP (relé cifrado designado para paquetes) 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 (traducción de direcciones de red) estricto, envían los paquetes a través de un relé. El relé 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, de modo que el plano de control es suyo, mientras que los relés son de Tailscale. Para la mayoría de los usuarios, es una solución equilibrada. Si no lo es, ejecute sus propios relés.

Para ejecutar su propio relé, establezca enabled: true en derp.server dentro de config.yaml, reinicie headscale y abra el puerto STUN (utilidades de recorrido de sesiones para 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. Vaciar la lista derp.urls elimina los relés de Tailscale del mapa. Si lo hace sin un relé integrado operativo, cualquier par de nodos que no pueda conectarse directamente no podrá conectarse.

Desde un cliente, tailscale netcheck muestra la latencia de cada región de relé 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 indica un problema de NAT, no un problema de headscale.

¿Por qué un nodo aparece sin conexión?

El proxy está descartando la actualización. Este es el caso más común. Su característica es que todo lo demás parece funcionar correctamente: /health devuelve 200, headscale nodes list muestra el nodo y el nodo nunca se conecta. 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 el bloque map anterior o cambie a Caddy para descartar el proxy.

server_url cambió después de que los nodos se registraran. Los nodos siguen conectándose al valor que recibieron durante el registro. Si lo editó, 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 línea, 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 expiraciones independientes. Confundirlas hace perder tiempo.

Las claves de preautorizació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 algo 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 haga caducar. Los nodos etiquetados nunca caducan. Establezca expiry: 180d si quiere que los registros caduquen, y tenga claro lo que implica: todos los nodos sin etiqueta 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 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 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-*.tgz

Mueva 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 de restic desde un VPS explica cómo hacerlo de forma programada y cifrada.

Las actualizaciones repiten la instalación: descargue la nueva versión de .deb y sudo apt install ./headscale.deb, reinicie y vuelva a ejecutar las comprobaciones de is-active y /health. Desde la versión 0.29, la ruta de actualización es estricta. No se permite omitir una versión menor ni cambiar 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 cambió el comportamiento de las políticas de ACL y trasladó varias claves de configuración.

FAQ

¿Por qué headscale no se inicia justo después de instalar el archivo .deb?

El paquete instala la unidad, pero deja el servicio detenido, y el /etc/headscale/config.yaml predeterminado es una plantilla, no una configuración funcional. Edite primero server_url, listen_addr y base_domain. Después, ejecute sudo systemctl enable --now headscale y confirme con sudo systemctl is-active headscale. Si sigue fallando, sudo journalctl -u headscale -n 50 --no-pager indica el problema. En esta etapa, casi siempre se trata de un error de YAML, porque headscale analiza todo el archivo antes de enlazar un puerto.

¿Tengo que instalar el cliente normal de Tailscale en mis equipos?

Sí. Headscale solo reemplaza el servidor de control. Cada nodo ejecuta el cliente oficial de Tailscale, y se le indica 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 recompilarlo.

¿El tráfico pasa por el servidor 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 solo toma una ruta alternativa cuando dos nodos no pueden comunicarse directamente y recurren a un repetidor DERP. Con la configuración incluida, esos repetidores son los públicos de Tailscale. Ejecute tailscale status en un nodo para comprobar si un par determinado está direct o usa un relay.

¿Por qué mi nodo permanece 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 proxy inverso. Esa conexión es una actualización HTTP enviada como POST con la cabecera Upgrade: tailscale-control-protocol. nginx la descarta si no se añade el bloque map $http_upgrade $connection_upgrade y las líneas proxy_set_header correspondientes. 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 indica que DERP requiere TLS. Un dominio junto con Caddy se configura en unos cinco minutos y proporciona un endpoint HTTPS que se renueva automáticamente. Ejecutar el servidor de control mediante HTTP sin cifrado significa que toda comunicación de los clientes con él atraviesa Internet sin cifrado.