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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user