Here I will walk you through setting up Headscale


Create Directories

mkdir -p /opt/headscale/config /opt/headscale/bin


Install Reqs

apt install -y wireguard-tools nginx apt-transport-https


Generate Key

wg genkey > /opt/headscale/config/private.key


  1. Download newest release from HERE

  2. wget https://github.com/juanfont/headscale/releases/download/v0.15.0-beta5/headscale_0.15.0-beta5_linux_amd64 -O /opt/headscale/bin/headscale

  3. Add headscale ~/.bashrc echo PATH=$PATH:/opt/headscale/bin >> ~/.bashrc

  4. Source the new PATH source ~/.bashrc


Create config

Create a config in /opt/headscale/config/config.yml

nano config.yaml

---
# The url clients will connect to.
# Typically this will be a domain.
server_url: https://headscale.domain.com

# Address to listen to / bind to on the server
listen_addr: 127.0.0.1:8080

# Address to listen to /metrics, you may want
# to keep this endpoint private to your internal
# network
#
metrics_listen_addr: 127.0.0.1:9090

# Address to listen for gRPC.
# gRPC is used for controlling a headscale server
# remotely with the CLI
# Note: Remote access _only_ works if you have
# valid certificates.
grpc_listen_addr: 127.0.0.1:50443

# Allow the gRPC admin interface to run in INSECURE
# mode. This is not recommended as the traffic will
# be unencrypted. Only enable if you know what you
# are doing.
grpc_allow_insecure: true

# Path to WireGuard private key file
private_key_path: /opt/headscale/data/private.key

ip_prefixes: 100.64.0.0/10

# DERP is a relay system that Tailscale uses when a direct
# connection cannot be established.
# https://tailscale.com/blog/how-tailscale-works/#encrypted-tcp-relays-derp
#
# headscale needs a list of DERP servers that can be presented
# to the clients.
derp:
  server:
    # If enabled, runs the embedded DERP server and
    # merges it into the rest of the DERP config
    # The Headscale server_url defined above MUST
    # be using https, DERP requires TLS to be in place
    enabled: true

    # Region ID to use for the embedded DERP server.
    # The local DERP prevails if the region ID collides
    # with other region ID coming from
    # the regular DERP config.
    region_id: 999

    # Region code and name are displayed in
    # the Tailscale UI to identify a DERP region
    region_code: "name01"
    region_name: "Domain DERP 01"

    # If enabled, also listens in UDP at the configured address
    # for STUN connections to help on NAT traversal
    # For more details on how this works
    # check this great article: https://tailscale.com/blog/how-tailscale-works/
    stun:
      enabled: true
      listen_addr: "0.0.0.0:3478"

  # List of externally available DERP maps encoded in JSON
  #urls:
  #  - https://controlplane.tailscale.com/derpmap/default

  # Locally available DERP map files encoded in YAML
  #
  # This option is mostly interesting for people hosting
  # their own DERP servers:
  # https://tailscale.com/kb/1118/custom-derp-servers/
  #
  # paths:
  #   - /etc/headscale/derp-example.yaml
  paths: []

  # If enabled, a worker will be set up to periodically
  # refresh the given sources and update the derpmap
  # will be set up.
  auto_update_enabled: true

  # How often should we check for updates?
  update_frequency: 24h


# Disables the automatic check for updates on startup
disable_check_updates: false
ephemeral_node_inactivity_timeout: 30m

# SQLite config
db_type: sqlite3
db_path: /opt/headscale/data/db.sql

# # Postgres config
# db_type: postgres
# db_host: localhost
# db_port: 5432
# db_name: headscale
# db_user: foo
# db_pass: bar

acme_url: https://acme-v02.api.letsencrypt.org/directory
acme_email: ""

#tls_letsencrypt_hostname: ""
#tls_letsencrypt_listen: ":http"
#tls_letsencrypt_cache_dir: ".cache"
#tls_letsencrypt_challenge_type: HTTP-01

