Skip to content

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.

step0-node-firewall-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)

step0-node-firewall-rules


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:

step1-ipsets-overview

IP Set Screenshot
admin_desktop step1-ipset-admin-desktop
lan_subnet step1-ipset-lan-subnet
router_gw step1-ipset-router-gw
edge_gw step1-ipset-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).

step3-ct-template-download

3.2 — Create the Container

step3-ct101-create-general step3-ct101-create-template step3-ct101-create-rootfs step3-ct101-create-cpu step3-ct101-create-memory step3-ct101-create-network step3-ct101-create-dns

Checked Start after created, then clicked Finish.

step3-ct101-create-confirm

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

step4-ct102-create-general step4-ct102-create-confirm

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.com72dbcbd0-...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)
step8-npm-proxy-host-edit
step8-n8n-https-working

✅ 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)

step9-access-app-config

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

step9-access-policy-owner-only

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)

step9-webhook-bypass-policy

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