Phase 1 Actions Log¶
Date Range: 2026-04-09 → 2026-04-10 Status: ✅ Complete
Step 0 — Secure Proxmox WebUI Access¶
0.1 — Enable the Node-level Firewall¶
Already enabled.

0.5.1 — Install Tailscale on the Proxmox Host¶
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up
tailscale ip -4
# → 100.89.70.63
0.5.2 — Install Tailscale on the Admin Laptop¶
Laptop Tailscale IP: 100.97.244.32
0.5.3 — Verify the Mesh¶
ssh root@100.89.70.63
# ✅ Connection successful
0.2 — Create Node Firewall Rules (Datacenter > pve > Firewall)¶

0.3 — Test Before Proceeding¶
Enabled: Datacenter > Firewall > Options > Firewall → On
# From 192.168.0.16:
curl -k https://192.168.0.200:8006 # ✅ Success
ssh root@192.168.0.200 # ✅ Success
# From phone:
# ❌ Not working (expected — firewall working correctly)
Step 1 — Create Datacenter IP Sets¶
Created all IP sets under Datacenter > Firewall > IP Sets:

| IP Set | Screenshot |
|---|---|
| admin_desktop | ![]() |
| lan_subnet | ![]() |
| router_gw | ![]() |
| edge_gw | ![]() |
Step 2 — Enable Datacenter Firewall¶
Already done in Step 0.3.
Step 3 — Create LXC: edge-gateway (192.168.0.51)¶
3.1 — Download CT Template¶
Downloaded debian-12-standard (latest).

3.2 — Create the Container¶

Checked Start after created, then clicked Finish.

3.3 — Verify Networking¶
Proxmox console into CT101, logged in as root.
ping -c 3 1.1.1.1
# ✅ 3/3 packets received, 0% loss (~3.8ms avg)
ping -c 3 google.com
# ✅ 3/3 packets received, 0% loss (~4.0ms avg)
apt update && apt upgrade -y
# ✅ 21 packages upgraded
3.4 — Install NPM + cloudflared (single Docker Compose stack)¶
apt install curl
curl -fsSL https://get.docker.com | sh
# ✅ Docker 29.4.0 installed
mkdir -p /opt/edge-gateway && cd /opt/edge-gateway
Created /opt/edge-gateway/docker-compose.yml:
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "127.0.0.1:80:80"
- "127.0.0.1:443:443"
- "192.168.0.51:81:81"
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate --edge-ip-version 4 --protocol http2 run --token ${TUNNEL_TOKEN}
depends_on:
- npm
network_mode: host
docker compose up -d npm
# ✅ NPM started
Step 4 — Create LXC: n8n-app (192.168.0.52)¶
4.1 — Create the Container¶

