How to Self-Host Headscale and Connect Tailscale on macOS, Windows, Linux, Android, and iOS
A practical beginner guide to running Headscale on Ubuntu with HTTPS and connecting Tailscale clients across desktop and mobile platforms.
Headscale is a self-hosted implementation of the Tailscale control server. It lets you keep the coordination layer on your own server while your devices use Tailscale and WireGuard to form the private network.
This beginner-friendly guide sets up Headscale on Ubuntu, puts it behind HTTPS, and connects macOS, Ubuntu, Windows, Android, and iPhone/iPad clients.
Before you begin: Headscale is aimed mainly at self-hosters, homelabs, and small organizations. Keep the server patched, back up its data, and review the current documentation before exposing it to the internet.
What you need
- An Ubuntu 22.04 or newer server with a public IP address
- A domain or subdomain, such as
vpn.example.com - DNS pointing that name to the server
- TCP ports 80 and 443 open while obtaining the certificate
- Root or sudo access
Replace vpn.example.com, usernames, keys, and hostnames in every example below.
1. Install Headscale on Ubuntu
Headscale recommends its Debian package on Ubuntu because the package creates the service account, installs a default configuration, and includes the systemd service.
Open the Headscale releases page, note the latest stable version, and check your server architecture:
dpkg --print-architecture
For an Intel/AMD 64-bit server, set the version without the leading v and download the package:
HEADSCALE_VERSION="X.Y.Z"
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 ./headscale.deb
Use arm64 instead of amd64 when appropriate.
2. Configure Headscale
Back up the packaged configuration, then edit it:
sudo cp /etc/headscale/config.yaml /etc/headscale/config.yaml.backup
sudo nano /etc/headscale/config.yaml
At minimum, set the public URL and keep Headscale listening only on localhost because Nginx will handle public HTTPS:
server_url: https://vpn.example.com
listen_addr: 127.0.0.1:8080
Keep the other paths and database settings supplied by your installed version. The example file shipped with the package is available at /usr/share/doc/headscale/examples/config-example.yaml.
Restart Headscale and check it:
sudo systemctl enable headscale
sudo systemctl restart headscale
sudo systemctl status headscale
3. Add Nginx and HTTPS
Install Nginx and Certbot:
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginx
Create /etc/nginx/sites-available/headscale:
server {
listen 80;
listen [::]:80;
server_name vpn.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
}
Enable the site, test the configuration, and reload Nginx:
sudo ln -s /etc/nginx/sites-available/headscale /etc/nginx/sites-enabled/headscale
sudo nginx -t
sudo systemctl reload nginx
Request a certificate:
sudo certbot --nginx -d vpn.example.com
Confirm the health endpoint responds:
curl https://vpn.example.com/health
4. Create a Headscale user and enrollment key
A Headscale user owns devices; it is not the device name. One user can own many computers and phones.
sudo headscale users create personal
sudo headscale users list
Create a pre-authentication key for that user:
sudo headscale preauthkeys create --user personal
Copy the returned key. Treat it like a password. By default, a Headscale pre-authentication key is short-lived and single-use. Create a new key for each device unless you intentionally choose different options.
5. Connect macOS
Install Tailscale from the Mac App Store or the official Tailscale download page. If the command-line launcher is available, connect with a unique, DNS-safe hostname:
sudo tailscale up \
--login-server=https://vpn.example.com \
--authkey=PASTE_ONE_TIME_KEY_HERE \
--hostname=mac-mini-m4
If Terminal says tailscale: command not found, open the Tailscale menu bar app, install its command-line integration if offered, or use the app interface: Settings → Accounts → add account, then enter the custom control server URL.
If Headscale shows a name such as invalid-xxxxxxxx, set a simple lowercase hostname and update what Tailscale advertises:
sudo scutil --set LocalHostName mac-mini-m4
sudo scutil --set HostName mac-mini-m4
sudo tailscale set --hostname=mac-mini-m4
6. Connect an Ubuntu client
Install Tailscale:
curl -fsSL https://tailscale.com/install.sh | sh
Join Headscale:
sudo tailscale up \
--login-server=https://vpn.example.com \
--authkey=PASTE_ONE_TIME_KEY_HERE \
--hostname=ubuntu-laptop
7. Connect Windows
Install Tailscale from the official download page, then open PowerShell as Administrator:
tailscale up `
--login-server=https://vpn.example.com `
--authkey=PASTE_ONE_TIME_KEY_HERE `
--hostname=windows-pc
You can also add the custom control server from the Tailscale app's account controls.
8. Connect Android, iPhone, or iPad
Mobile clients do not provide the Tailscale command-line interface. Install the official Tailscale app, open its account or sign-in options, choose the option to use a custom or alternate server, and enter:
https://vpn.example.com
The app opens your Headscale registration page. Follow the displayed instructions to approve the device under the intended user. The exact menu wording can vary by client version. For managed fleets, Tailscale also supports the LoginURL system policy on iOS and Android.
9. Verify the private network
On the Headscale server:
sudo headscale nodes list
On a desktop client:
tailscale status
tailscale ip -4
tailscale ping OTHER_DEVICE_NAME_OR_IP
Every device should have a unique name and a private address in Headscale's configured range. Devices may be under the same Headscale user; that is normal.
Useful Headscale management commands
# List users
sudo headscale users list
# List nodes
sudo headscale nodes list
# See the exact syntax supported by your installed version
sudo headscale nodes --help
sudo headscale preauthkeys --help
Headscale command syntax can change between releases, so use the built-in help before renaming, expiring, or deleting a node.
Troubleshooting
Headscale is unreachable
sudo systemctl status headscale
sudo systemctl status nginx
sudo nginx -t
curl -v https://vpn.example.com/health
Also verify DNS and that TCP 443 reaches the server.
The device appears as invalid-xxxxxxxx
Advertise a unique hostname using lowercase letters, numbers, and hyphens. Avoid spaces and punctuation:
sudo tailscale set --hostname=mac-mini-m4
A registration command says “user not found”
List users and use the username or user identifier expected by your installed Headscale version:
sudo headscale users list
sudo headscale preauthkeys create --help
A pre-authentication key does not work
It may have expired or already been used. Create a new one and do not paste real keys into screenshots, public posts, or shell history shared with others.
A few security notes
- Use HTTPS; mobile clients should not be pointed at an insecure HTTP control server.
- Keep Ubuntu, Headscale, Nginx, and Tailscale updated.
- Back up Headscale's database, configuration, and key material.
- Use short-lived, single-use enrollment keys.
- Review Headscale access-control policies before adding untrusted users or devices.
- Do not expose Headscale's local service port directly when Nginx is the public entry point.
Conclusion
You now have a self-hosted Headscale control server and Tailscale clients on the major desktop and mobile platforms. Headscale coordinates identity, keys, names, and policy; encrypted device traffic usually travels directly between peers, with relays used when a direct path is not possible.
For production use, continue with access-control policies, DNS, subnet routers, exit nodes, monitoring, and tested backups.