brew install doppiscantsleep/shireguard/shireguard
shireguard login
shireguard register-device
shireguard up
/usr/local/bin, and sets the required capability.curl -sSL https://shireguard.com/install.sh | bash
curl, tar, and libcap2-bin.
On Debian/Ubuntu: sudo apt install curl tar libcap2-bin
shireguard login
shireguard register-device
shireguard up
| Name | Platform | IP Address | Status | Last Seen | Version |
|---|
| Device | Quality | Latency | Jitter | Loss | Type |
|---|
| Name | Prefix | Created | Last Used | Expires |
|---|
| When | Who | Action | Detail |
|---|
Shireguard is a private mesh VPN built on WireGuard. When you run shireguard up, your device
creates an encrypted tunnel directly to every other device in your network — or, when a direct path is blocked by NAT,
via a relay server. The control plane (this dashboard) manages identities, device registration, and peer discovery.
Nothing in the data path touches Cloudflare or the relay unless it has to.
WireGuard is a modern VPN protocol designed to be simpler, faster, and more auditable than IPsec or OpenVPN. It operates at Layer 3 (network layer), wrapping IP packets inside encrypted UDP datagrams. There is no TCP, no handshake overhead at the transport layer — every WireGuard packet is a self-contained, authenticated UDP frame.
Why UDP? TCP connections carry retransmission and ordering state. If you tunnel TCP-over-TCP and either layer drops a packet, you get a "TCP meltdown" where both layers fight over retransmission. UDP avoids this entirely. Applications running inside the tunnel use their own TCP/UDP as normal — WireGuard's UDP is just the carrier.
wireguard-go,
a pure Go implementation. On macOS it creates a utun interface assigned by the kernel (e.g. utun8).
On Linux it creates a tun device and requires cap_net_admin
(set via setcap — no root needed at runtime).
Shireguard uses federated identity — no passwords are stored. You prove who you are to Apple, Google, or GitHub, and they vouch for you to the control plane. The control plane then issues its own short-lived JWT access token that clients present on every API call.
Browser login flow (web dashboard)
CLI login flow (polling)
response_mode=form_post,
meaning the callback arrives as an HTTP POST. Browsers block HTTPS→HTTP redirects (localhost), so the usual
"open a local callback server" trick doesn't work. Polling a shared KV key avoids that restriction entirely
— the browser never needs to reach back to the CLI process.
JWT_SECRET.
Contains sub (user ID), email,
iat, exp.
Expires in 15 minutes. Algorithm is validated on verify — no alg:none attacks.
appleid.apple.com,
aud = service ID, exp > now.
Apple sends user info (email) only on first login via form_post.
accounts.google.com or
https://accounts.google.com.
GitHub: no id_token — exchanges code for access token, then calls
api.github.com/user directly.
Rate limiting is applied to all auth endpoints using a fixed-window counter stored in Cloudflare KV. Keys are epoch-scoped — rl:<action>:<ip>:<epoch> — so counters naturally expire without a separate cleanup pass. Every key is written with a TTL of 2× the window size.
The control plane runs on Cloudflare Workers — a V8 isolate runtime that executes at Cloudflare's edge (200+ locations). There are no traditional servers. Requests are handled by the nearest PoP, cold-start time is measured in milliseconds, and there is no infrastructure to manage.
| Table | What it stores | Key fields |
|---|---|---|
| users | One row per account. Identity linked from OAuth providers. | id, email, apple_sub, google_sub, github_id |
| devices | Every registered machine — its WireGuard public key and assigned IP. | id, user_id, network_id, public_key, assigned_ip, endpoint, last_seen_at |
| networks | Private networks (default: one per user, CIDR 100.65.0.0/16). | id, user_id, name, cidr |
| api_keys | Programmatic access credentials. Key is stored as SHA-256 hash only. | key_hash, prefix, expires_at, last_used_at |
| peer_metrics | Latency, jitter, packet loss, connection type per device-pair. | device_id, peer_device_id, latency_ms, connection_type |
The shireguard binary is a Go application
that manages the local WireGuard interface and maintains a live connection to the control plane.
It runs in the foreground (shireguard up)
and exits cleanly on interrupt, tearing down the TUN interface.
register-device
and saved to ~/.config/shireguard/privatekey.
The public key is uploaded to the control plane. The private key never leaves the device.
TUN vs TAP: Shireguard uses a TUN (tunnel) device, not TAP. TUN operates at Layer 3 — it carries IP packets only. TAP operates at Layer 2 and carries Ethernet frames. WireGuard is a Layer 3 protocol so a TUN device is the correct fit; it also has significantly less overhead because there are no MAC address lookups or ARP broadcasts.
WireGuard itself has no built-in discovery mechanism — you must tell it where each peer is. Shireguard solves this with a real-time WebSocket signaling channel backed by a Cloudflare Durable Object (a single-instance actor with strong consistency).
One Durable Object instance exists per network ID. All devices in a network share the same DO instance, so messages delivered there are immediately visible to all connected peers. When a new device joins, it receives the current state of all existing peers in its first message.
Most devices sit behind NAT (Network Address Translation) — their router rewrites the source IP and port on outbound packets and maps inbound packets back. This works fine for outbound connections, but two NATted devices cannot directly reach each other without help.
Direct path — UDP hole punching
WireGuard's built-in keepalive packets (sent every ~25 s by default) maintain these NAT mappings. As long as at least one side sends traffic regularly, the mapping stays open and the direct tunnel survives indefinitely.
Relay path — when direct fails
Some NAT implementations (symmetric NAT, carrier-grade NAT) assign a different external port for each destination, making hole punching unreliable. In those cases Shireguard falls back to a relay server hosted on AWS Lightsail.
The Metrics page shows whether each peer pair is using a direct or relay connection. Direct is always preferred; relay is a transparent fallback that activates automatically.
| Layer | Technology | Purpose | Protocol |
|---|---|---|---|
| Identity | Apple / Google / GitHub OAuth 2.0 | Prove who you are without passwords | HTTPS / RS256 JWT |
| Session | HS256 JWT + Cloudflare KV refresh token | Short-lived access, long-lived refresh | HTTPS Bearer |
| Control plane | Cloudflare Workers + D1 + Hono | Device registry, peer list, metrics | HTTPS / REST |
| Signaling | Cloudflare Durable Object WebSocket | Real-time peer endpoint exchange | WSS |
| Tunnel (direct) | WireGuard / wireguard-go | Encrypted peer-to-peer data plane | UDP (port 51820) |
| Tunnel (relay) | Go UDP relay on AWS Lightsail | NAT traversal fallback | UDP (port 51821) |
| TUN device | utun (macOS) / tun (Linux) | Inject/extract packets from the OS network stack | IP (Layer 3) |
| Distribution | Homebrew tap / install.sh | Install the client binary | HTTPS |
Shireguard weaves secret tunnels — WireGuard pathways sealed with cryptographic keys that only trusted devices may hold — so that your machines may speak freely across any distance, as if gathered around the same hearth in Bag End.