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.

Official references