In this blog, we'll walk through how to set up Headscale, a self-hosted alternative to Tailscale coordination server and connect multiple systems using Tailscale — all within your own private network. This setup enables secure and seamless access between devices, governed by customisable ACLs (Access Control Lists).
🧩 What Are Tailscale and Headscale?
Tailscale is a modern VPN built on WireGuard. It creates secure peer-to-peer connections between devices, even behind NATs and firewalls.
Headscale is an open-source self-hosted control server that manages Tailscale nodes without relying on Tailscale's cloud infrastructure.
By setting up Headscale and connecting clients via Tailscale, you gain full control over your network topology, access rules, and data flow.
🛠️ Step-by-Step Implementation
1. Provision an EC2 Server for Headscale
First, we spin up an EC2 instance (Ubuntu is preferred) to host our Headscale server. Make sure to:
Open required ports in your EC2 security group (e.g., 443 for HTTPS, 80 for HTTP if using a web UI later).
Assign a static public IP or use Elastic IP for persistent access.
2. Install and Configure Headscale
SSH into the EC2 instance and follow these steps:
# Update packages
sudo apt update && sudo apt upgrade -y
# Install dependencies
sudo apt install -y curl git sqlite3
# Install Headscale binary
curl -fsSL https://tailscale.com/headscale/install.sh | sh
# Generate configuration
sudo headscale generate-config > /etc/headscale/config.yaml
# Start Headscale service
sudo systemctl enable --now headscale
3. Create a User in Headscale
sudo headscale users create employee
This creates a user under which multiple devices can be registered.
4. Register a Node (Application Server)
Now, create another EC2 server that we’ll use to host a sample application:
For the testing purpose, I am simply running a sample application
# Start a simple web server on port 8888
python3 -m http.server 8888
Make sure to open port 8888 on the EC2 security group.
To add this server as a node:
- Install Tailscale on the node.
- Get a join URL from Headscale, run the following command on headscale server :
sudo headscale preauthkeys create --reusable --expiration 24h --user employee
- On the node, run the following command to connect node:
sudo tailscale up --login-server "https://<your-headscale-ec2-ip>" --authkey <generated-key>
You can also try with http for the learning and POC purpose.
The server is now securely connected to the Headscale-managed private network.
- Go to the headscale server and confirm the node has been attached successfully.
sudo headscale list nodes
5. Add a Client System (Intern's Laptop)
Repeat the above Tailscale installation steps on another system (e.g., your laptop or intern’s machine), using a different auth key or the same reusable one. Once connected, this device becomes a node in your mesh network.
6. Define ACLs to Control Access
To restrict access based on roles, define ACLs in the Headscale configuration file.
- Create a file with name acl.hujson and save this file on the location /etc/headscale. You can refer the following ACL for example
{
"groups": {
"group:employees": [
"username1",
"username2"
],
"group:interns": [
"shubham.kumar"
]
},
"acls": [
{
"action": "accept",
"src": ["group:employees"],
"dst": ["*:*"]
},
{
"action": "accept",
"src": ["group:interns"],
"dst": ["<app server ip>:8888"]
}
]
}
This is just a simple test policy which is allowing full access to the employee group and only a specific IP on specific port access to the Intern group.
- In the headscale config file which is present at /etc/headscale, add/uncomment the following -
policy:
path: "/etc/headscale/acl.hujson"
- Restart the headscale to update the ACL:
sudo systemctl stop headscale
sudo systemctl start headscale
- Check the Headscale status
sudo systemctl status headscale
✅ Final Test
Test 1 - Connectivity Check
From the intern's laptop, run:
telnet <app server ip> 88888
Response should be connected.
Test 2 - Web Access Test
Open the browser and visit:
http://<tailscale-ip-of-app-server>:8888
Confirm that access works only as defined in the ACL rules.
Test 3 - ACL Enforcement
Change the ACL policy to remove access or modify the port, then restart Headscale:
# Example change: remove 8888 access for interns
Now retry access — it should be blocked as per the updated ACL.
I hope this guide helps you get started with setting up your own Headscale-Tailscale network. Setting up your own secure, self-hosted VPN network doesn't have to be complex — and with Headscale and Tailscale, it's easier than ever. Try it out and take full control of your network access.
If you have questions or want to share your experience, feel free to drop a comment or reach out to me at https://www.linkedin.com/in/shubham-kumar1807/
Good Luck!!