← Back to juanfont/headscale

How to Deploy & Use juanfont/headscale

Headscale Deploy and Usage Guide

An open-source, self-hosted implementation of the Tailscale control server written in Go.

Prerequisites

  • Go: Latest version (required for building from source)
  • Buf: Protocol Buffers generator (required for code generation)
  • Git: For cloning the repository
  • Nix (optional but recommended): For reproducible development environment setup
  • Docker (optional): Required only for running integration tests, not for production deployment

System Requirements

  • Linux server (physical or VM)
  • Public IP address or accessible endpoint for client connections
  • Persistent storage for SQLite database (or PostgreSQL instance)

Installation

Option 1: Using Nix (Recommended)

# Clone the repository
git clone https://github.com/juanfont/headscale.git
cd headscale

# Enter development shell with all dependencies
nix develop

# Verify tools are available
go version
buf --version

Option 2: Manual Tool Installation

# Install Go (latest version)
# https://golang.org/doc/install

# Install Buf
# https://docs.buf.build/installation

# Clone repository
git clone https://github.com/juanfont/headscale.git
cd headscale

Generate Protocol Buffer Code

Required if building from source or modifying proto/ files:

make generate

Note: If contributing, check in changes from gen/ in a separate commit.

Configuration

Headscale requires a configuration file and TLS certificates for production use.

Configuration File

Create a configuration file at /etc/headscale/config.yaml (or specify with --config):

server_url: https://headscale.example.com:8080
listen_addr: 0.0.0.0:8080
metrics_listen_addr: 127.0.0.1:9090

# Database configuration (SQLite default)
db_type: sqlite
db_path: /var/lib/headscale/db.sqlite

# Or use PostgreSQL
# db_type: postgres
# db_host: localhost
# db_port: 5432
# db_name: headscale
# db_user: headscale
# db_pass: changeme

# TLS configuration
tls_cert_path: /etc/headscale/tls.cert
tls_key_path: /etc/headscale/tls.key

# ACL Policy file
acl_policy_path: /etc/headscale/acl.hujson

# OIDC configuration (optional)
# oidc:
#   issuer: "https://accounts.google.com"
#   client_id: "your-client-id"
#   client_secret: "your-client-secret"

Required Paths

Based on the source configuration:

PathPurposePermissions
/etc/headscale/Configuration directory0755
/etc/headscale/acl.hujsonAccess Control List policy0644
/etc/headscale/tls.certTLS certificate0644
/etc/headscale/tls.keyTLS private key0600
/var/lib/headscale/Database storage0750

ACL Policy Example

Create /etc/headscale/acl.hujson:

{
  "acls": [
    {
      "action": "accept",
      "src": ["*"],
      "dst": ["*:*"]
    }
  ]
}

Build & Run

Development Mode

# Format code
make fmt

# Run linting
make lint

# Run tests (requires Docker for integration tests)
make test

# Build binary
make build

# Run directly (uses default config paths or --config flag)
./headscale serve

Production Build

# Build optimized binary
make build

# Binary will be available as ./headscale
# Move to system PATH
sudo mv ./headscale /usr/local/bin/
sudo chmod +x /usr/local/bin/headscale

Create Required Directories

sudo mkdir -p /etc/headscale /var/lib/headscale
sudo chown -R headscale:headscale /etc/headscale /var/lib/headscale

Deployment

Important: The maintainers do not support or encourage using reverse proxies or containers for running Headscale. Deploy using the binary directly or via the NixOS module.

Method 1: Systemd Service (Recommended)

Create /etc/systemd/system/headscale.service:

[Unit]
Description=Headscale Control Server
After=network.target

[Service]
Type=simple
User=headscale
Group=headscale
ExecStart=/usr/local/bin/headscale serve
Restart=always
RestartSec=5
WorkingDirectory=/var/lib/headscale

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/headscale /etc/headscale

[Install]
WantedBy=multi-user.target

Enable and start:

sudo useradd -r -s /bin/false headscale
sudo systemctl daemon-reload
sudo systemctl enable headscale
sudo systemctl start headscale

Method 2: NixOS Module

For NixOS users, use the module available in the repository:

# configuration.nix
imports = [
  ./path/to/headscale/nix/module.nix
];

services.headscale = {
  enable = true;
  settings = {
    server_url = "https://headscale.example.com:8080";
    listen_addr = "0.0.0.0:8080";
    db_type = "sqlite";
    db_path = "/var/lib/headscale/db.sqlite";
  };
};

Method 3: Direct Execution

# With custom config
headscale serve --config /path/to/config.yaml

# Environment variables can be used for some settings
HEADSCALE_DB_TYPE=postgres HEADSCALE_DB_HOST=localhost headscale serve

Client Registration

After deployment, register clients using:

# On client machine
tailscale up --login-server https://your-headscale-server:8080

# Or generate auth keys on server
headscale preauthkeys create --user myuser --reusable --expiration 24h

Troubleshooting

Build Issues

Error: protoc-gen-go-grpc: program not found or is not executable

  • Solution: Run make generate to generate protobuf code, or ensure buf is installed and in PATH.

Error: missing go.sum entry or module errors

  • Solution: Run go mod tidy or use nix develop to ensure consistent dependencies.

Error: Linting/formatting failures in CI

  • Solution: Run make fmt and make lint before committing. Configure editor to run golines (width 88), gofumpt, and golangci-lint.

Runtime Issues

Error: bind: address already in use

  • Solution: Check port 8080 availability or change listen_addr in config.

Error: permission denied on database

  • Solution: Ensure headscale user owns /var/lib/headscale and has write permissions.

Error: TLS certificate errors

  • Solution: Verify certificates exist at configured paths and are readable by the headscale user. Ensure certificates include full chain if using Let's Encrypt.

Error: Clients cannot connect

  • Solution:
    • Verify firewall allows UDP 3478 (STUN) and TCP 8080 (control)
    • Check server_url in config matches the public URL clients use
    • Ensure TLS certificates are valid and not self-signed (or use --accept-risk flags for testing only)

Integration Test Failures

Error: HEADSCALE_INTEGRATION_HEADSCALE_IMAGE must be set in CI

  • Solution: Set environment variable with format repository:tag when running integration tests in CI.

Error: Container initialization failures

  • Solution: Ensure Docker daemon is running and user has permissions to access Docker socket.

Getting Help