docs(n8n): enhance setup guide with PostgreSQL 15+ fixes and encryption key validation

Update n8n deployment documentation to prevent three critical issues discovered during troubleshooting:

1. PostgreSQL 15+ Compatibility (Phase 3):
   - Add explicit schema permission grants for public schema
   - Include C.utf8 locale specification for Debian 12 minimal LXC
   - Add permission validation test before proceeding

2. Encryption Key Generation (Phase 5):
   - Add pre-generation validation to prevent literal command strings in .env
   - Include verification steps for 64-character hex key format
   - Document common misconfiguration and remediation steps

3. SSL Termination Architecture (Phase 7):
   - Clarify NPM scheme setting (http backend vs https external)
   - Explain reverse proxy SSL termination pattern
   - Document why https scheme causes 502 Bad Gateway errors

Update CLAUDE_STATUS.md to mark troubleshooting session complete and document deployment success.

These preventive measures ensure clean deployments on PostgreSQL 16 and avoid the 805+ restart crash loops encountered during initial deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-02 08:55:41 -07:00
parent a626c48e7b
commit 779ae2fb24
2 changed files with 476 additions and 395 deletions

View File

@@ -603,16 +603,72 @@ timedatectl set-timezone America/New_York # Adjust to your TZ
### Phase 3: PostgreSQL Setup (10 minutes)
> **⚠️ POSTGRESQL 15+ COMPATIBILITY NOTICE**
>
> PostgreSQL 15 and later versions introduced a **breaking change** that removed the default `CREATE` privilege on the `public` schema. This affects n8n's ability to create tables during initial database migration.
>
> This guide includes the necessary permission grants for PostgreSQL 15+. If you're using PostgreSQL 14 or earlier, these steps are still safe to execute but not strictly required.
>
> **Affected Versions**: PostgreSQL 15, 16, 17+
> **Reference**: [PostgreSQL 15 Release Notes - Public Schema Permissions](https://www.postgresql.org/docs/15/ddl-schemas.html#DDL-SCHEMAS-PUBLIC)
```bash
# Switch to postgres user
sudo -u postgres psql
# Switch to postgres user and create database with proper locale and permissions
sudo -u postgres psql << 'EOSQL'
-- Execute these SQL commands:
CREATE DATABASE n8n_db;
-- Create database user
CREATE USER n8n_user WITH ENCRYPTED PASSWORD 'YourSecurePassword123!';
GRANT ALL PRIVILEGES ON DATABASE n8n_db TO n8n_user;
\q
-- Create database with C.utf8 locale (Debian 12 minimal LXC compatibility)
CREATE DATABASE n8n_db
OWNER n8n_user
ENCODING 'UTF8'
LC_COLLATE = 'C.utf8'
LC_CTYPE = 'C.utf8'
TEMPLATE template0;
-- Connect to the database to grant schema permissions
\c n8n_db
-- PostgreSQL 15+ REQUIRED: Grant CREATE on public schema
GRANT ALL ON SCHEMA public TO n8n_user;
-- Grant privileges on all current and future objects
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO n8n_user;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO n8n_user;
GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA public TO n8n_user;
-- Ensure future objects are also granted to n8n_user
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO n8n_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO n8n_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON FUNCTIONS TO n8n_user;
-- Verify database settings
SELECT datname, datcollate, datctype, pg_get_userbyid(datdba) as owner
FROM pg_database
WHERE datname = 'n8n_db';
\q
EOSQL
```
> **📝 LOCALE SELECTION RATIONALE**
>
> This guide uses `C.utf8` locale for maximum compatibility with minimal LXC containers:
>
> - **Debian 12 Minimal LXC**: Only includes `C`, `C.utf8`, and `POSIX` locales by default
> - **Case Sensitivity**: PostgreSQL locale names are case-sensitive (`C.utf8` ≠ `C.UTF-8`)
> - **Verification**: Run `locale -a` to see available locales on your system
>
> If you need full locale support (e.g., `en_US.UTF-8`), install the `locales` package:
> ```bash
> apt install locales
> dpkg-reconfigure locales
> ```
>
> Then recreate the database with your preferred locale. For most automation workflows, `C.utf8` is sufficient and provides better performance for ASCII-based data.
```bash
# Configure PostgreSQL
cat >> /etc/postgresql/16/main/postgresql.conf << 'EOF'
@@ -635,8 +691,27 @@ EOF
systemctl restart postgresql
systemctl enable postgresql
# Test connection
# Test connection and verify permissions
PGPASSWORD='YourSecurePassword123!' psql -U n8n_user -d n8n_db -h localhost -c "SELECT version();"
# Verify n8n_user can create tables (critical for PostgreSQL 15+)
echo "Testing n8n_user permissions..."
PGPASSWORD='YourSecurePassword123!' psql -U n8n_user -d n8n_db -h localhost << 'TEST_SQL'
-- This is what n8n will attempt during first startup
CREATE TABLE permission_test (
id SERIAL PRIMARY KEY,
test_data VARCHAR(100)
);
DROP TABLE permission_test;
SELECT 'PostgreSQL permissions OK' AS status;
TEST_SQL
if [ $? -eq 0 ]; then
echo "✓ SUCCESS: n8n_user has correct permissions"
else
echo "✗ ERROR: Permission test failed - check PostgreSQL 15+ grants"
exit 1
fi
```
### Phase 4: Node.js & n8n Installation (15 minutes)
@@ -663,9 +738,27 @@ chown -R n8n:n8n /opt/n8n
### Phase 5: N8N Configuration (10 minutes)
> **⚠️ CRITICAL: N8N_ENCRYPTION_KEY GENERATION**
>
> The `N8N_ENCRYPTION_KEY` is used to encrypt credentials stored in the database. This key:
> - **MUST** be a 64-character hexadecimal string (32 bytes)
> - **CANNOT** be changed after initial setup (encrypted data becomes unreadable)
> - **MUST** be generated before creating the .env file
>
> **Common Mistake**: Writing `N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)` in the .env file results in the **literal string** being stored, not the generated key. This causes n8n to crash immediately on startup.
>
> **Correct Approach**: Generate the key first, then insert it as a static value.
```bash
# Create environment configuration
cat > /opt/n8n/.env << 'EOF'
# STEP 1: Generate encryption key FIRST (execute in subshell to capture output)
ENCRYPTION_KEY=$(openssl rand -hex 32)
# STEP 2: Verify key was generated (should be 64 characters)
echo "Generated Encryption Key: $ENCRYPTION_KEY"
echo "Key Length: ${#ENCRYPTION_KEY} characters (should be 64)"
# STEP 3: Create .env file with the generated key (note: EOF without quotes to allow variable expansion)
cat > /opt/n8n/.env << EOF
# Database Configuration
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=localhost
@@ -694,10 +787,10 @@ EXECUTIONS_TIMEOUT_MAX=3600
# Timezone
GENERIC_TIMEZONE=America/New_York
# Security
# Security - DO NOT MODIFY AFTER INITIAL SETUP
N8N_BASIC_AUTH_ACTIVE=false
N8N_JWT_AUTH_ACTIVE=true
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
# Paths
N8N_USER_FOLDER=/opt/n8n/.n8n
@@ -705,11 +798,50 @@ N8N_LOG_LOCATION=/opt/n8n/logs/
N8N_LOG_LEVEL=info
EOF
# STEP 4: Verify the .env file contains actual key, not command substitution
echo ""
echo "Verifying .env file encryption key..."
grep "N8N_ENCRYPTION_KEY" /opt/n8n/.env
echo ""
# Validation check
if grep -q "N8N_ENCRYPTION_KEY=\$(openssl" /opt/n8n/.env; then
echo "✗ ERROR: Encryption key was not expanded! Contains literal command."
echo "Fix required before proceeding."
exit 1
elif grep -q "^N8N_ENCRYPTION_KEY=[a-f0-9]\{64\}$" /opt/n8n/.env; then
echo "✓ SUCCESS: N8N_ENCRYPTION_KEY properly configured (64 hex characters)"
else
echo "⚠ WARNING: Encryption key format unexpected. Manual verification required."
fi
# Secure environment file
chown n8n:n8n /opt/n8n/.env
chmod 600 /opt/n8n/.env
```
> **🔍 VERIFICATION: Encryption Key Format**
>
> Before proceeding, manually inspect `/opt/n8n/.env` and verify:
>
> **CORRECT** ✅:
> ```
> N8N_ENCRYPTION_KEY=a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
> ```
>
> **INCORRECT** ❌:
> ```
> N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
> N8N_ENCRYPTION_KEY=
> ```
>
> If the key is missing or contains a command string:
> ```bash
> # Regenerate and update the .env file
> NEW_KEY=$(openssl rand -hex 32)
> sed -i "s/^N8N_ENCRYPTION_KEY=.*/N8N_ENCRYPTION_KEY=${NEW_KEY}/" /opt/n8n/.env
> ```
### Phase 6: Systemd Service Creation (5 minutes)
```bash
@@ -766,6 +898,28 @@ journalctl -u n8n -f
Unlike traditional nginx configuration, NPM uses a web-based GUI for all proxy management. No SSH required.
> **🔒 SSL TERMINATION ARCHITECTURE**
>
> Understanding the request flow is critical for correct proxy configuration:
>
> ```
> Client Browser ──HTTPS(443)──► NPM ──HTTP(5678)──► n8n Container
> [Encrypted] [Unencrypted]
> [Internal Network]
> ```
>
> **Key Concepts**:
> 1. **SSL Termination**: NPM handles SSL/TLS encryption/decryption
> 2. **Backend Protocol**: NPM communicates with n8n over **HTTP** (not HTTPS)
> 3. **Internal Security**: Traffic between NPM and n8n is on your private LAN (192.168.2.x)
>
> **Common Misconfiguration**: Setting scheme to `https` when n8n listens on HTTP causes **502 Bad Gateway** errors because NPM attempts SSL handshake with a non-SSL backend.
>
> This is the standard reverse proxy pattern and is **secure** because:
> - Client-to-NPM traffic is encrypted (HTTPS)
> - NPM-to-backend traffic is on your isolated internal network
> - No external parties can intercept the internal HTTP traffic
**Prerequisites:**
- NPM is installed and running on CT 102
- NPM admin UI accessible at `http://192.168.2.101:81`
@@ -789,9 +943,12 @@ From your workstation browser:
2. **Configure Details Tab**:
```
Domain Names: n8n.yourdomain.com
Scheme: http
Forward Hostname/IP: 192.168.2.113
Forward Port: 5678
Scheme: http ⚠️ CRITICAL: Use 'http' not 'https'
(n8n listens on HTTP, NPM handles SSL)
Forward Hostname/IP: 192.168.2.113 (n8n container IP)
Forward Port: 5678 (n8n default port)
Options:
☑ Cache Assets
@@ -800,6 +957,33 @@ From your workstation browser:
☐ Access List (optional - configure if needed)
```
> **⚠️ IMPORTANT: Understanding the "Scheme" Setting**
>
> The "Scheme" dropdown controls how NPM communicates with the BACKEND service, NOT how external clients connect to NPM.
>
> **Correct Configuration:**
> - Scheme: `http` ← Backend communication (NPM → n8n at 192.168.2.113:5678)
> - Force SSL: `☑ Enabled` ← External connections (browser → NPM)
>
> **Traffic Flow:**
> 1. External client connects via HTTPS to NPM (SSL termination at proxy)
> 2. NPM decrypts HTTPS traffic and validates certificates
> 3. NPM forwards plain HTTP to backend at 192.168.2.113:5678
> 4. n8n receives HTTP request (no SSL certificate or processing needed)
> 5. n8n sends HTTP response back to NPM
> 6. NPM encrypts response with Let's Encrypt certificate
> 7. NPM sends HTTPS response to external client
>
> **Why This Matters:**
> - n8n listens on HTTP port 5678 with no SSL certificate configured
> - Using `https` scheme causes NPM to attempt TLS connection to backend
> - Backend cannot complete TLS handshake → 502 Bad Gateway error
> - This is standard reverse proxy SSL termination architecture
>
> **Common Mistake:**
> ❌ Setting Scheme to `https` thinking it affects external connections
> ✅ External HTTPS is controlled by "Force SSL" in SSL tab (next step)
3. **Configure SSL Tab**:
```
SSL Certificate: Request a new SSL Certificate