What is Headscale?Headscale aims to implement a self-hosted, open source alternative to the Tailscale control server. Headscale's goal is to provide self-hosters and hobbyists with an open-source server they can use for their projects and labs. It implements a narrow scope, a single Tailscale network (tailnet), suitable for a personal use, or a small open-source organisation.
A web frontend for the headscale Tailscale-compatible coordination server by gurucomputing. It provides a simple browser-based interface for managing users, devices, pre-auth keys, and routes. The UI stores your API key locally in the browser — no backend configuration needed.
Headscale is best run from somewhere outside your network, ideally in the cloud. As such, you need to have a VPS to install Headscale.
A minimal VPS instance with 1 vCPU, 1GB RAM, and 8GB SSD will perform perfectly well for most use cases. In some cases, you may be able to get away with even less.
One option is Racknerd and honestly it's a great choice, but any VPS will do. Note this is an affiliate link
Check out my guide for Racknerd
Script
Docker ComposeThe easiest way to deploy this is with my script, which (after you have met all the pre-requisites) will pull the docker containers, insert your FQDN, and generate an API key for you.
The script will prompt you for:
headscale.example.com)ts.example.com)Run this command to launch the script:
wget -q -O headscale.sh https://raw.githubusercontent.com/serversathome/ServersatHome/main/headscale.sh && chmod +x headscale.sh && sudo ./headscale.sh
Follow the directions at the end of the script for login information
services:
headscale:
image: 'headscale/headscale:0.28.0'
container_name: 'headscale'
restart: 'unless-stopped'
command: 'serve'
read_only: true
tmpfs:
- /var/run/headscale
volumes:
- './config:/etc/headscale:ro'
- './lib:/var/lib/headscale'
environment:
TZ: 'America/New_York'
healthcheck:
test: ["CMD", "headscale", "health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
labels:
- "traefik.enable=true"
- "traefik.http.routers.headscale.rule=Host(`my.domain.com`)"
- "traefik.http.routers.headscale.tls.certresolver=myresolver"
- "traefik.http.routers.headscale.entrypoints=websecure"
- "traefik.http.routers.headscale.tls=true"
- "traefik.http.services.headscale.loadbalancer.server.port=8080"
headscale-ui:
image: 'ghcr.io/gurucomputing/headscale-ui:2025.08.23'
container_name: 'headscale-ui'
restart: 'unless-stopped'
labels:
- "traefik.enable=true"
- "traefik.http.services.headscale-ui.loadbalancer.server.port=8080"
- "traefik.http.routers.headscale-ui.rule=Host(`my.domain.com`) && PathPrefix(`/web`)"
- "traefik.http.routers.headscale-ui.entrypoints=websecure"
- "traefik.http.routers.headscale-ui.tls=true"
- "traefik.http.routers.headscale-ui.tls.certresolver=myresolver"
traefik:
image: "traefik:v3.3"
container_name: "traefik"
restart: "unless-stopped"
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "[email protected]"
- "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
ports:
- "80:80"
- "443:443"
- "3478:3478/udp"
volumes:
- "./letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
my.domain.com with your FQDN (e.g. headscale.domain.com)[email protected] with your actual email for Let's EncryptHeadscale-UI serves on port 8080 and uses the
/webpath. This is different from the old headscale-admin which used port 80 and the/adminpath.
Save this as config/config.yaml:
---
server_url: https://my.domain.com
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090
grpc_listen_addr: 127.0.0.1:50443
grpc_allow_insecure: false
noise:
private_key_path: /var/lib/headscale/noise_private.key
prefixes:
v4: 100.64.0.0/10
v6: fd7a:115c:a1e0::/48
allocation: sequential
derp:
server:
enabled: true
region_id: 999
region_code: "headscale"
region_name: "Headscale Embedded DERP"
verify_clients: true
stun_listen_addr: "0.0.0.0:3478"
private_key_path: /var/lib/headscale/derp_server_private.key
automatically_add_embedded_derp_region: true
ipv4: 1.2.3.4
urls:
- https://controlplane.tailscale.com/derpmap/default
paths: []
auto_update_enabled: true
update_frequency: 3h
disable_check_updates: false
ephemeral_node_inactivity_timeout: 30m
database:
type: sqlite
debug: false
gorm:
prepare_stmt: true
parameterized_queries: true
skip_err_record_not_found: true
slow_threshold: 1000
sqlite:
path: /var/lib/headscale/db.sqlite
write_ahead_log: true
wal_autocheckpoint: 1000
acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: ""
tls_letsencrypt_hostname: ""
tls_letsencrypt_cache_dir: /var/lib/headscale/cache
tls_letsencrypt_challenge_type: HTTP-01
tls_letsencrypt_listen: ":http"
tls_cert_path: ""
tls_key_path: ""
log:
format: text
level: info
policy:
mode: database
path: ""
dns:
magic_dns: true
base_domain: ts.example.com
override_local_dns: true
nameservers:
global:
- 1.1.1.1
- 1.0.0.1
- 2606:4700:4700::1111
- 2606:4700:4700::1001
split: {}
search_domains: []
extra_records: []
unix_socket: /var/run/headscale/headscale.sock
unix_socket_permission: "0770"
logtail:
enabled: false
randomize_client_port: false
taildrop:
enabled: true
my.domain.com with your FQDN (e.g. headscale.domain.com)1.2.3.4 with your VPS public IPv4 addressts.example.com with your MagicDNS base domain (this must be different from your server domain)The
dns.base_domaincannot be the same as yourserver_urldomain. Use a subdomain likets.yourdomain.comor a completely different domain.
docker exec headscale headscale apikeys create
https://your.domain.com/webhttps://your.domain.comTo connect a device to your headscale instance:
tailscale up --login-server=https://your.domain.com
On first connection, you'll need to create a user and register the node. Run
docker exec headscale headscale users create myuserto create a user, then follow the registration URL shown in the Tailscale client output.
| Command | Description |
|---|---|
docker exec headscale headscale users create <name> |
Create a new user |
docker exec headscale headscale users list |
List all users |
docker exec headscale headscale nodes list |
List all connected nodes |
docker exec headscale headscale apikeys create |
Generate a new API key |
docker exec headscale headscale apikeys list |
List existing API keys |
docker exec headscale headscale preauthkeys create --user <name> |
Create a pre-auth key |
7 · Video