🔐 Setting Up Headscale and Tailscale for Secure Private Networking: A Step-by-Step Guide
Shubham Kumar

Shubham Kumar @shubhamkcloud

About: AWS|Cloud Infrastructure|AWS Multi Account Architecture Setup|MicroServices|DevOps|GenAI|K8S|Data Engineer|Cloud Architecture|Infra automation|Cloud Security|Cloud Administrator|AWS Cost Optimisation|

Joined:
Jul 21, 2021

🔐 Setting Up Headscale and Tailscale for Secure Private Networking: A Step-by-Step Guide

Publish Date: Jul 24
6 0

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

Enter fullscreen mode Exit fullscreen mode

3. Create a User in Headscale

sudo headscale users create employee

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode
  • On the node, run the following command to connect node:
sudo tailscale up --login-server "https://<your-headscale-ec2-ip>" --authkey <generated-key>

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"]
}
  ]
}
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode
  • Restart the headscale to update the ACL:
sudo systemctl stop headscale
sudo systemctl start headscale
Enter fullscreen mode Exit fullscreen mode
  • Check the Headscale status
sudo systemctl status headscale
Enter fullscreen mode Exit fullscreen mode

✅ Final Test

Test 1 - Connectivity Check
From the intern's laptop, run:

telnet <app server ip> 88888
Enter fullscreen mode Exit fullscreen mode

Response should be connected.

Test 2 - Web Access Test
Open the browser and visit:

http://<tailscale-ip-of-app-server>:8888
Enter fullscreen mode Exit fullscreen mode

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!!

Comments 0 total

    Add comment