Architecture Diagram¶
Phase 1 — ExzenTCG Homelab
Network Topology¶
flowchart TB
subgraph Internet
USER["User Browser"]
CF["Cloudflare Edge<br/>TLS termination"]
CFZT["Cloudflare Zero Trust<br/>Access Policy"]
WEBHOOK["External Webhooks<br/>(Stripe, Telegram, etc.)"]
end
subgraph Proxmox["Proxmox VE (192.168.0.200)"]
subgraph CT101["CT 101 — edge-gateway (192.168.0.51)"]
CFLRD["cloudflared<br/>:7844 → CF edge"]
NPM["Nginx Proxy Manager<br/>:80 (localhost only)<br/>:81 (admin, LAN)"]
end
subgraph CT102["CT 102 — n8n-app (192.168.0.52)"]
N8N["n8n v1.80.0<br/>:5678 (LAN only)"]
end
PVFW["Proxmox Firewall<br/>Per-CT rules + IP sets"]
end
subgraph Admin["Admin Access"]
DESKTOP["Admin Desktop<br/>192.168.0.16"]
LAPTOP["Admin Laptop<br/>via Tailscale"]
TS["Tailscale Mesh<br/>100.89.70.63"]
end
ROUTER["Home Router<br/>192.168.0.1<br/>DNS / DHCP / NAT"]
%% Traffic flow
USER -->|"HTTPS :443"| CF
WEBHOOK -->|"HTTPS :443"| CF
CF -->|"Access check"| CFZT
CFZT -->|"Tunnel (TCP :7844)"| CFLRD
CFLRD -->|"HTTP localhost:80"| NPM
NPM -->|"HTTP :5678"| N8N
%% Admin access
DESKTOP -->|":81 NPM admin"| NPM
DESKTOP -->|":22 SSH"| N8N
DESKTOP -->|":8006 WebUI"| Proxmox
LAPTOP -->|"WireGuard"| TS
TS -->|":8006 WebUI"| Proxmox
%% DNS
CT101 -->|"UDP :53"| ROUTER
CT102 -->|"UDP :53"| ROUTER
%% Firewall
PVFW -.->|"enforces"| CT101
PVFW -.->|"enforces"| CT102
style CF fill:#f38020,color:#fff
style CFZT fill:#f38020,color:#fff
style CT101 fill:#1a3a4a,color:#fff
style CT102 fill:#1a3a4a,color:#fff
style N8N fill:#ea4b71,color:#fff
style TS fill:#4c8bf5,color:#fff
Traffic Flow (request lifecycle)¶
1. User visits https://n8n.exzentcg.com
2. DNS resolves → CNAME → cfargotunnel.com → Cloudflare edge IP
3. Cloudflare terminates TLS
4. Cloudflare Zero Trust checks Access policy
→ /webhook/* paths: bypass (no auth)
→ all other paths: require email login (Google OAuth or one-time PIN)
5. Authenticated request enters Cloudflare Tunnel
6. cloudflared (CT 101, network_mode: host) receives on TCP 7844
7. cloudflared forwards to http://localhost:80 (NPM)
8. NPM matches Host header "n8n.exzentcg.com"
9. NPM proxies to http://192.168.0.52:5678 (n8n-app)
10. n8n processes request and responds
11. Response travels back through the same chain
Firewall Boundaries¶
┌─────────────────────────────┐
│ INTERNET (any) │
└─────────┬───────────────────-┘
│
┌─────────▼───────────────────-┐
│ Cloudflare Edge + Tunnel │
│ (TLS + Access + WAF) │
└─────────┬───────────────────-┘
│ TCP 7844
┌───────────────▼───────────────────-┐
│ CT 101 — edge-gateway │
│ │
│ IN: localhost:80,443 only │
│ +admin_desktop:81 │
│ everything else: DROP │
│ │
│ OUT: router:53 (DNS) │
│ 192.168.0.52:5678 (n8n) │
│ DROP +lan_subnet ◄── key! │
│ 7844 (tunnel) │
│ 443, 80 (APIs) │
└───────────────┬───────────────────-┘
│ TCP 5678
┌───────────────▼───────────────────-┐
│ CT 102 — n8n-app │
│ │
│ IN: +edge_gw:5678 only │
│ +admin_desktop:22 (SSH) │
│ everything else: DROP │
│ │
│ OUT: router:53 (DNS) │
│ DROP +lan_subnet ◄── key! │
│ 443 only (HTTPS APIs) │
└───────────────────────────────────-┘
Important
The DROP +lan_subnet rule before ACCEPT :443 is the critical lateral movement prevention. It ensures that even if a container is compromised, it cannot reach other LAN devices (desktop, router admin, other containers) — even on port 443.
IP Address Map¶
| IP | Role | Access |
|---|---|---|
| 192.168.0.1 | Home router | DNS/DHCP/NAT |
| 192.168.0.16 | Admin desktop | In admin_desktop IP set |
| 192.168.0.51 | edge-gateway (CT 101) | NPM + cloudflared |
| 192.168.0.52 | n8n-app (CT 102) | n8n only |
| 192.168.0.200 | Proxmox host | Hypervisor |
| 100.89.70.63 | Proxmox via Tailscale | Remote admin |