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:
| Path | Purpose | Permissions |
|---|---|---|
/etc/headscale/ | Configuration directory | 0755 |
/etc/headscale/acl.hujson | Access Control List policy | 0644 |
/etc/headscale/tls.cert | TLS certificate | 0644 |
/etc/headscale/tls.key | TLS private key | 0600 |
/var/lib/headscale/ | Database storage | 0750 |
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 generateto generate protobuf code, or ensurebufis installed and in PATH.
Error: missing go.sum entry or module errors
- Solution: Run
go mod tidyor usenix developto ensure consistent dependencies.
Error: Linting/formatting failures in CI
- Solution: Run
make fmtandmake lintbefore committing. Configure editor to rungolines(width 88),gofumpt, andgolangci-lint.
Runtime Issues
Error: bind: address already in use
- Solution: Check port 8080 availability or change
listen_addrin config.
Error: permission denied on database
- Solution: Ensure
headscaleuser owns/var/lib/headscaleand 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_urlin config matches the public URL clients use - Ensure TLS certificates are valid and not self-signed (or use
--accept-riskflags for testing only)
Integration Test Failures
Error: HEADSCALE_INTEGRATION_HEADSCALE_IMAGE must be set in CI
- Solution: Set environment variable with format
repository:tagwhen 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
- Documentation: https://headscale.net/stable/
- Discord: https://discord.gg/c84AZQhmpx
- Check logs:
journalctl -u headscale -f - Debug mode:
headscale serve --verbose