A self-hosted mesh VPN that connects your devices into a private network, allowing them to communicate as if on the same LAN — from anywhere in the world. Think Tailscale, but self-hosted.
┌─────────────────────────────────────────────────────────────┐
│ CONTROL SERVER │
│ (VPS with public IP) │
│ │
│ • Device registry (public keys, endpoints, IPs) │
│ • Authenticates devices via network key │
│ • Assigns virtual IPs (10.100.0.x) │
│ • Coordinates peer discovery │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Laptop │◄══►│ Phone │◄══►│ Server │
└──────────┘ └──────────┘ └──────────┘
▲ ▲ ▲
└═══════════════╧═══════════════┘
WireGuard P2P tunnels
- WireGuard-based — fast, modern, and secure (ChaCha20-Poly1305)
- Mesh topology — devices connect directly to each other (P2P)
- Central coordination — lightweight server handles discovery, not traffic
- Simple auth — shared network key (like a WiFi password)
- Virtual IPs — each device gets a stable
10.100.0.xaddress - Web dashboard — React UI for monitoring your network
- Go 1.23+
- Node.js 18+ and pnpm (for the dashboard)
go run ./cmd/tnnl-server --network-key "your-secret-key" --port 2424# Initialize device (generates WireGuard keypair)
go run ./cmd/tnnl init --name "laptop"
# Connect to your server
go run ./cmd/tnnl login your-secret-key --server http://your-server:2424
# Register with the network
go run ./cmd/tnnl register# View device info
go run ./cmd/tnnl status
# List all devices in the network
go run ./cmd/tnnl peersRepeat step 2 on each device you want to add to your network.
cd web && pnpm install && pnpm devgo build -o tnnl ./cmd/tnnl
go build -o tnnl-server ./cmd/tnnl-server
go build -o tnnl-client ./cmd/tnnl-client| Component | Binary | Purpose |
|---|---|---|
| Control Server | tnnl-server |
Central coordination, device registry, IP assignment |
| CLI | tnnl |
User commands (init, login, register, status, peers) |
| Daemon | tnnl-client |
Background service maintaining the VPN (WIP) |
| Dashboard | web/ |
React UI for network monitoring |
- Each device generates a WireGuard keypair on
tnnl init - Devices authenticate with the control server using a shared network key
- The server assigns each device a virtual IP and tracks its public key + endpoints
- Devices discover each other through the server and establish direct WireGuard tunnels
- All traffic between devices is encrypted end-to-end by WireGuard
| Endpoint | Method | Purpose |
|---|---|---|
/api/register |
POST | Register a device |
/api/peers |
GET | List all devices |
/api/heartbeat |
POST | Update device presence |
All endpoints require Authorization: Bearer <network-key>.
tnnl/
├── cmd/
│ ├── tnnl/ # CLI tool
│ ├── tnnl-server/ # Control server
│ └── tnnl-client/ # Daemon
├── internal/
│ ├── protocol/ # Shared types (Device, RegisterRequest, etc.)
│ ├── server/ # HTTP handlers, in-memory store
│ ├── client/ # Config management, API client
│ ├── wg/ # WireGuard key generation
│ └── netutil/ # Network utilities (planned)
├── web/ # React dashboard (Vite + TanStack Router + shadcn)
└── plan/ # Design docs and task tracking
Device config is stored at ~/.tnnl/config.json:
{
"server_url": "http://localhost:2424",
"network_key": "your-secret-key",
"device_id": "uuid...",
"device_name": "laptop",
"private_key": "base64...",
"public_key": "base64...",
"virtual_ip": "10.100.0.1"
}- Control server with REST API
- CLI (init, login, register, status, peers)
- WireGuard key generation
- Client-server communication
- Web dashboard scaffolding
- Endpoint discovery (local IPs + STUN)
- WireGuard interface management
- Daemon with heartbeat and peer sync
- WebSocket real-time peer updates
- DERP relay for NAT fallback
- NAT hole punching
- Cross-platform support (Linux, macOS, Windows)
- Network key — shared secret to join the network
- WireGuard keys — per-device Curve25519 keypairs; private keys never leave the device
- Encryption — all mesh traffic encrypted with WireGuard (ChaCha20-Poly1305)
- No root required — uses wireguard-go userspace implementation
MIT