exzentcg.com Homelab — Full Implementation Guide¶
Covers: Phase 1 end-to-end — from bare Proxmox host to n8n.exzentcg.com live on the internet.
Prerequisites:
- Proxmox VE installed and accessible at 192.168.0.200:8006
- exzentcg.com domain registered and DNS managed by Cloudflare
- A Cloudflare account (free tier is sufficient)
- Admin desktop at 192.168.0.16 with browser and SSH client
Step 0 — Proxmox Firewall Lockout Prevention¶
⚠️ Do this FIRST. If you enable the datacenter firewall without these rules, you will be locked out and must fix it from the physical console.
0.1 — Enable the Node-level Firewall¶
In the Proxmox WebUI (https://192.168.0.200:8006):
- Go to Datacenter → pve → Firewall → Options
- Set Firewall to
Yes(node level) - Do NOT enable the Datacenter-level firewall yet
0.2 — Create Node Firewall Rules¶
Go to Datacenter → pve → Firewall → Add
Create these rules in this exact order:
| # | Direction | Action | Source | Dest | Port | Protocol | Comment |
|---|---|---|---|---|---|---|---|
| 1 | IN | ACCEPT | 192.168.0.16 | — | 8006 | TCP | Admin WebUI from LAN desktop |
| 2 | IN | ACCEPT | 192.168.0.16 | — | 22 | TCP | Admin SSH from LAN desktop |
| 3 | IN | DROP | — | — | 8006 | TCP | Block all others from WebUI |
Tailscale note: These rules use a hardcoded LAN IP because the
admin_desktopIP set does not exist yet. After completing Step 0.5 (Tailscale install) and Step 1 (IP set creation), come back here and replace the hardcoded192.168.0.16source on rules 1 and 2 with+admin_desktop. That single change extends admin access to both the LAN desktop and the Tailscale laptop without duplicate rules.
0.3 — Test Before Proceeding¶
From your desktop (192.168.0.16), confirm:
# Should succeed
curl -k https://192.168.0.200:8006
# Should succeed
ssh root@192.168.0.200
From any OTHER device on your network (e.g. phone on WiFi), confirm:
# Should timeout / fail
https://192.168.0.200:8006 → should NOT load
If the above checks pass, proceed. If not, fix the rules before continuing.
Step 0.5 — Install Tailscale for Remote Admin¶
Per the architecture decision, Tailscale is the remote admin mechanism. It is deliberately independent of Cloudflare so an outage of one service does not lock the operator out of the hypervisor. Installing Tailscale before locking down the datacenter firewall means the admin laptop's Tailscale IP is known and can be added to the
admin_desktopIP set in Step 1.
0.5.1 — Install Tailscale on the Proxmox Host¶
From the Proxmox host (SSH from desktop or use the node shell in the WebUI):
curl -fsSL https://tailscale.com/install.sh | sh
tailscale up
Follow the auth URL printed in the terminal and log in with your Tailscale account (create one if needed — free tier is sufficient).
After auth, get the host's Tailscale IP:
tailscale ip -4
# Example output: 100.64.12.34
Record this — it is the Proxmox host on the mesh.
0.5.2 — Install Tailscale on the Admin Laptop¶
Install Tailscale on the laptop used for remote admin (download from tailscale.com/download, or use the relevant package manager). Log in with the same Tailscale account.
From the laptop, get its Tailscale IP:
tailscale ip -4
# Example output: 100.64.56.78
Record this IP — it will be added to the admin_desktop IP set in Step 1.
0.5.3 — Verify the Mesh¶
From the laptop (while off your home LAN, or with LAN disabled to confirm it's going over Tailscale):
ping <proxmox_tailscale_ip>
ssh root@<proxmox_tailscale_ip>
Both should succeed. If so, remote access works; proceed.
Note on ACLs: The default Tailscale ACL allows all devices in your tailnet to reach each other. If you later add non-admin devices to the tailnet, tighten the ACL in the Tailscale admin console so only the admin laptop can reach the Proxmox host.
Step 1 — Create Datacenter IP Sets¶
Go to Datacenter → Firewall → IPSet
Create these IP sets:
| IP Set Name | Members | Purpose |
|---|---|---|
admin_desktop |
192.168.0.16, <laptop_tailscale_ip_from_0.5.2> |
Trusted admin machines (LAN + Tailscale) |
lan_subnet |
192.168.0.0/24 | Entire home LAN |
router_gw |
192.168.0.1 | Default gateway / DNS |
edge_gw |
192.168.0.51 | edge-gateway LXC |
After creating
admin_desktop: Return to the Step 0.2 node firewall rules and replace the hardcoded192.168.0.16source on rules 1 and 2 with+admin_desktop. This extends node admin access over Tailscale.
These will be referenced in all container firewall rules.
Step 2 — Enable Datacenter Firewall¶
Go to Datacenter → Firewall → Options
- Set Firewall to
Yes - Set Input Policy to
DROP - Set Output Policy to
ACCEPT
Verify you can still access the WebUI from your desktop. If locked out, access the physical console and run:
# Emergency disable from Proxmox physical console
pve-firewall stop
Step 3 — Create LXC: edge-gateway (192.168.0.51)¶
3.1 — Download CT Template¶
In the Proxmox WebUI:
- Go to local (pve) → CT Templates → Templates
- Download debian-12-standard (latest)
3.2 — Create the Container¶
Go to Create CT (top right button):
| Setting | Value |
|---|---|
| CT ID | 101 (or your preference) |
| Hostname | edge-gateway |
| Password | (set a strong root password) |
| Template | debian-12-standard |
| Disk | 4 GB |
| CPU | 1 core |
| Memory | 1024 MB |
| Network | Bridge: vmbr0, IPv4: 192.168.0.51/24, Gateway: 192.168.0.1 |
| DNS | Domain: exzentcg.com, Server: 192.168.0.1 |
Check Start after created, then click Finish.
3.3 — Verify Networking¶
SSH into the container:
ssh root@192.168.0.51
Test connectivity:
ping -c 3 1.1.1.1
ping -c 3 google.com
apt update
If DNS fails, check /etc/resolv.conf:
cat /etc/resolv.conf
# Should contain: nameserver 192.168.0.1
# If missing:
echo "nameserver 192.168.0.1" > /etc/resolv.conf
3.4 — Install NPM + cloudflared (single Docker Compose stack)¶
Both services live in one LXC (edge-gateway) and one compose file. This keeps the install paradigm consistent (all apps = docker-compose in LXCs), backups simple (one directory), and lets NPM bind to localhost only — a second layer of defence behind the Proxmox firewall.
# Install Docker
curl -fsSL https://get.docker.com | sh
# Create edge-gateway directory
mkdir -p /opt/edge-gateway && cd /opt/edge-gateway
Create the compose file:
cat > docker-compose.yml << 'EOF'
services:
npm:
image: jc21/nginx-proxy-manager:latest
container_name: npm
restart: unless-stopped
ports:
- "127.0.0.1:80:80" # localhost only — cloudflared reaches this
- "127.0.0.1:443:443" # localhost only
- "192.168.0.51:81:81" # admin panel on LAN IP only
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
depends_on:
- npm
network_mode: host # so localhost:80 resolves to NPM's bind
EOF
Create an empty .env — the tunnel token will be filled in at Step 7.3 after the tunnel is created in the Cloudflare dashboard:
cat > .env << 'EOF'
TUNNEL_TOKEN=
EOF
chmod 600 .env
Start NPM only for now (cloudflared will fail to start without a valid token — that's expected):
docker compose up -d npm
Wait ~30 seconds, then access the NPM admin panel from your desktop:
http://192.168.0.51:81
Default credentials:
- Email: admin@example.com
- Password: changeme
Immediately change the admin email and password on first login.
Why
network_mode: hostfor cloudflared? Without it, cloudflared runs in its own Docker network andlocalhost:80would mean the cloudflared container's own loopback, not NPM. Host networking makes cloudflared share the LXC's network namespace, solocalhost:80correctly reaches NPM's127.0.0.1:80bind.Backup target:
/opt/edge-gateway/now contains everything — compose file, NPM data, certs, tunnel token.tar czf edge-gateway-backup.tgz /opt/edge-gateway/grabs the lot.
Step 4 — Create LXC: n8n-app (192.168.0.52)¶
4.1 — Create the Container¶
Go to Create CT:
| Setting | Value |
|---|---|
| CT ID | 102 (or your preference) |
| Hostname | n8n-app |
| Password | (set a strong root password) |
| Template | debian-12-standard |
| Disk | 10 GB |
| CPU | 2 cores |
| Memory | 2048 MB |
| Network | Bridge: vmbr0, IPv4: 192.168.0.52/24, Gateway: 192.168.0.1 |
| DNS | Domain: exzentcg.com, Server: 192.168.0.1 |
Start the container.
4.2 — Verify Networking¶
ssh root@192.168.0.52
ping -c 3 google.com
apt update
4.3 — Generate n8n Encryption Key (do this FIRST)¶
⚠️ Do this before starting n8n for the first time. n8n encrypts every credential it stores (API keys, OAuth tokens, DB passwords) with this key. If n8n generates its own random key on first start and the volume is later lost or corrupted, every stored credential becomes permanently unrecoverable. Generating the key yourself means you can back it up separately.
On the n8n-app container:
openssl rand -hex 32
Copy the output. Save it immediately to your password manager under an entry like n8n encryption key (exzentcg homelab). Treat it like a root password — losing it is equivalent to losing every credential n8n will ever hold.
4.4 — Install n8n via Docker Compose¶
# Install Docker
curl -fsSL https://get.docker.com | sh
# Create n8n directory
mkdir -p /opt/n8n && cd /opt/n8n
Create the compose file (pin to a specific version — replace 1.80.0 with the current stable at install time, see https://github.com/n8n-io/n8n/releases):
cat > docker-compose.yml << 'EOF'
services:
n8n:
image: n8nio/n8n:1.80.0
container_name: n8n
restart: unless-stopped
ports:
- "192.168.0.52:5678:5678" # bind to LXC IP only, not 0.0.0.0
environment:
- N8N_HOST=n8n.exzentcg.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.exzentcg.com
- NODES_EXCLUDE=["n8n-nodes-base.executeCommand"]
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- GENERIC_TIMEZONE=Asia/Singapore
- TZ=Asia/Singapore
volumes:
- ./data:/home/node/.n8n
EOF
Create the .env file with the encryption key you generated in Step 4.3:
cat > .env << 'EOF'
N8N_ENCRYPTION_KEY=<paste the openssl output from Step 4.3 here>
EOF
chmod 600 .env
Start n8n:
docker compose up -d
Production hardening notes: - Image pinned to a specific version. Weekly maintenance will upgrade this deliberately instead of
:latestauto-breaking you. - Port bound to192.168.0.52(LXC IP), not0.0.0.0. Belt-and-suspenders with the Proxmox firewall. - Timezone set so scheduled workflows fire at the right local clock time. AdjustAsia/Singaporeto your zone. -.envis chmod 600 and should be included in any backup of/opt/n8n/— but also backed up separately in a password manager because this key is the master secret for all credentials.
4.5 — Verify n8n is Running¶
# From inside the n8n-app container (note: bind is on 192.168.0.52, not localhost)
curl -s http://192.168.0.52:5678/healthz
# Should return OK or a JSON response
# From the edge-gateway container
ssh root@192.168.0.51
curl -s http://192.168.0.52:5678
# Should return the n8n UI HTML
Step 5 — Configure Proxmox Container Firewalls¶
5.1 — edge-gateway Firewall Rules¶
Go to Datacenter → 101 (edge-gateway) → Firewall
First, enable the firewall: Options → Firewall → Yes
Then add rules in this exact order:
| # | Direction | Action | Source/Dest | Port | Protocol | Comment |
|---|---|---|---|---|---|---|
| 1 | IN | ACCEPT | 127.0.0.1 | 80,443 | TCP | cloudflared local to NPM |
| 2 | IN | ACCEPT | +admin_desktop | 81 | TCP | NPM admin panel |
| 3 | IN | DROP | — | — | — | Block all other inbound |
| 4 | OUT | ACCEPT | +router_gw | 53 | UDP | DNS resolution |
| 5 | OUT | ACCEPT | +router_gw | 53 | TCP | DNS resolution TCP |
| 6 | OUT | ACCEPT | 192.168.0.52 | 5678 | TCP | Proxy to n8n-app |
| 7 | OUT | ACCEPT | 192.168.0.53 | — | TCP | Proxy to biz-app Phase 2 |
| 8 | OUT | DROP | +lan_subnet | — | — | Block all other LAN |
| 9 | OUT | ACCEPT | — | 443 | TCP | cloudflared tunnel + APIs |
| 10 | OUT | ACCEPT | — | 80 | TCP | HTTP fallback |
Rule order matters. Rule 8 (DROP lan_subnet) MUST be above rules 9-10 (ALLOW any). This ensures traffic to LAN IPs on port 443 is dropped before the wildcard allows it.
5.2 — n8n-app Firewall Rules¶
Go to Datacenter → 102 (n8n-app) → Firewall
Enable the firewall: Options → Firewall → Yes
Add rules in this exact order:
| # | Direction | Action | Source/Dest | Port | Protocol | Comment |
|---|---|---|---|---|---|---|
| 1 | IN | ACCEPT | +edge_gw | 5678 | TCP | Only proxy may reach n8n |
| 2 | IN | ACCEPT | +admin_desktop | 22 | TCP | SSH from admin desktop |
| 3 | IN | DROP | — | — | — | Block all other inbound |
| 4 | OUT | ACCEPT | +router_gw | 53 | UDP | DNS resolution |
| 5 | OUT | ACCEPT | +router_gw | 53 | TCP | DNS resolution TCP |
| 6 | OUT | DROP | +lan_subnet | — | — | Lateral movement prevention |
| 7 | OUT | ACCEPT | — | 443 | TCP | n8n external API calls |
Critical: Rule 6 (DROP lan_subnet) MUST be above rule 7 (ALLOW any:443). This is the rule that prevents a compromised n8n from reaching your desktop, Proxmox host, or any LAN device — including via DNS rebinding attacks.
5.3 — Test Firewall Rules¶
From the n8n-app container:
# Should SUCCEED — external API access
curl -s https://httpbin.org/get | head -5
# Should FAIL / timeout — lateral movement blocked
curl -s --connect-timeout 3 http://192.168.0.16
# Expected: connection timeout
curl -s --connect-timeout 3 https://192.168.0.200:8006
# Expected: connection timeout
curl -s --connect-timeout 3 http://192.168.0.1
# Expected: connection timeout (except DNS on port 53)
From the edge-gateway container:
# Should SUCCEED — can reach n8n
curl -s http://192.168.0.52:5678 | head -5
# Should FAIL — cannot reach desktop
curl -s --connect-timeout 3 http://192.168.0.16
# Expected: connection timeout
If any test returns unexpected results, review rule ordering in the Proxmox Firewall UI.
Step 6 — Cloudflare DNS Configuration¶
6.1 — Add DNS Records¶
In the Cloudflare dashboard for exzentcg.com:
Go to DNS → Records → Add Record
| Type | Name | Content | Proxy |
|---|---|---|---|
| CNAME | n8n | (will be set by tunnel) | Proxied (orange cloud) |
Note: When you create the tunnel in Step 7, Cloudflare will automatically create or update the DNS record. You can also set this manually to point to the tunnel's generated CNAME.
Step 7 — Create Cloudflare Tunnel¶
7.1 — Create Tunnel in Dashboard¶
- Go to Cloudflare → Zero Trust → Networks → Tunnels
- Click Create a tunnel
- Select Cloudflared as the connector
- Name:
exzentcg-homelab - Click Save tunnel
7.2 — Copy the Tunnel Token¶
Cloudflare will show an install command containing a token. Copy the token (the long string after --token).
It will look like:
cloudflared service install eyJhIjoiNjI...very_long_string
Copy everything after --token.
7.3 — Start cloudflared on edge-gateway¶
cloudflared runs as a Docker Compose service alongside NPM (see Step 3.4). You only need to fill in the token and bring the service up.
SSH into edge-gateway:
ssh root@192.168.0.51
cd /opt/edge-gateway
Fill in the token in the .env file you created earlier:
# Replace the empty TUNNEL_TOKEN= line with your real token
nano .env
The file should now look like:
TUNNEL_TOKEN=eyJhIjoiNjI...<your_long_token>
Save and exit. Then start cloudflared:
docker compose up -d cloudflared
Verify:
# Check the cloudflared container is running
docker compose ps cloudflared
# Should show "Up" / "running"
# Tail logs to confirm the tunnel registered successfully
docker compose logs --tail 30 cloudflared
# Look for: "Registered tunnel connection" and 2-4 connection lines to Cloudflare edge
# Then check the Cloudflare dashboard — tunnel should show HEALTHY
If cloudflared fails to start: the most common cause is a malformed
.env(extra quotes, stray whitespace, line breaks in the token). Make sureTUNNEL_TOKEN=is followed by the raw token with no quotes and no spaces.
7.4 — Configure Tunnel Ingress Rules¶
Back in the Cloudflare Dashboard → Zero Trust → Tunnels → exzentcg-homelab → Configure:
Go to the Public Hostname tab and add:
| Subdomain | Domain | Path | Service |
|---|---|---|---|
| n8n | exzentcg.com | (empty) | http://localhost:80 |
Important: The service target is
http://localhost:80because cloudflared runs on the same container as NPM. cloudflared forwards to NPM, and NPM routes to the correct backend.
Click Save.
Step 8 — Configure NPM Proxy Host¶
8.1 — Add Proxy Host for n8n¶
From your desktop, go to http://192.168.0.51:81 (NPM admin panel).
- Click Hosts → Proxy Hosts → Add Proxy Host
- Fill in:
| Field | Value |
|---|---|
| Domain Names | n8n.exzentcg.com |
| Scheme | http |
| Forward Hostname / IP | 192.168.0.52 |
| Forward Port | 5678 |
| Websockets Support | ✅ Enable |
- Click Save
Websockets support is required — n8n's UI uses websockets for real-time updates.
8.2 — Test the Full Path¶
At this point, the traffic path is:
Internet → Cloudflare → Tunnel → cloudflared (localhost) → NPM (:80) → n8n (:5678)
From your desktop browser, visit:
https://n8n.exzentcg.com
You should see the n8n setup screen (or login screen if already configured). If you get a Cloudflare error page, check:
- Tunnel status in Cloudflare dashboard (should say HEALTHY)
- NPM proxy host configuration
docker logson both containers for errors
Step 9 — Cloudflare Zero Trust Access Policy¶
9.1 — Protect the n8n UI¶
- Go to Cloudflare → Zero Trust → Access → Applications
- Click Add an application → Self-hosted
- Fill in:
| Field | Value |
|---|---|
| Application name | n8n |
| Application domain | n8n.exzentcg.com |
| Session duration | 24 hours |
- Click Next to configure the policy:
| Field | Value |
|---|---|
| Policy name | Allow admin |
| Action | Allow |
| Selector | Emails |
| Value | your-email@example.com |
- Authentication method: One-time PIN (simplest) or connect Google/GitHub as an identity provider.
- Click Save
9.2 — Bypass for Webhooks¶
If n8n will receive webhooks (from Stripe, Telegram, etc.), create a bypass:
- In the same application, go to Policies → Add a policy
| Field | Value |
|---|---|
| Policy name | Webhook bypass |
| Action | Bypass |
| Selector | URI Path |
| Value | /webhook/* |
- Add another rule for the test webhook path:
| Selector | Value |
|---|---|
| URI Path | /webhook-test/* |
- Click Save
9.3 — Test Access Policy¶
From your desktop browser in an incognito/private window:
- Visit
https://n8n.exzentcg.com - You should see a Cloudflare Access login page asking for your email
- Enter your email → receive a one-time PIN → enter PIN
- You should now see the n8n login/setup screen
From a different email (or test with a colleague), verify that unauthorized emails are blocked.
Step 10 — n8n Initial Setup¶
10.1 — Create Owner Account¶
After passing the Cloudflare Access gate:
- n8n will show a setup wizard
- Create your owner account with a strong, unique password
- This is a secondary auth layer behind Cloudflare Zero Trust
10.2 — Verify Webhook URL¶
- Go to Settings → General in n8n
- Confirm Webhook URL is
https://n8n.exzentcg.com - If not, set it manually
10.3 — Test a Webhook¶
Create a simple test workflow:
- Add a Webhook trigger node
- Set method to GET
- Activate the workflow
- Copy the production webhook URL (e.g.
https://n8n.exzentcg.com/webhook/abc123) - Open that URL in a browser or curl it from anywhere:
curl https://n8n.exzentcg.com/webhook/abc123
If you get a response, the full chain is working end to end: Internet → Cloudflare → Tunnel → NPM → n8n → Response
Step 11 — Snapshot Everything¶
Now that Phase 1 is fully operational, take snapshots of both containers:
11.1 — Stop Containers Before Snapshot (Optional but Recommended)¶
# From Proxmox host
pct stop 101
pct stop 102
11.2 — Create Snapshots¶
In the Proxmox WebUI:
- Go to 101 (edge-gateway) → Snapshots → Take Snapshot
- Name:
phase1-complete -
Description:
Phase 1 complete — cloudflared + NPM working -
Go to 102 (n8n-app) → Snapshots → Take Snapshot
- Name:
phase1-complete - Description:
Phase 1 complete — n8n running, firewall tested
11.3 — Restart Containers¶
pct start 101
pct start 102
Verify everything comes back up:
# Wait 30 seconds, then check
curl -s --connect-timeout 5 https://n8n.exzentcg.com
Post-Deployment Verification Checklist¶
Run through every check. All must pass.
Security Checks¶
# From n8n-app container:
# ✅ Can reach external APIs
curl -s --connect-timeout 5 https://httpbin.org/get | head -3
# ❌ Cannot reach desktop
curl -s --connect-timeout 3 http://192.168.0.16
# Expected: timeout
# ❌ Cannot reach Proxmox host
curl -s --connect-timeout 3 https://192.168.0.200:8006
# Expected: timeout
# ❌ Cannot reach router (except DNS)
curl -s --connect-timeout 3 http://192.168.0.1
# Expected: timeout
# From edge-gateway container:
# ✅ Can reach n8n
curl -s --connect-timeout 5 http://192.168.0.52:5678 | head -3
# ❌ Cannot reach desktop
curl -s --connect-timeout 3 http://192.168.0.16
# Expected: timeout
# ❌ Cannot reach Proxmox host WebUI
curl -s --connect-timeout 3 https://192.168.0.200:8006
# Expected: timeout
Functionality Checks¶
| Check | How | Expected Result |
|---|---|---|
| n8n UI loads | Browser: https://n8n.exzentcg.com |
Cloudflare Access gate → n8n login |
| Zero Trust blocks strangers | Incognito + wrong email | Access denied |
| Webhook works | Curl a test webhook URL | Response from n8n |
| NPM admin accessible | Browser: http://192.168.0.51:81 from desktop only |
NPM dashboard |
| Proxmox WebUI accessible | Browser: https://192.168.0.200:8006 from desktop only |
Proxmox login |
| Tunnel status | Cloudflare dashboard → Tunnels | Shows HEALTHY |
Service Persistence Checks¶
# Reboot both containers and verify auto-start
pct reboot 101
pct reboot 102
# Wait 60 seconds, then verify
curl -s --connect-timeout 10 https://n8n.exzentcg.com
# Should load (Cloudflare Access page or n8n UI)
ssh root@192.168.0.51 "cd /opt/edge-gateway && docker compose ps"
# Should show both npm and cloudflared containers running
ssh root@192.168.0.52 "docker ps"
# Should show n8n container running
Ongoing Maintenance¶
| Frequency | Task | Command / Location |
|---|---|---|
| Weekly | Update n8n (bump pinned version in compose, then) | ssh root@192.168.0.52 "cd /opt/n8n && docker compose pull && docker compose up -d" |
| Weekly | Check Cloudflare Access logs | Zero Trust → Logs → Access |
| Monthly | Review firewall DROP logs | Proxmox → each LXC → Firewall → Log |
| Monthly | Snapshot both LXCs | Proxmox → Snapshots |
| Monthly | Verify firewall rule order | Proxmox → each LXC → Firewall (rules haven't shifted) |
| Monthly | Update edge-gateway LXC packages | ssh root@192.168.0.51 "apt update && apt upgrade -y" |
| Monthly | Update NPM + cloudflared images | ssh root@192.168.0.51 "cd /opt/edge-gateway && docker compose pull && docker compose up -d" |
| As needed | Rotate n8n credentials | n8n Settings → Credentials |
| Never | Rotate N8N_ENCRYPTION_KEY |
Would require re-entering every credential in n8n. Back it up instead. |
Troubleshooting Quick Reference¶
| Symptom | Likely Cause | Fix |
|---|---|---|
| Locked out of Proxmox WebUI | Firewall misconfiguration | Physical console: pve-firewall stop then fix rules |
| Tunnel shows DEGRADED | cloudflared container stopped | ssh root@192.168.0.51 "cd /opt/edge-gateway && docker compose restart cloudflared" |
| cloudflared can't reach NPM | network_mode: host missing on cloudflared |
Check compose file — cloudflared must be network_mode: host to see NPM's 127.0.0.1:80 |
| n8n unreachable via browser | NPM proxy misconfigured | Check NPM → Proxy Hosts → ensure :5678 and websockets on |
| n8n shows 502 Bad Gateway | n8n container down | ssh root@192.168.0.52 "cd /opt/n8n && docker compose up -d" |
| n8n credentials unusable after restart | N8N_ENCRYPTION_KEY changed or .env missing |
Restore .env from password manager backup — there is no other recovery |
| Webhooks failing | Access policy blocking them | Add /webhook/* bypass in Zero Trust |
| n8n can reach LAN devices | Firewall rule order wrong | Verify DROP lan_subnet is ABOVE ALLOW any:443 |
| DNS not resolving in containers | Missing resolver | echo "nameserver 192.168.0.1" > /etc/resolv.conf |
| NPM admin panel not loading | Firewall blocking port 81 | Verify rule: IN ACCEPT admin_desktop 81 TCP |