--- title: Homelab Network Architecture created: 2026-04-29 updated: 2026-04-29 type: concept tags: [concept, networking, homelab, traefik, ha] sources: [] --- # Homelab Network Architecture Complete traffic flow and routing topology for the homelab cluster. Covers Traefik dual-instance HA, VRRP failover, certificate distribution, Docker network segmentation, and all routing rules. ## Traffic Flow Overview ``` Internet (Cloudflare DNS) │ ▼ *.tophermayor.com A → home public IP ══════════════════════════════════════════════════════════════════════ VRRP VIP 192.168.50.80/27 (eth0.50) — keepalived ┌─────────────────────────────────────────────────────────────┐ │ PRIMARY: ubuntu traefik (when up) │ │ BACKUP: grizzley traefik-pi (when ubuntu fails) │ └─────────────────────────────────────────────────────────────┘ │ ▼ port 80/443 ┌──────────────────────────────────────────────────────────────────┐ │ grizzley traefik-pi │ │ Edge ingress controller (ACME master, Cloudflare DNS challenge) │ │ IP: 192.168.50.84 | Ports: 80,443,2222,8080,19132udp,19134udp │ │ Network: traefik-proxy │ │ Certs: /mnt/truenas/traefik-certs/grizzley (NFS) │ └──────────────────────────────────────────────────────────────────┘ │ ├──[grizzley-local services]──────────────────────────► served directly │ vaultwarden, uptime-kuma, komodo, homepage, │ aiostreams, aiomanager, aiometadata, │ opencode-ice, homeassistant, proxmox, truenas │ └──[everything else]────────────────────────────────────► forwarded to ubuntu (upstream-ingress.yml load-balances to ubuntu:443) ``` ## DNS Zones | Zone | Example | Resolution | |------|---------|------------| | Public (`*.tophermayor.com`) | `gitea.tophermayor.com`, `jellyfin.tophermayor.com` | Cloudflare → home public IP | | Local (`*.local.tophermayor.com`) | `sonarr.local.tophermayor.com`, `proxmox.local.tophermayor.com` | UniFi Controller DHCP/DNS | Cloudflare proxies all `*.tophermayor.com` — origin IP is hidden, DDoS protection active. ## Network Segmentation ### Physical / VLAN | Network | Subnet | Gateway | Hosts | |---------|--------|---------|-------| | Production (VLAN 50) | 192.168.50.0/24 | 192.168.50.1 | ice, grizzley, ubuntu, proxmox, truenas | | Default (VLAN 1) | 192.168.1.0/24 | 192.168.1.1 | Management workstations | | Trusted (VLAN 3) | 192.168.3.0/24 | — | Trusted devices | | WireGuard VPN | 192.168.4.0/24 | — | VPN clients | | Docker bridge | 172.16.0.0/12 | — | Container internal networking | ### Docker Networks (ubuntu) | Network | Driver | Subnet | Connected Services | |---------|--------|--------|-------------------| | `proxy-net` | bridge | 172.18.0.0/16 | traefik (primary ingress), homepage-ubuntu | | `app-net` | bridge | 172.20.0.0/16 | general application containers | | `uefi-proxynet` | bridge | 172.26.0.0/16 | — | | `authentik_authentik-internal` | bridge | — | authentik server/worker/redis | | `monitoring_monitoring-internal` | bridge | — | prometheus, grafana, loki, alertmanager | | `immich_immich-internal` | bridge | — | immich stack | | `reccollection-internal` | bridge | — | reccollection stack | | `ai-subscriptions_default` | bridge | — | ai-subscriptions | | `infisical_infisical` | bridge | — | infisical stack | ### Docker Networks (grizzley) | Network | Driver | Connected Services | |---------|--------|-------------------| | `traefik-proxy` | bridge | traefik-pi, homepage-grizzley, komodo, aiostreams, aiomanager, aiometadata, vaultwarden, uptime-kuma | | `aiomanager_default` | bridge | aiomanager stack | | `aiometadata_aiometadata-internal` | bridge | aiometadata stack | | `komodo_komodo-internal` | bridge | komodo stack | | `homepage_default` | bridge | homepage-grizzley | | `desktop-test_default` | bridge | test containers | ## High Availability (VRRP / Keepalived) Two Traefik instances provide failover via keepalived VRRP on VLAN 50. | Parameter | Value | |-----------|-------| | Interface | `eth0.50` (VLAN 50) | | Virtual Router ID | 51 | | ubuntu priority | **PRIMARY** (higher) | | grizzley priority | **BACKUP** (90) | | Virtual IP | `192.168.50.80/27` | | Auth | PASS (`HomelabH`) | | Health check | `/etc/keepalived/check_traefik.sh` — 2s interval, fall 2, rise 2 | When ubuntu Traefik fails health checks, keepalived promotes grizzley to MASTER and the VIP moves to grizzley's interface. Traffic for `*.tophermayor.com` and `*.local.tophermayor.com` then routes to grizzley's traefik-pi (192.168.50.84). ## Certificate Architecture ``` Cloudflare DNS Challenge (grizzley traefik-pi) │ ▼ ACME writes certs to /etc/traefik/certs/acme.json │ ▼ (real-time via NFS) /mnt/truenas/traefik-certs/grizzley (NFS share from TrueNAS) │ ▼ (read by ubuntu traefik at startup/reread) ubuntu traefik serves same wildcard certs (*.tophermayor.com) ``` Both instances serve the **same** Cloudflare-issued wildcard certificate (`*.tophermayor.com`) for all public-facing services. The ACME challenge only runs on grizzley — ubuntu syncs certs via NFS. ## Traefik Instance Comparison | Aspect | ubuntu (PRIMARY) | grizzley (BACKUP / ACME) | |--------|-----------------|--------------------------| | Container | `traefik` | `traefik-pi` | | Image | `traefik:v3.6.7` | `traefik:v3.6.7` | | IP | 192.168.50.61 | 192.168.50.84 | | Port 80/443 | Direct | Direct | | HTTP→HTTPS | ✓ | ✓ | | Cloudflare ACME | ✗ (reads via NFS) | ✓ (origin) | | Static configs | `middlewares.yml` | `middlewares.yml` | | Dynamic configs | 29 files | 4 files | | Networks | `proxy-net`, `app-net`, `uefi-proxynet` | `traefik-proxy` | | Metrics port | — | 8080 | | SSH proxy port | — | 2222 | | UDP Minecraft | — | 19132, 19134 | | upstream-ingress | (receives traffic) | forwards to ubuntu | ## Traefik Dynamic Configs ### grizzley (Edge / ACME) | File | Contents | |------|---------| | `pi-routers.yml` | Wildcard cert triggers (`traefik-wildcard.local.tophermayor.com`, `traefik-wildcard.tophermayor.com`) | | `grizzley-services.yml` | 11 local routers: vaultwarden, uptime-kuma, komodo, homepage, opencode-ice, aiostreams, aiomanager, aiometadata, homeassistant, proxmox, truenas | | `upstream-ingress.yml` | Forwards all unmatched traffic to ubuntu Traefik (HTTPS 192.168.50.61) | | `metrics.yml` | Internal metrics endpoints | | `middlewares.yml` | IP allowlists (`local-only`, `homepage-localonly`), security headers | ### ubuntu (Primary Router) | File | Contents | |------|---------| | `gitea.yml` | gitea.tophermayor.com → gitea:3000 | | `immich.yml` | immich.tophermayor.com → immich_server:2283 | | `jellyfin.yml` | jellyfin.tophermayor.com → jellyfin:8096 (rate limit + jellyfin headers) | | `media-stack.yml` | sonarr, radarr, lidarr, prowlarr, qbittorrent, sabnzbd, readarr, sonarr-anime, radarr-anime, lazylibrarian, nzbdav → via gluetun VPN tunnel | | `opencode.yml` | opencode.tophermayor.com → host.docker.internal:4096 | | `proxmox.yml` | proxmox.local.tophermayor.com → https://192.168.50.11:8006 | | `homepage-widgets.yml` | Internal routes (sonarr-internal, radarr-internal, etc.) → gluetun VPN tunnel | | `upstream-ingress.yml` | Homepage routes to homepage-ubuntu:3003 and homepage-grizzley:3000 | | `whisper.yml` | whisper.local.tophermayor.com → faster-whisper-server:8394 | | `truenas.yml` | truenas.local.tophermayor.com → TrueNAS web UI | | `navidrome.yml` | navidrome.tophermayor.com | | `audiobookshelf.yml` | audiobooks.tophermayor.com | | `calibre-web.yml` | calibre-web.local.tophermayor.com | | `kavita.yml` | kavita.tophermayor.com | | `rustfs.yml` | rustfs S3 routes | | `stremio.yml` | stremio routes | | `jellyseerr.yml` | jellyseerr.tophermayor.com | | `comparaison.yml` | comparison service | | `inventory.yml` | inventory service | | `cabo-voting.yml` | Cabo voting app | | `gsd-mcp.yml` | GSD MCP server | | `ai-subscriptions.yml` | AI subscriptions service | | `hermes-dashboard.yml` | Hermes dashboard routes | | `homeassistant.yml` | Home Assistant route | | `umm.yml` | Unified media manager | | `middlewares.yml` | Full middleware stack (see below) | ## All Traefik Routes ### grizzley traefik-pi (Local Services) | Domain | Service | Backend | Middleware | Cert | |--------|---------|---------|------------|------| | `vaultwarden.tophermayor.com` | vaultwarden | vaultwarden:80 | — | cloudflare | | `status.tophermayor.com` | uptime-kuma | uptime-kuma:3001 | — | cloudflare | | `komodo.local.tophermayor.com` | komodo | komodo:9120 | — | cloudflare | | `homepage.local.tophermayor.com` | homepage | homepage-grizzley:3000 | homepage-localonly | cloudflare | | `opencode-ice.local.tophermayor.com` | opencode-ice | 192.168.50.197:4096 | local-only | cloudflare | | `aiostreams.tophermayor.com` | aiostreams | aiostreams:3002 | — | cloudflare | | `aiomanager.tophermayor.com` | aiomanager | aiomanager:1610 | — | cloudflare | | `aiometadata.tophermayor.com` | aiometadata | aiometadata:1337 | — | cloudflare | | `ha.tophermayor.com` | homeassistant | 192.168.30.196:8123 | — | cloudflare | | `proxmox.local.tophermayor.com` | proxmox | 192.168.50.11:8006 | local-only | cloudflare | | `truenas.local.tophermayor.com` | truenas | 192.168.50.12:8080 | local-only | cloudflare | | `traefik-grizzley.local.tophermayor.com` | dashboard | api@internal | local-only | cloudflare | | `metrics-grizzley.local.tophermayor.com` | metrics | api@internal | local-only | cloudflare | ### grizzley traefik-pi (Upstream → ubuntu) Traffic NOT matched above is forwarded via `upstream-ingress.yml`: | Rule | Target | |------|--------| | `HostRegexp(^[a-z0-9-]+\.local\.tophermayor\.com$) && !homepage && !traefik-grizzley && !metrics-grizzley && !traefik-wildcard && !opencode-ice` | → ubuntu:443 | | `HostRegexp(^[a-z0-9-]+\.tophermayor\.com$) && !traefik-wildcard` | → ubuntu:443 | ### ubuntu traefik (Public Routes — *.tophermayor.com) | Domain | Backend | Middleware | |--------|---------|------------| | `gitea.tophermayor.com` | gitea:3000 | homelab-public | | `immich.tophermayor.com` | immich_server:2283 | homelab-public | | `jellyfin.tophermayor.com` | jellyfin:8096 | ratelimit, jellyfin-headers | | `audiobooks.tophermayor.com` | audiobookshelf | homelab-public | | `navidrome.tophermayor.com` | navidrome | homelab-public | | `kavita.tophermayor.com` | kavita:5000 | homelab-public | | `opencode.tophermayor.com` | host.docker.internal:4096 | local-only, opencode-streaming, opencode-cors | | `ha.tophermayor.com` | 192.168.30.196:8123 | (see homeassistant.yml) | | `jellyseerr.tophermayor.com` | jellyseerr | homelab-public | ### ubuntu traefik (Local Routes — *.local.tophermayor.com) | Domain | Backend | Middleware | Notes | |--------|---------|------------|-------| | `sonarr.local.tophermayor.com` | gluetun:8989 | local-only | Via VPN tunnel | | `radarr.local.tophermayor.com` | gluetun:7878 | local-only | Via VPN tunnel | | `lidarr.local.tophermayor.com` | gluetun:8686 | local-only | Via VPN tunnel | | `sabnzbd.local.tophermayor.com` | gluetun:8080 | local-only | Via VPN tunnel | | `qbittorrent.local.tophermayor.com` | qbittorrent | local-only | | | `prowlarr.local.tophermayor.com` | prowlarr | local-only | | | `readarr.local.tophermayor.com` | readarr | local-only | | | `sonarr-anime.local.tophermayor.com` | sonarr-anime | local-only | Via VPN tunnel | | `radarr-anime.local.tophermayor.com` | radarr-anime | local-only | Via VPN tunnel | | `flaresolverr.local.tophermayor.com` | flaresolverr | local-only | | | `bazarr.local.tophermayor.com` | bazarr:6767 | local-only | | | `lazylibrarian.local.tophermayor.com` | lazylibrarian | local-only | | | `nzbdav.local.tophermayor.com` | nzbdav | local-only | | | `calibre-web.local.tophermayor.com` | calibre-web:8083 | local-only | | | `stremio.local.tophermayor.com` | stremio-server | local-only | | | `proxmox.local.tophermayor.com` | 192.168.50.11:8006 | proxmox-headers, local-only | | | `truenas.local.tophermayor.com` | 192.168.50.12:8080 | local-only | | | `opencode-ice.local.tophermayor.com` | 192.168.50.197:4096 | local-only | | | `whisper.local.tophermayor.com` | faster-whisper-server:8394 | local-only | | | `traefik.local.tophermayor.com` | api@internal | local-only | Dashboard | ### Internal Widget Routes (sonarr-internal, etc.) These are `*-internal.local.tophermayor.com` routes for Homepage widgets, accessible only inside the network via the gluetun VPN tunnel. From `homepage-widgets.yml`: | Internal Domain | Backend (via gluetun) | |-----------------|----------------------| | `sonarr-internal.local.tophermayor.com` | gluetun:8989 | | `radarr-internal.local.tophermayor.com` | gluetun:7878 | | `lidarr-internal.local.tophermayor.com` | gluetun:8686 | | `sabnzbd-internal.local.tophermayor.com` | gluetun:8080 | | `seerr-internal.local.tophermayor.com` | seerr:5055 | | `jellyfin-internal.local.tophermayor.com` | jellyfin:8096 | | `prometheus-internal.local.tophermayor.com` | prometheus:9090 | ### Special Protocols | Protocol | Port | Host | Purpose | |----------|------|------|---------| | HTTP→HTTPS | 80 | grizzley | Redirects to 443 | | HTTPS | 443 | grizzley | All TLS traffic | | QUIC/HTTP3 | 443/udp | grizzley | HTTP3 support | | Traefik metrics | 8080 | grizzley | Prometheus scraping | | Gitea SSH proxy | 2222 | grizzley | → ubuntu:2222 | | Minecraft Bedrock | 19132/udp | grizzley | Bedrock server (standby) | | Minecraft Bedrock | 19134/udp | grizzley | Bedrock server (sison) | ## Middleware Chains (ubuntu) ### homelab-public Applied to: gitea, immich, audiobookshelf, navidrome, kavita, jellyseerr, etc. ``` chain: [compress, security-headers, buffering, ratelimit] ``` ### Security Headers Applied to most services: ```yaml browserXssFilter: true contentTypeNosniff: true forceSTSHeader: true stsIncludeSubdomains: true stsPreload: true stsSeconds: 31536000 # 1 year customFrameOptionsValue: SAMEORIGIN ``` ### Jellyfin-specific Headers Adds CSP allowing jsDelivr CDN for the Ultrachromic theme: ```yaml contentSecurityPolicy: "style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; ..." ``` ### Authentik ForwardAuth (SSO) Applied to: sonarr, radarr, lidarr, prowlarr, bazarr, sabnzbd, transmission, qbittorrent, flaresolverr, jellyseerr, listsync, dockge, it-tools, bentopdf, code-ai, and more. Each service has its own middleware with `X-authentik-host` query param: ``` http://authentik-server:9000/outpost.goauthentik.io/auth/traefik?X-authentik-host= ``` ### local-only IP Allowlist ```yaml sourceRange: - 127.0.0.1/32 - 192.168.50.0/24 # Production - 192.168.1.0/24 # Management - 192.168.3.0/24 # Trusted - 192.168.4.0/24 # WireGuard VPN - 172.16.0.0/12 # Docker - 10.0.0.0/8 # VPN/Docker ``` ### Rate Limiting ```yaml average: 100 burst: 50 ``` ## VPN Tunnel (gluetun) Media automation services route through **gluetun** VPN container for privacy when connecting to torrent/indexer services: - sonarr → gluetun:8989 - radarr → gluetun:7878 - lidarr → gluetun:8686 - sabnzbd → gluetun:8080 gluetun ports: 8000, 8388, 8888 (TCP), 8388 (UDP) — exposed on ubuntu's Docker network. ## SSH Routing Gitea SSH is proxied through grizzley: ``` Internet → grizzley:2222 (SNI * → any) → forwards to ubuntu:2222 → gitea container handles git SSH protocol ``` ## UniFi Controller Network services (DHCP, DNS, VLAN tagging) managed by UniFi Controller at 192.168.1.1 (or similar). All internal DNS for `*.local.tophermayor.com` resolves through the UniFi DNS forwarder. ## Related - [[traefik]] — Traefik entity page - [[grizzley]] — RPi5 edge node (ACME master, backup ingress) - [[ubuntu]] — Primary Docker host (primary ingress router) - [[truenas]] — NFS storage for cert sync - [[traefik-ha]] — HA concept page - [[homepage]] — Dashboard services with widget routes - [[authentik]] — SSO identity provider - [[sso-authentik]] — SSO configuration details