#tls_cert_path: ""
#tls_key_path: ""

# Path to a file containg ACL policies.
acl_policy_path: "/opt/headscale/config/acls.yaml"
#acl_policy_path: "/opt/headscale/config/acls.hjson"

#dns_config:
#  # Upstream DNS servers
#  nameservers:
#    - 100.64.0.1
#    - 107.152.39.57
#  domains: []

#  magic_dns: true
#  base_domain: domain.com

Create systemd script

nano /etc/systemd/system/headscale.service

[Unit]
Description=headscale
After=network.target

[Service]
WorkingDirectory=/opt/headscale/config
ExecStart=/opt/headscale/bin/headscale serve
# Disable debug mode
Environment=GIN_MODE=release

[Install]
WantedBy=multi-user.target

Now we can start the headscale service systemctl enable --now headscale.service


Now it’s time to configure NGiNX

Unlink default config unlink /etc/nginx/sites-enabled/default

Create new headscale config nano /etc/nginx/conf.d/headscale.domain.com.conf

server {
  listen 80;
  server_name headscale.domain.com;
  return 301 https://headscale.domain.com$request_uri;
}

server {
  server_name headscale.domain.com;

  client_body_timeout 5m;
  client_header_timeout 5m;

  access_log            /var/log/nginx/foobar.example.com.access.log;
  error_log            /var/log/nginx/foobar.example.com.error.log info;

  # reverse proxy
  location / {
     proxy_pass http://127.0.0.1:8080;  # headscale listen_addr
     proxy_read_timeout 6m;
     proxy_ignore_client_abort off;
     proxy_request_buffering off;
     proxy_buffering off;
     proxy_no_cache "always";
     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    #Websockets
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
  }

  # API access
  location /headscale {
     grpc_pass grpc://127.0.0.1:50443;
  }

  # reverse proxy
  location /metrics {
     proxy_pass http://127.0.0.1:9090/metrics;  # headscale listen_addr
     proxy_read_timeout 6m;
     proxy_ignore_client_abort off;
     proxy_request_buffering off;
     proxy_buffering off;
     proxy_no_cache "always";
     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

     auth_basic "Password please";
     auth_basic_user_file /etc/nginx/passwd/headscale-metrics;
  }

  listen 443 ssl http2;
  ssl_certificate       /etc/nginx/ssl/headscale.domain.com/fullchain.crt;
  ssl_certificate_key   /etc/nginx/ssl/headscale.domain.com/key;
}

Now it’s time to configure UFW

ufw allow http ufw allow https


Now it’s time to install tailscale. Below is a useful script to do so

#!/bin/bash

apt-get update
apt-get install -y apt-transport-https gnupg2

# Add the Tailscale repo

source /etc/os-release

test $VERSION_ID = "7" && versionName="wheezy"
test $VERSION_ID = "8" && versionName="jessie"
test $VERSION_ID = "9" && versionname="stretch"
test $VERSION_ID = "10" && versionName="buster"
test $VERSION_ID = "11" && versionName="bullseye"

curl -fsSL https://pkgs.tailscale.com/stable/debian/${versionName}.gpg | apt-key add -
curl -fsSL https://pkgs.tailscale.com/stable/debian/${versionName}.list | tee /etc/apt/sources.list.d/tailscale.list

apt-get update
apt-get install -y tailscale

tailscale up --login-server https://headscale.domain.com

This should give you a URL to go to. It will tell you what to time into the headscale CLI. Make sure your in the /opt/headscale/config directory and follow the link.


If you’d like to script adding it you can use the script below and just use the KEY part of the URL. This will be ran as script network_name node_key. This will auto add the node to the network

#!/bin/bash

network=$1
key=$2

data=$(curl -s "https://headscale.domain.com/register?key=${key}")
command=$(echo "${data}" | grep '<b>' | grep headscale | cut -d'>' -f2 | cut -d'<' -f1 | sed "s/NAMESPACE/$network/g")
$command