4.2 — Install Docker¶
Same process as edge-gateway. Docker 29.4.0 installed.
4.3 — Generate Encryption Key¶
openssl rand -hex 32
# → 44c93188a2458ea<REDACTED>19e699607dcad28e7bf
Warning
Critical: Back up this encryption key. Losing it means losing access to all stored credentials in n8n.
4.4 — Deploy n8n¶
Created /opt/n8n/docker-compose.yml and .env with hardened config. n8n bound to 192.168.0.52:5678 only.
docker compose up -d
# ✅ n8n running
Step 5 — Configure Proxmox Container Firewalls¶
5.1 — edge-gateway Firewall (CT 101)¶
Enabled firewall: Options → Firewall → Yes
/etc/pve/firewall/101.fw (final version after cloudflared fixes):
[OPTIONS]
enable: 1
[RULES]
IN ACCEPT -source 127.0.0.1 -p tcp -dport 80,443 # cloudflared local to NPM
IN ACCEPT -source +admin_desktop -p tcp -dport 81 # NPM admin panel
IN DROP # Block all other inbound
OUT ACCEPT -dest +router_gw -p udp -dport 53 # DNS resolution UDP
OUT ACCEPT -dest +router_gw -p tcp -dport 53 # DNS resolution TCP
OUT ACCEPT -dest 192.168.0.52 -p tcp -dport 5678 # Proxy to n8n-app
OUT ACCEPT -dest 192.168.0.53 -p tcp # Proxy to biz-app (Phase 2)
OUT DROP -dest +lan_subnet # Block all other LAN
OUT ACCEPT -p udp -dport 7844 # cloudflared QUIC to Cloudflare edge
OUT ACCEPT -p tcp -dport 7844 # cloudflared HTTP/2 fallback
OUT ACCEPT -p tcp -dport 443 # APIs / Let's Encrypt
OUT ACCEPT -p tcp -dport 80 # HTTP fallback
OUT ACCEPT -p udp -dport 53 # Public DNS fallback (1.1.1.1)
OUT ACCEPT -p tcp -dport 53 # Public DNS fallback
Note
UDP/TCP 7844 added after cloudflared failed — it uses port 7844 (not 443) for tunnel connections. Public DNS fallback rules added after router rate-limited DNS during cloudflared crash-loop. /etc/resolv.conf changed to 1.1.1.1 and locked with chattr +i.
Firewall test results:
| Test | Expected | Result |
|---|---|---|
| DNS via router | ✅ | ✅ |
| External HTTPS (1.1.1.1) | ✅ | ✅ |
| apt update | ✅ | ✅ |
| Reach LAN 192.168.0.16 | ❌ HTTP 000 | ✅ |
| Reach Proxmox :8006 | ❌ HTTP 000 | ✅ |
| Reach desktop on 443 (DNS rebind test) | ❌ HTTP 000 | ✅ |
| Reach n8n-app :5678 | ✅ HTTP 200 | ✅ |
5.2 — n8n-app Firewall (CT 102)¶
/etc/pve/firewall/102.fw:
[OPTIONS]
enable: 1
[RULES]
IN ACCEPT -source +edge_gw -p tcp -dport 5678 # Only edge-gateway may reach n8n
IN ACCEPT -source +admin_desktop -p tcp -dport 22 # SSH from admin desktop
IN DROP # Block all other inbound
OUT ACCEPT -dest +router_gw -p udp -dport 53 # DNS resolution UDP
OUT ACCEPT -dest +router_gw -p tcp -dport 53 # DNS resolution TCP
OUT DROP -dest +lan_subnet # Lateral movement prevention
OUT ACCEPT -p tcp -dport 443 # n8n external API calls (HTTPS only)
Note
No port 80 outbound intentionally. n8n calls HTTPS-only APIs. Add targeted rules per-integration if HTTP is ever needed.
Firewall test results:
| Test | Expected | Result |
|---|---|---|
| External HTTPS (api.github.com) | ✅ HTTP 200 | ✅ |
| DNS via router | ✅ | ✅ |
| Reach desktop 192.168.0.16 | ❌ HTTP 000 | ✅ |
| Reach desktop on 443 | ❌ HTTP 000 | ✅ |
| Reach Proxmox :8006 | ❌ HTTP 000 | ✅ |
| Reach edge-gateway 192.168.0.51 | ❌ HTTP 000 | ✅ |
| HTTP outbound port 80 | ❌ HTTP 000 | ✅ |
Step 6 — Cloudflare DNS Migration¶
Moved exzentcg.com from Hostinger to Cloudflare nameservers.
Note
During migration, telegram.exzentcg.com was briefly unreachable for some users because the Cloudflare zone was missing records during the NS transition window.
Step 7 — Create Cloudflare Tunnel¶
7.1 — Create Tunnel¶
Zero Trust → Networks → Tunnels → Create a tunnel
- Name: exzentcg-homelab
- Connector: cloudflared
- Tunnel ID:
72dbcbd0-d7d9-41d4-b55e-87e5eb9b27ad
7.2 — Add Token to .env¶
nano /opt/edge-gateway/.env
# TUNNEL_TOKEN=eyJhIjoi...<token>
7.3 — Start cloudflared¶
Initial issue: cloudflared crashed with "there are no free edge addresses left to resolve to"
Root causes: 1. UDP/TCP 7844 not in LXC firewall — cloudflared uses 7844, not 443 2. Router DNS rate-limiting after crash-loop flooded it
Fix applied:
- Added 7844 rules and public DNS fallback to 101.fw (see Step 5.1 final version)
- Changed /etc/resolv.conf to 1.1.1.1, locked with chattr +i
- Added --edge-ip-version 4 --protocol http2 flags to compose
docker compose up -d cloudflared
# ✅ Tunnel HEALTHY — connector registered in sin datacenter
7.4 — Configure Public Hostname¶
Zero Trust → Networks → Tunnels → exzentcg-homelab → Published application routes → Add:
| Field | Value |
|---|---|
| Subdomain | n8n |
| Domain | exzentcg.com |
| Path | (empty) |
| Service Type | HTTP |
| URL | localhost:80 |
| HTTP Host Header | n8n.exzentcg.com |
DNS CNAME auto-created: n8n.exzentcg.com → 72dbcbd0-...cfargotunnel.com (proxied)
Step 8 — Configure NPM Proxy Host¶
NPM admin panel: http://192.168.0.51:81
Hosts → Proxy Hosts → Add Proxy Host:
| Field | Value |
|---|---|
| Domain Names | n8n.exzentcg.com |
| Scheme | http |
| Forward Hostname / IP | 192.168.0.52 |
| Forward Port | 5678 |
| Block Common Exploits | ✅ |
| Websockets Support | ✅ |
| SSL Certificate | None (Cloudflare handles TLS) |
![]() |
|
![]() |
✅ n8n setup wizard loaded over HTTPS — full chain working.
Step 9 — Cloudflare Zero Trust Access Policy¶
9.1 — Create Access Application (n8n UI)¶
Zero Trust → Access → Applications → Add → Self-hosted:
| Field | Value |
|---|---|
| Application name | n8n |
| Session Duration | 24 hours |
| Subdomain | n8n |
| Domain | exzentcg.com |
| Path | (empty) |

