Headscale with DERP
Self-hosted Tailscale control server with DERP relay for NAT traversal.
Overview
Headscale is an open-source, self-hosted implementation of the Tailscale coordination server that enables you to run your own mesh VPN infrastructure without relying on Tailscale's SaaS platform. Originally created by Juan Font, headscale provides full control over your WireGuard-based mesh network, allowing organizations to maintain complete sovereignty over their networking infrastructure while benefiting from Tailscale's elegant architecture. The project implements the Tailscale protocol to coordinate peer-to-peer connections between devices running standard Tailscale clients.
This configuration combines headscale with a custom DERP (Designated Encrypted Relay for Packets) server and headscale-ui management interface to create a comprehensive self-hosted mesh VPN solution. The DERP component serves as a relay server that facilitates connections between clients behind restrictive NATs or firewalls that prevent direct peer-to-peer communication. When direct connections fail, traffic is encrypted and relayed through your DERP server, ensuring reliable connectivity across diverse network environments. The headscale-ui component provides a web-based management interface for monitoring connected devices, managing users, and viewing network topology.
This stack is ideal for privacy-conscious organizations, security teams managing distributed infrastructure, and homelab enthusiasts who want enterprise-grade mesh networking without cloud dependencies. By running your own coordination and relay infrastructure, you eliminate external dependencies, reduce latency through strategically placed DERP relays, and maintain complete audit trails of network activity. This approach is particularly valuable for organizations with strict data residency requirements or those operating in air-gapped environments.
Key Features
- Self-hosted Tailscale coordination server with full protocol compatibility
- Integrated DERP relay server for reliable NAT traversal and connection fallback
- Web-based management interface for device enrollment and network visualization
- WireGuard-based mesh networking with automatic key rotation and peer discovery
- Multi-user namespace support with granular access control policies
- Built-in STUN server functionality for peer-to-peer connection establishment
- API-driven device management with support for programmatic client provisioning
- Custom DNS resolution with MagicDNS functionality for internal service discovery
Common Use Cases
- 1Enterprise organizations requiring complete control over mesh VPN infrastructure
- 2Development teams connecting distributed build servers and staging environments
- 3Security-conscious businesses avoiding third-party coordination servers
- 4Remote work setups needing secure access to on-premises resources
- 5Homelab enthusiasts building personal mesh networks across multiple locations
- 6Organizations with strict data residency requirements for networking metadata
- 7Air-gapped environments requiring isolated mesh networking capabilities
Prerequisites
- Domain name with DNS control for DERP server configuration
- SSL/TLS certificates for DERP server placed in ./certs directory
- Minimum 1GB RAM for stable operation of all three components
- Ports 80, 443, 3478/UDP, 8080, 8443, and 9090 available on host system
- Understanding of WireGuard concepts and mesh networking principles
- Basic knowledge of Tailscale client configuration and authentication flows
For development & testing. Review security settings, change default credentials, and test thoroughly before production use. See Terms
docker-compose.yml
docker-compose.yml
1services: 2 headscale: 3 image: headscale/headscale:latest4 container_name: headscale5 command: serve6 volumes: 7 - ./config:/etc/headscale8 - headscale-data:/var/lib/headscale9 ports: 10 - "8080:8080"11 - "9090:9090"12 networks: 13 - headscale-network14 restart: unless-stopped1516 headscale-ui: 17 image: ghcr.io/gurucomputing/headscale-ui:latest18 container_name: headscale-ui19 environment: 20 - HEADSCALE_URL=http://headscale:808021 ports: 22 - "8443:443"23 depends_on: 24 - headscale25 networks: 26 - headscale-network27 restart: unless-stopped2829 derper: 30 image: ghcr.io/tailscale/derper:latest31 container_name: derper32 environment: 33 - DERP_DOMAIN=${DERP_DOMAIN}34 - DERP_CERT_MODE=manual35 - DERP_ADDR=:44336 - DERP_HTTP_PORT=8037 - DERP_STUN_PORT=347838 - DERP_VERIFY_CLIENTS=false39 volumes: 40 - ./certs:/app/certs:ro41 ports: 42 - "443:443"43 - "80:80"44 - "3478:3478/udp"45 networks: 46 - headscale-network47 restart: unless-stopped4849volumes: 50 headscale-data: 5152networks: 53 headscale-network: 54 driver: bridge.env Template
.env
1# Headscale with DERP2DERP_DOMAIN=derp.example.com34# Create config/config.yaml with headscale configuration5# See: https://github.com/juanfont/headscale/blob/main/config-example.yamlUsage Notes
- 1Headscale API at http://localhost:8080
- 2Web UI at https://localhost:8443
- 3DERP relay for NAT traversal
- 4Create users: headscale users create <name>
- 5Generate auth keys for clients
Individual Services(3 services)
Copy individual services to mix and match with your existing compose files.
headscale
headscale:
image: headscale/headscale:latest
container_name: headscale
command: serve
volumes:
- ./config:/etc/headscale
- headscale-data:/var/lib/headscale
ports:
- "8080:8080"
- "9090:9090"
networks:
- headscale-network
restart: unless-stopped
headscale-ui
headscale-ui:
image: ghcr.io/gurucomputing/headscale-ui:latest
container_name: headscale-ui
environment:
- HEADSCALE_URL=http://headscale:8080
ports:
- "8443:443"
depends_on:
- headscale
networks:
- headscale-network
restart: unless-stopped
derper
derper:
image: ghcr.io/tailscale/derper:latest
container_name: derper
environment:
- DERP_DOMAIN=${DERP_DOMAIN}
- DERP_CERT_MODE=manual
- DERP_ADDR=:443
- DERP_HTTP_PORT=80
- DERP_STUN_PORT=3478
- DERP_VERIFY_CLIENTS=false
volumes:
- ./certs:/app/certs:ro
ports:
- "443:443"
- "80:80"
- 3478:3478/udp
networks:
- headscale-network
restart: unless-stopped
Quick Start
terminal
1# 1. Create the compose file2cat > docker-compose.yml << 'EOF'3services:4 headscale:5 image: headscale/headscale:latest6 container_name: headscale7 command: serve8 volumes:9 - ./config:/etc/headscale10 - headscale-data:/var/lib/headscale11 ports:12 - "8080:8080"13 - "9090:9090"14 networks:15 - headscale-network16 restart: unless-stopped1718 headscale-ui:19 image: ghcr.io/gurucomputing/headscale-ui:latest20 container_name: headscale-ui21 environment:22 - HEADSCALE_URL=http://headscale:808023 ports:24 - "8443:443"25 depends_on:26 - headscale27 networks:28 - headscale-network29 restart: unless-stopped3031 derper:32 image: ghcr.io/tailscale/derper:latest33 container_name: derper34 environment:35 - DERP_DOMAIN=${DERP_DOMAIN}36 - DERP_CERT_MODE=manual37 - DERP_ADDR=:44338 - DERP_HTTP_PORT=8039 - DERP_STUN_PORT=347840 - DERP_VERIFY_CLIENTS=false41 volumes:42 - ./certs:/app/certs:ro43 ports:44 - "443:443"45 - "80:80"46 - "3478:3478/udp"47 networks:48 - headscale-network49 restart: unless-stopped5051volumes:52 headscale-data:5354networks:55 headscale-network:56 driver: bridge57EOF5859# 2. Create the .env file60cat > .env << 'EOF'61# Headscale with DERP62DERP_DOMAIN=derp.example.com6364# Create config/config.yaml with headscale configuration65# See: https://github.com/juanfont/headscale/blob/main/config-example.yaml66EOF6768# 3. Start the services69docker compose up -d7071# 4. View logs72docker compose logs -fOne-Liner
Run this command to download and set up the recipe in one step:
terminal
1curl -fsSL https://docker.recipes/api/recipes/headscale-derp/run | bashTroubleshooting
- DERP server fails to start: Verify SSL certificates exist in ./certs directory with correct permissions and valid domain names
- Clients cannot connect through DERP relay: Check DERP_DOMAIN environment variable matches certificate common name and DNS resolution
- headscale-ui shows connection refused: Ensure headscale service is fully started before UI attempts connection, check network connectivity between containers
- Device registration fails with authentication errors: Verify pre-auth keys are generated correctly using headscale CLI and haven't expired
- Peer-to-peer connections not establishing: Confirm STUN server is accessible on port 3478/UDP and firewall allows WireGuard traffic
- API requests timing out: Increase headscale container memory allocation and check for resource constraints in container logs
Community Notes
Loading...
Loading notes...
Download Recipe Kit
Get all files in a ready-to-deploy package
Includes docker-compose.yml, .env template, README, and license
Components
headscaleheadscale-uiderper
Tags
#vpn#tailscale#headscale#wireguard#mesh-vpn
Category
Security & NetworkingAd Space
Shortcuts: C CopyF FavoriteD Download