# Architecture ## Overview The infrastructure spans four physical locations connected by a Tailscale mesh network. All public traffic enters through a single Hetzner Cloud VPS (helsinki-a) running Caddy as a reverse proxy, which forwards requests over Tailscale to backend services running on physical servers in London and Copenhagen. The setup is entirely self-hosted (with the exception of Hetzner Cloud VPSs and Cloudflare for DNS/CDN). Servers are old personal computers repurposed into server duty — cheaper than cloud, and I get a rack cabinet that doubles as a bedroom white noise machine. ## Network Topology ```mermaid graph TD CF["Cloudflare
DNS + CDN
*.pez.sh"] CF -->|HTTPS| HEL HEL["helsinki-a
Hetzner Cloud VPS

Caddy (reverse proxy)
Authelia (SSO)
Bitwarden
LLDAP"] HEL --> TS["Tailscale Mesh
WireGuard-based VPN"] TS --> LB["london-b
Storage / Media
Docker services
(46T ZFS)"] TS --> LA["london-a
Monitoring
Prometheus / Grafana
(FreeBSD)"] TS --> NA["nuremberg-a
Mail
poste.io
(Alpine)"] TS --> CA["copenhagen-a
Gaming
Minecraft / WoW/MaNGOS
(Ubuntu)"] TS --> CC["copenhagen-c
(idle)"] style CC stroke-dasharray: 5 5 ``` ## Traffic Flow All public-facing services follow the same pattern: ``` User → Cloudflare (DNS + TLS) → helsinki-a (Caddy) → Backend (over Tailscale) ``` 1. DNS for `*.pez.sh` is managed by Cloudflare (provisioned via Terraform) 2. Cloudflare proxies traffic to helsinki-a 3. Caddy on helsinki-a terminates TLS and routes to the correct backend 4. For protected services, Caddy calls Authelia first (`forward_auth`) 5. If authenticated (or no auth required), traffic is proxied over Tailscale to the backend ```mermaid graph LR subgraph "helsinki-a (Caddy)" A1["forward_auth → Authelia"] A2["(no auth)"] A3["forward_auth → Authelia"] A4["(local)"] end R["radarr.pez.sh"] --> A1 --> LB1["london-b:7878"] J["jellyfin.pez.sh"] --> A2 --> LB2["london-b:8096"] G["grafana.pez.sh"] --> A3 --> LA["london-a:3000"] AU["auth.pez.sh"] --> A4 --> LO["localhost:9091"] ``` ## Auth Architecture ```mermaid graph TD Caddy["Caddy
forward_auth"] --> Authelia["Authelia
SSO
auth.pez.sh"] Authelia --> LLDAP["LLDAP
User directory"] ``` Authelia authenticates against LLDAP (both on helsinki-a). One place to manage users — add or remove someone in LDAP and it propagates to all protected services. Services with their own auth (Bitwarden, Jellyfin, Plex, Nextcloud, Navidrome, Jellyseerr) are not behind Authelia. ## Design Principles - **Self-hosted first.** Cloud VPSs only where it makes sense (public gateway, mail with clean IP reputation). Everything else runs on physical hardware I own. - **Tailscale as the backbone.** No ports exposed on residential IPs. All inter-server communication goes over the mesh. - **Ansible for everything.** If a server dies, reinstall the OS, install Tailscale, run Ansible. 30 minutes to full recovery. - **Terraform for DNS.** All Cloudflare records are in code. No clicking around in dashboards. - **Cattle, not pets (as much as possible).** The servers are technically pets — old hardware in specific locations — but the configs are cattle. Everything is reproducible from this repo.