Policy: owner-only
- Action: Allow
- Selector: Emails → exzensg@gmail.com

9.2 — Set up Google OAuth¶
Created Google Cloud project → APIs & Services → Credentials → OAuth 2.0 Client ID
Authorized redirect URI: https://exzen.cloudflareaccess.com/cdn-cgi/access/callback
Client ID: 3<REDACTED>b.apps.googleusercontent.com
Client Secret: GOCSPX-<REDACTED>-NhI
Added to: Zero Trust → Settings → Authentication → Google
9.3 — Webhook Bypass Application¶
Created second Access application for webhook endpoints:
| Application | Hostname | Path | Policy |
|---|---|---|---|
| n8n | n8n.exzentcg.com | / | owner-only (Allow) |
| n8n-webhooks | n8n.exzentcg.com | /webhook/ | Webhook Bypass (Bypass + Everyone) |

9.4 — Test Access Policy¶
| Test | Result |
|---|---|
| Visit https://n8n.exzentcg.com → Cloudflare login page | ✅ |
| One-time PIN via email | ✅ Access granted |
| Google OAuth (exzensg@gmail.com) | ✅ Access granted |
| Random/unauthorised email | ❌ Blocked correctly |
Step 10 — n8n Owner Account Setup¶
Visited https://n8n.exzentcg.com → completed the setup wizard.
- Owner email:
exzensg@gmail.com
Step 11 — Snapshot & Boot Config¶
# From Proxmox host
pct stop 101
pct stop 102
pct snapshot 101 phase1-complete --description "Phase 1 complete — NPM + cloudflared"
pct snapshot 102 phase1-complete --description "Phase 1 complete — n8n 1.80.0"
# Turn on 'Start at boot' for both containers
pct start 101
pct start 102
Phase 1 Complete ✅¶
The following infrastructure is live and hardened:
| Component | Host | Details |
|---|---|---|
| Proxmox host | 192.168.0.200 | Tailscale + node/datacenter firewall enabled |
| edge-gateway (CT 101) | 192.168.0.51 | Nginx Proxy Manager + cloudflared, container firewall locked |
| n8n-app (CT 102) | 192.168.0.52 | n8n bound to LAN only, container firewall locked |
| Cloudflare Tunnel | exzentcg-homelab | HEALTHY, registered in sin datacenter |
| Public endpoint | https://n8n.exzentcg.com | Zero Trust gated, Google OAuth enabled |
| Webhook endpoint | https://n8n.exzentcg.com/webhook/ | Bypass policy for external service callbacks |
| DNS | exzentcg.com | Migrated to Cloudflare, all records confirmed |





