kotoyuuko

CORE

昨日より、明日より、笑える今が一番大好き。
github
telegram
email

Self-hosted Headscale + DERP server

Previously, I accessed my NAS at home through port mapping, but there were some issues:

  • Need to configure DDNS, there may be a delay when switching IP
  • Accessing web services requires adding ports
  • Only expose certain services, more critical services (such as router configuration) cannot be accessed publicly

Recently, I have researched and tried various networking solutions (ZeroTier/WireGuard/Tailscale), and finally chose to build my own Headscale solution.

Preparation#

  • A VPS, with Docker and Docker Compose installed, and the following ports open:
    • 8080 Headscale HTTP
    • 9090 Headscale Metrics
    • 8443 DERP server HTTPS
    • 3478/udp DERP server STUN
    • 8008 Headscale-Admin
  • Client (Linux/macOS/Windows/iOS/Android)

Deploy DERP Server#

Create a directory to store the configuration file for the DERP server

mkdir -p /app/derper
cd /app/derper

Create a docker-compose.yaml file with the following content:

services:
  derper:
    image: ghcr.nju.edu.cn/yangchuansheng/ip_derper
    container_name: derper
    restart: always
    volumes:
      - ./config:/etc/headscale
    ports:
      - 8443:443
      - 3478:3478/udp

Here, the GHCR image provided by Nanjing University is used. If not needed, replace ghcr.nju.edu.cn with ghcr.io

Finally, start the DERP server

docker compose pull
docker compose up -d

Create a derp.json configuration file and upload it to a location that can be directly accessed:

{
  "Regions": {
    "901": {
      "RegionID": 901,
      "RegionCode": "cn-hangzhou",
      "RegionName": "Hangzhou",
      "Nodes": [
        {
          "Name": "901h",
          "RegionID": 901,
          "DERPPort": 8443,
          "HostName": "<IP>",
          "IPv4": "<IP>",
          "InsecureForTests": true
        }
      ]
    }
  }
}

Here, it is assumed that we have uploaded it to https://example.com/derp.json

Deploy Headscale#

Create the Headscale configuration file directory

mkdir -p /app/headscale
cd /app/headscale
mkdir config
touch config/db.sqlite
wget -O config/config.yaml https://raw.githubusercontent.com/juanfont/headscale/main/config-example.yaml

Modify the configuration in the config/config.yaml file:

  • Modify server_url to http://<IP>:8080
  • Modify listen_addr to 0.0.0.0:8080
  • Modify metrics_listen_addr to 0.0.0.0:9090 (optional)
  • Modify grpc_listen_addr to 0.0.0.0:50443
  • Modify noise.private_key_path to /etc/headscale/noise_private.key
  • Modify prefixes.v4 and prefixes.v6 as needed
  • Modify derp.urls[0] to the URL of the uploaded derp.json file, which is https://example.com/derp.json here
  • Modify database.sqlite.path to /etc/headscale/db.sqlite
  • Modify dns_config.nameservers to 223.5.5.5 and 223.6.6.6 (modify according to your situation)
  • Modify dns_config.base_domain to your own domain name, which will be displayed in the client

Create a docker-compose.yaml file:

services:
  headscale:
    image: headscale/headscale:0.23.0-alpha8
    container_name: headscale
    restart: always
    command: serve
    volumes:
      - ./config:/etc/headscale
    ports:
      - 8080:8080
      - 9090:9090

Finally, start Headscale

docker compose pull
docker compose up -d

Deploy Headscale-Admin#

Create the Headscale-Admin configuration file directory

mkdir -p /app/headscale-admin
cd /app/headscale-admin

Due to the HTTP security restrictions of the browser, direct access through the domain name will result in cross-origin issues. Here, we use Caddy as a reverse proxy to avoid this

nano Caddyfile

The content is as follows:

:{$PORT:80} {
  reverse_proxy /api/* <IP>:8080
  root * /app
  encode gzip zstd
  try_files {path}.html {path}
  file_server
}

Create a docker-compose.yaml file:

services:
  headscale-admin:
    image: goodieshq/headscale-admin:latest
    container_name: headscale-admin
    restart: unless-stopped
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
    environment:
      - PORT=80
      - ENDPOINT=/headscale
    ports:
      - 8008:80

Finally, start it

docker compose pull
docker compose up -d

The API Key for Headscale can be created with the following command:

docker exec -it headscale headscale apikey create

The Web UI can be accessed at http://<IP>:8008/headscale, and the Legacy API option should be unchecked in the configuration.

Create Users#

Execute the following command on the server

docker exec -it headscale headscale users create <your_username>

Client Configuration#

Linux#

First, install the Tailscale client

curl -fsSL https://tailscale.com/install.sh | sh

Then, apply to join the Headscale network

tailscale up --login-server=http://<IP>:8080 --accept-routes

If you need to access other networks through this node, add the --advertise-routes parameter:

tailscale up --login-server=http://<IP>:8080 --advertise-routes=192.168.1.0/24 --accept-routes

Open the link in the returned result, there is an authentication command, execute it on the server

docker exec headscale headscale nodes register --user <your_username> --key nodekey:******

Apple Devices#

Directly access http://<IP>:8080/apple and follow the steps.

Android#

Open this page on F-Droid to download the APK installation package:

f-droid.org/en/packages/com.tailscale.ipn

After installation, open the app, tap the three dots in the upper right corner multiple times, and the Change Server option will appear. Enter http://<IP>:8080 and follow the prompts to complete the authentication.

Windows#

I don't have a Windows device at home, so I can't test and verify it. Please refer to the tutorials in the reference materials.

Reference Materials#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.