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 HTTP9090
Headscale Metrics8443
DERP server HTTPS3478/udp
DERP server STUN8008
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
withghcr.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
tohttp://<IP>:8080
- Modify
listen_addr
to0.0.0.0:8080
- Modify
metrics_listen_addr
to0.0.0.0:9090
(optional) - Modify
grpc_listen_addr
to0.0.0.0:50443
- Modify
noise.private_key_path
to/etc/headscale/noise_private.key
- Modify
prefixes.v4
andprefixes.v6
as needed - Modify
derp.urls[0]
to the URL of the uploadedderp.json
file, which ishttps://example.com/derp.json
here - Modify
database.sqlite.path
to/etc/headscale/db.sqlite
- Modify
dns_config.nameservers
to223.5.5.5
and223.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.