Self-Hosting Headscale + Tailscale: Secure Access to Your Home Network via EC2

Most developers and homelab enthusiasts want secure, zero-config access to their home network from anywhere—without exposing ports or struggling with dynamic DNS. Tailscale offers this using WireGuard and NAT traversal, but for privacy, control, or cost reasons, you might prefer to self-host your coordination server.

This post walks through:

  1. Deploying Headscale (the open-source Tailscale coordination server) in Docker on AWS EC2
  2. Connecting devices (Mac, Windows, iOS, Raspberry Pi, etc.)
  3. Accessing your home LAN securely by advertising and enabling subnet routes

Why Tailscale?

Tailscale creates a secure, encrypted mesh network between your devices. Each device gets a unique IP in the 100.x.y.z range and can directly communicate with others, bypassing NAT, firewalls, and even CG-NAT. Key benefits:

  • Zero port forwarding
  • Single sign-on and ACLs
  • Cross-platform clients
  • MagicDNS for easy name resolution
  • Optional exit nodes and subnet routes

However, using Tailscale.com means trusting a third-party control plane. That’s where Headscale comes in.


(1) Docker-Based Headscale on EC2

Assuming you’re comfortable with SSH, Docker, and DNS:

Prerequisites

  • EC2 instance (Ubuntu 22.04 or 24.04 preferred)
  • Domain/subdomain pointing to your instance (e.g., headscale.example.com)
  • Docker and Docker Compose
  • Basic UFW firewall rules allowing ports 80, 443, 3478/udp, and 41641/udp

Docker Compose Example

services:
  headscale:
    image: headscale/headscale:latest
    container_name: headscale
    restart: always
    ports:
      - "8080:8080"         # Headscale web API
    volumes:
      - ./config:/etc/headscale
      - ./data:/var/lib/headscale
    command: headscale serve

Above, also assumes you know how to reverse proxy tailscale. Such as caddy.

Initialize Headscale:

docker exec headscale headscale users create <user>
docker exec headscale headscale preauthkeys create --reusable --expiration 24h

<user> is the user you’re creating.

Use the output URL (or tailscale up –login-server) to connect clients.


(2) Installing Clients on Devices

macOS / Linux / Windows

Install the official Tailscale client, then run:

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

iOS / Android

Install the app, then visit the /register URL from Headscale to authenticate. You can also generate a reusable key with –ephemeral for IoT-style devices.

Raspberry Pi

Install via:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --login-server https://headscale.example.com \
  --advertise-routes=192.168.0.0/24 \
  --accept-routes

This advertises your home subnet to the Tailscale network.


(3) Enabling Subnet Routes to Your LAN

After advertising the subnet, you’ll need to enable it manually via Headscale:

docker exec headscale headscale routes list
# Note the ID for the route, e.g., 1

docker exec headscale headscale routes enable --route 1

Now all devices on your Tailscale network can access your local network (e.g., 192.168.0.x) via your Pi.

Make sure IP forwarding is enabled and iptables/nftables allows routing:

echo 1 > /proc/sys/net/ipv4/ip_forward

Use a systemd script or rc.local to persist this change across reboots.


Final Thoughts

Using Tailscale with Headscale gives you:

  • Private WireGuard-based networking
  • Access to all your devices, including those behind CG-NAT
  • Control over coordination with no third-party dependency
  • Subnet routing, acting like a VPN into your LAN

You maintain complete control of authentication, ACLs, and key expiration—ideal for those who want privacy and flexibility.

Whether you’re backing up photos from your phone to your NAS, administering a Docker swarm, or checking on a security cam, this is one of the cleanest ways to securely bridge multiple networks.


Posted

in

by

Tags:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.