Configuración¶
Headscale Easy es un solo contenedor, configurado de tres maneras que se apilan en este orden (gana la primera):
- Variables de entorno (
-e, o el.envjunto al fichero compose). /data/config/settings.json, que escriben el asistente de primer arranque y las páginas de Ajustes de la consola.- Valores por defecto.
Sin ninguna (sin settings.json y sin HSE_PUBLIC_URL) el contenedor arranca en
el modo de configuración. La imagen renderiza
config.yaml de Headscale, el Caddyfile y el mapa DERP a partir de esos ajustes en
cada arranque (/data/config/); nunca editas a mano los ficheros generados, salvo el
bloque DNS, que gestiona la consola. docker exec headscale-easy hse reload vuelve a
renderizar y reinicia tras un cambio.
Referencia de variables de entorno¶
Todas las variables que lee la imagen. Las marcadas asistente también las pregunta el asistente de primer arranque.
Servidor y HTTPS¶
| Variable | Por defecto | Significado |
|---|---|---|
HSE_PUBLIC_URL |
(ninguna: asistente) | http(s)://host[:puerto] público del servidor. Definirla se salta el asistente |
HSE_TLS |
auto con https, off con http |
Quién termina TLS: ver Modos de HTTPS. asistente |
ACME_EMAIL |
Obligatoria con HSE_TLS=auto |
|
HSE_DERP_PORT |
3478 |
Puerto UDP del relé DERP/STUN integrado. compose.yaml publica el mismo puerto en el host (los clientes reciben este puerto): cámbialo en .env, nunca solo en la asignación de puertos |
HSE_DERP_MODE |
embedded |
embedded, public (también los relés de Tailscale) o custom: ver Relés. asistente |
HSE_DERP_URL |
URL del mapa DERP, con HSE_DERP_MODE=custom |
|
HEADSCALE_HTTP_PORT, HEADSCALE_METRICS_PORT, HEADSCALE_GRPC_PORT |
8080, 9090, 50443 |
Puertos internos de Headscale (dentro del contenedor; no se publican) |
IP_PREFIXES_V4, IP_PREFIXES_V6 |
100.64.0.0/10, fd7a:115c:a1e0::/48 |
Rangos de direcciones que reciben los dispositivos. Cambiarlos renumera todos los dispositivos |
LOG_LEVEL |
info |
Nivel de log de Headscale |
HSE_TRUSTED_PROXIES, HSE_TRUSTED_PROXIES_ANY |
El proxy delante: IP reales de los clientes. Ver Configuraciones avanzadas | |
UI_LANG |
en |
Idioma por defecto de la consola (en, es, fr, de, pt) |
TZ |
UTC |
Zona horaria (también el reloj de la programación de copias) |
Tailnet¶
| Variable | Por defecto | Significado |
|---|---|---|
TAILNET_NAME |
myorg |
Etiqueta de la tailnet. asistente |
HSE_BASE_DOMAIN |
hse.net |
Dominio base de MagicDNS: los dispositivos son <dispositivo>.<dominio base>. Debe ser distinto del dominio del propio servidor. Se fija en el primer arranque; luego se edita en la página DNS. asistente |
NETWORK_ISOLATION |
true |
Cada usuario sólo alcanza sus dispositivos: ver Aislamiento de red. asistente |
NODE_KEY_EXPIRY |
180d |
Vida de la clave de los dispositivos: ver Caducidad de la clave |
Cuentas e inicio de sesión¶
| Variable | Por defecto | Significado |
|---|---|---|
HSE_ADMIN_EMAIL, HSE_ADMIN_PASSWORD |
Primer administrador, creado en el primer arranque si no hay ninguna cuenta. asistente | |
HSE_SIGNUP |
off |
Auto-registro: off, invite (pide una clave de invitación) u open. asistente |
MFA_REQUIRED |
admins |
Quién debe configurar la verificación en dos pasos: admins, everyone u optional. Se define aquí; no se cambia desde la consola |
SESSION_SECRET |
(generado) | Firma las sesiones de la consola. Si está vacío se genera y se guarda en /data/config/session-secret |
SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_USE_TLS, SMTP_USE_SSL, SMTP_FROM |
El servidor de correo para los enlaces de invitación y de restablecimiento de contraseña. Con SMTP_HOST definido, un administrador ve un botón Enviar por correo junto a un enlace nuevo (nunca automático); SMTP_USE_TLS es STARTTLS, SMTP_USE_SSL TLS implícito y SMTP_PORT vale 587 por defecto. Sin él, copia el enlace y envíalo en privado |
|
OIDC_ISSUER, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET |
Iniciar sesión con un proveedor OIDC externo: ver Inicio de sesión | |
OIDC_SCOPE |
openid profile email |
Scopes que piden la consola y Headscale |
HSE_OIDC_ALLOWED_DOMAINS, HSE_OIDC_ALLOWED_USERS, HSE_OIDC_ALLOWED_GROUPS |
Quién puede entrar con el proveedor (separado por comas; vacío = todos los que el proveedor deje pasar) | |
PORTAL_ADMIN_EMAILS |
HSE_ADMIN_EMAIL |
Cuentas del proveedor que son administradoras en la consola |
PORTAL_ADMIN_GROUPS, PORTAL_NETWORK_ADMIN_GROUPS, PORTAL_AUDITOR_GROUPS |
Grupos del proveedor que corresponden a cada rol de la consola: ver Roles | |
HSE_AUTHENTIK_UPSTREAM |
host:puerto de un Authentik externo que Caddy sirve bajo /authentik, para que no cambie la URL de su emisor |
Notificaciones¶
| Variable | Por defecto | Significado |
|---|---|---|
NOTIFY_URLS |
Destinos (Slack, Telegram, ntfy, webhook): ver Operación → Notificaciones | |
NOTIFY_EVENTS |
todos | Qué eventos de dispositivos se envían |
Ajustes de la consola¶
Opcionales. Sin valor, la consola usa el valor por defecto indicado.
| Variable | Por defecto | Significado |
|---|---|---|
EXPIRY_WARNING_DAYS |
14 |
Una máquina "caduca pronto" cuando su clave caduca dentro de estos días |
INACTIVE_DAYS |
30 |
Una máquina se considera inactiva tras estos días desconectada |
AUTO_RENAME_LOCALHOST |
true |
Renombra las máquinas que se registran como localhost |
RENAME_INTERVAL |
5 |
Segundos entre dos pasadas de renombrado (mínimo 1) |
AUDIT_RETENTION_DAYS |
90 |
Días que el registro de actividad conserva los eventos; 0 los guarda siempre |
STATUS_UPDATE_CHECK |
true |
Busca una versión nueva y la muestra en la página Estado |
SIGNIN_RATE_LIMIT, SIGNIN_RATE_WINDOW |
10, 600 |
Inicios de sesión fallidos permitidos desde una IP dentro de la ventana (segundos) antes de que la consola responda 429 |
PORTAL_API_KEY_LOGIN |
false |
Acceso de emergencia con la clave de API de Headscale (siempre activo si no hay proveedor) |
BACKUP_UPLOAD_MAX_MB |
1024 |
Mayor copia que acepta la página Copias al subirla |
Base de datos¶
| Variable | Por defecto | Significado |
|---|---|---|
HEADSCALE_DB_TYPE |
sqlite |
sqlite o postgres (un servidor externo): ver Base de datos |
HEADSCALE_PG_HOST, HEADSCALE_PG_PORT, HEADSCALE_PG_NAME, HEADSCALE_PG_USER, HEADSCALE_PG_PASS, HEADSCALE_PG_SSLMODE |
puerto 5432, base y usuario headscale, TLS disable |
Conexión al PostgreSQL externo |
HEADSCALE_PG_RO_USER, HEADSCALE_PG_RO_PASS |
Rol de solo lectura que la imagen crea para la consola, para que nunca tenga las credenciales del propietario |
Copias de seguridad¶
| Variable | Por defecto | Significado |
|---|---|---|
BACKUP_SCHEDULE |
0 3 * * * |
Sintaxis cron, en la zona horaria TZ; off desactiva las copias programadas. Un valor no válido detiene el contenedor al arrancar. asistente |
BACKUP_KEEP_DAYS |
14 |
Las copias más antiguas se borran; la última correcta nunca se borra. asistente |
BACKUP_MODE |
create |
Sólo para la imagen backup usada como sidecar: sync sube los archivos que escribió la imagen todo en uno |
BACKUP_SYNC_INTERVAL |
900 |
Segundos entre dos subidas en modo sync |
Modos de HTTPS¶
HSE_TLS |
Quién termina TLS | Úsalo cuando |
|---|---|---|
auto |
Caddy, con un certificado de Let's Encrypt | Servidor público con dominio; puertos 80/443 abiertos |
internal |
Caddy, con su CA interna | Sin DNS público; puedes instalar una CA en los clientes |
off |
Nadie (HTTP plano), o un proxy delante | localhost, una LAN de confianza, o detrás de tu propio reverse proxy |
Con internal, el certificado raíz de Caddy está en /data/caddy/pki/ (y en cada
copia). Instálalo en todos los clientes o se negarán a conectar
(x509: certificate signed by unknown authority). Las apps de Android e iOS sólo
funcionan con un certificado de confianza pública.
Detrás de un reverse proxy que ya usas (nginx, Traefik, Caddy, Nginx Proxy Manager)
usa HSE_TLS=off, un HSE_PUBLIC_URL con https:// y HSE_TRUSTED_PROXIES: mira
Configuraciones avanzadas → Un proxy delante para la lista de
comprobación y los ejemplos listos.
El relé DERP integrado necesita el UDP 3478 accesible desde Internet en todos los modos: ningún proxy HTTP puede transportarlo.
Relés (DERP)¶
Por defecto (HSE_DERP_MODE=embedded) el contenedor es su propio relé: DERP y STUN
corren dentro y derp.urls queda vacío, así que ni Headscale ni la consola contactan
con el mapa DERP de tailscale.com. Los dispositivos que no pueden conectar directamente
dependen entonces de tu relé: mantén accesibles el UDP 3478 y HTTPS. public añade los
relés públicos de Tailscale; custom usa tu propio mapa (HSE_DERP_URL, o uno subido en
Red → Relés DERP).
Inicio de sesión¶
Hay tres maneras de entrar en la consola, y se combinan:
| Método | Cuentas | Notas |
|---|---|---|
| Cuentas locales (por defecto) | Se crean en la consola: contraseña, verificación en dos pasos opcional | Invitaciones, enlaces de restablecimiento y auto-registro, todo en la consola. Sin otro servicio que mantener |
| OIDC externo | Tu proveedor (Authentik, Keycloak, Pocket ID, Google…) | Define OIDC_*. El mismo cliente inicia sesión en la consola y registra dispositivos en Headscale |
| API key | Ninguna | Acceso de emergencia para administradores con una API key de Headscale |
Cuentas locales¶
- El primer administrador sale del asistente, o de
HSE_ADMIN_EMAIL+HSE_ADMIN_PASSWORD. - Usuarios → Invitar crea un enlace de un solo uso (1, 7 o 30 días) donde la persona elige nombre de usuario y contraseña; si la invitación lleva un email, la cuenta debe usarlo. Usuarios → Invitaciones pendientes lista los enlaces aún no usados (copiar de nuevo o revocar).
- Usuarios → ⋯ → Enlace de restablecimiento… crea un enlace de un solo uso (1 hora, 24 horas o 7 días). Establecer contraseña pone una temporal que la persona debe cambiar en su próximo inicio de sesión, y cierra sus sesiones abiertas.
- El enlace se muestra una sola vez, con un botón Copiar y su caducidad. Con
SMTP_*definido también puedes pulsar Enviar por correo; si no, envíalo en privado. - Las contraseñas tienen al menos 8 caracteres y se guardan como hashes con sal. Los inicios de sesión fallidos tienen límite de intentos por dirección.
- El auto-registro desde la página de inicio de sesión es
off,inviteuopen(HSE_SIGNUP, o Ajustes → General). Quien se registra solo es siempre miembro.
Roles¶
| Rol | Puede |
|---|---|
| Admin | Todo: máquinas, usuarios, DNS, control de acceso, claves, ajustes, logs, copias |
| Admin de red | Editar la política de Control de acceso y el DNS. Nada más |
| Auditor | Ver todo lo que ve un admin, sin cambiar nada, en ningún sitio, ni siquiera sus propios dispositivos |
| Miembro | Ver y gestionar sólo sus propias máquinas y claves de autenticación |
Con cuentas locales el rol se fija por cuenta (Usuarios). Con un proveedor externo sale de sus grupos y correos:
| Variable | Rol |
|---|---|
PORTAL_ADMIN_GROUPS, PORTAL_ADMIN_EMAILS |
Admin |
PORTAL_NETWORK_ADMIN_GROUPS |
Admin de red |
PORTAL_AUDITOR_GROUPS |
Auditor |
Admin tiene prioridad si alguien está en varios grupos. Tu proveedor debe enviar un claim
groups (el scope profile por defecto suele incluirlo).
Verificación en dos pasos¶
Las cuentas locales pueden pedir un segundo factor tras la contraseña: una app
autenticadora (TOTP), con códigos de recuperación. El administrador elige quién debe usarlo con
MFA_REQUIRED:
MFA_REQUIRED |
Comportamiento |
|---|---|
admins (por defecto) |
Los admins deben configurarlo la primera vez que entran; los miembros pueden |
everyone |
Todos los usuarios deben configurarlo |
optional |
A nadie se le obliga |
A quien tiene segundo factor siempre se le pide. Cada persona gestiona el suyo en Ajustes → General → Cuenta. Con un proveedor externo, la verificación en dos pasos se configura allí. El acceso de emergencia con API key no tiene segundo factor.
Tu propio proveedor OIDC¶
Registra un cliente con dos redirect URI:
https://<tu-dominio>/oidc/callback(Headscale)https://<tu-dominio>/console/callback(consola)
La consola y Headscale comparten el cliente para que la identidad (sub) de una persona
coincida en ambos. Hay ejemplos paso a paso para Authentik, Pocket ID, Keycloak y Google
en la configuraciones avanzadas.
Aislamiento de red y ACL¶
Con NETWORK_ISOLATION=true (el valor por defecto, o la opción de aislamiento del asistente) la
configuración inicial aplica esta política la primera vez:
{
"acls": [
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self:*"]},
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:internet:*"]}
]
}
Cada usuario sólo alcanza sus dispositivos, admins incluidos, y puede sacar
tráfico a internet por exit nodes (sin la regla autogroup:internet un exit
node acepta conexiones pero no reenvía nada). Una política existente nunca se
sobrescribe. Edítala en Control de acceso; la sintaxis
es la de Tailscale.
Control de acceso tiene seis pestañas:
- Reglas, Grupos y etiquetas: formularios para los casos habituales
—quién puede llegar a qué, grupos reutilizables de usuarios, quién es dueño
de cada etiqueta— sin escribir HuJSON. Editan la misma política que usa
Headscale: por debajo, cada guardado reescribe solo el bloque
acls,groupsotagOwnersque tocó y deja el resto del archivo —comentarios, orden de las claves, una secciónhostsu otra cosa escrita a mano— exactamente igual. Una regla cuyos destinos mezclan puertos distintos (algo que los formularios no pueden representar) se puede eliminar desde aquí, pero solo se edita en Avanzado. - Auto-aprobación: declara qué etiqueta, grupo o usuario recibe la
aprobación automática de una ruta de subred (o del rol de exit node), en
vez de aprobar cada dispositivo a mano desde su página de máquina (ver
Gestionar máquinas para ese flujo
manual de doble confirmación). Es la sección de política
autoApproversde Headscale. - Reglas SSH: quién puede conectar por SSH a qué máquinas, como qué
usuarios del sistema, usando Tailscale
SSH —sin gestionar claves
SSH—. Una regla también puede exigir volver a autenticarse cada cierto
tiempo en vez de un permiso fijo. Esto solo controla a quién se le
permite entrar: Tailscale SSH sigue necesitando
tailscale up --ssh(o el equivalente) en cada dispositivo que deba aceptar conexiones. - Probar acceso: elige un origen y un destino (un dispositivo, un
usuario, una etiqueta…) y dice si la política lo permite y qué regla
coincidió. Es una simulación sobre la política guardada, no una prueba
real de paquetes; para tener certeza, pruébalo desde los dispositivos
reales (
tailscale ping, o intenta llegar al servicio). - Avanzado (HuJSON): el editor de texto original, sin cambios. Es la vía de escape completa: cualquier cosa que el editor visual no pueda representar —posturas de dispositivo, comentarios escritos a mano— solo se edita aquí, y no se pierde nada por tener ambos.
Caducidad de la clave de los dispositivos¶
Como en Tailscale, cada dispositivo tiene una clave que caduca: pasado ese plazo
tiene que volver a iniciar sesión. Headscale Easy la fija en 180 días (NODE_KEY_EXPIRY) (el
valor de Tailscale); los admins la cambian en Ajustes → General → Gestión de
dispositivos (de 1 a 365 días, o nunca). Al guardar se reinicia Headscale, y
se aplica a los dispositivos que se añadan desde entonces: los existentes se
cambian en cada máquina (⋯ → Activar/Desactivar caducidad).
La caducidad de una clave de autenticación es otra cosa: solo limita hasta cuándo puede la clave añadir dispositivos.
DNS¶
Los admins editan el DNS en la consola (página DNS), organizada como la de Tailscale:
- Nombre DNS de la tailnet — Renombrar tailnet… pide confirmación antes:
cambia el nombre completo de cada máquina (
<máquina>.<dominio de la tailnet>). - MagicDNS — activar/desactivar; desactivarlo pide confirmación, porque los nombres de las máquinas dejan de resolverse en todos los dispositivos.
- Servidores de nombres — con MagicDNS activado, el dominio de la tailnet se
resuelve siempre con
100.100.100.100(se muestra de solo lectura). Debajo, los de DNS dividido (un servidor restringido a un dominio) y los globales, uno por fila (una IP o un resolvedor DoHhttps://…). Usar la configuración DNS local activado: los dispositivos mantienen sus servidores y los globales son un respaldo; desactivado (override_local_dns: true): todos los dispositivos usan los servidores globales. - Dominios de búsqueda — con MagicDNS activado, el dominio de la tailnet es siempre el primero.
- Registros personalizados —
nombre+dirección(por ejemplonas.example.com→100.64.0.5): lo resuelven todos los dispositivos de la tailnet; A o AAAA según la dirección.
Los miembros ven los mismos ajustes en solo lectura. La consola escribe el
bloque dns: de /data/config/config.yaml entre estos marcadores:
después ejecuta headscale configtest y reinicia Headscale, restaurando el
bloque anterior si la comprobación falla. La imagen conserva ese bloque al
volver a renderizar el fichero (por ejemplo al cambiar un ajuste), así que tu DNS sobrevive.
La validación y el reinicio pasan por el supervisor del propio contenedor: no hay socket de Docker en ningún sitio. Ver Seguridad.
Base de datos¶
Headscale guarda usuarios, máquinas y claves en SQLite por defecto: un fichero en
/data/headscale/, sin nada más que ejecutar. Es la recomendación del propio Headscale y
lo adecuado para casi cualquier tailnet. PostgreSQL se admite como servidor
externo, para tailnets grandes o si ya ejecutas (y respaldas) uno:
| Opción | HEADSCALE_DB_TYPE |
Qué aportas |
|---|---|---|
| SQLite (por defecto) | sqlite |
Nada |
| PostgreSQL externo | postgres |
host, puerto, base de datos, usuario propietario y contraseña, modo TLS (HEADSCALE_PG_*) |
- No hay conversión entre SQLite y PostgreSQL (Headscale no tiene herramienta para ello). Elige antes de añadir dispositivos.
- La consola lee con un rol de solo lectura. Necesita el Hostinfo que informan los
dispositivos (SO, versión de Tailscale, relé DERP, endpoints), que la API de Headscale
no expone. Con
HEADSCALE_PG_RO_USERla imagen crea ese rol contemplates/headscale-pg-readonly.sql: sólo puede hacerSELECTde las columnasid,host_infoyendpointsdenodes(sin claves, sin otras tablas) y sus sesiones son de solo lectura. La consola nunca recibe las credenciales propias de Headscale. Habla con PostgreSQL con un pequeño cliente integrado (solo biblioteca estándar: SCRAM-SHA-256, TLS opcional). - Tu servidor: debe autenticar con
scram-sha-256(el valor por defecto de PostgreSQL desde la 14; el método antiguomd5se rechaza). La base de datos debe existir y su propietario debe ser el usuario que indiques (Headscale crea sus tablas con él). Para crear el rol de solo lectura la imagen ejecuta ese SQL como propietario, lo que requiere el privilegioCREATEROLE; si no puede, la consola recurre a las credenciales del propietario y lo avisa claramente en el log.HEADSCALE_PG_SSLMODE(disable,prefer,require,verify-ca,verify-full) se aplica a Headscale, a la consola y a las copias. - Las copias usan
pg_dump(ver Operación → Copias de seguridad). La configuraciones avanzadas tiene un compose y una lista de comprobación.
Idioma¶
La consola sigue el idioma del navegador (inglés, español, francés, alemán o portugués) y
cada persona puede cambiarlo en Ajustes → General. UI_LANG (en, es, fr, de o
pt) fija el valor por defecto cuando el navegador pide un idioma que la consola no tiene.
Qué hay en /data¶
| Ruta | Contenido |
|---|---|
headscale/ |
Base de datos de Headscale, claves y socket local |
caddy/ |
Certificados y logs de acceso |
console/ |
Bases de datos de cuentas, sesiones y auditoría, API key de Headscale |
config/ |
settings.json, config.yaml renderizado, Caddyfile, derp.yaml |
backups/ |
Las copias integradas y status.json |
El contenedor lo crea todo con permisos privados (700 / 600).