Goal: set the n8n encryption key correctly, persist your data with proper volumes, and back up everything so you never lose credentials or workflows.
Why the Encryption Key Matters
The N8N_ENCRYPTION_KEY is the single most important configuration for n8n. Without it set correctly, you will lose access to all saved credentials after a container restart or migration.
n8n encrypts API keys, OAuth tokens, and database passwords using this key. Lose it, and you start over.
This guide shows exactly how to:
- Set the encryption key correctly (and fix the
_FILEvariant bug) - Configure Docker volumes for proper data persistence
- Back up and restore workflows and credentials safely
- Scale to queue mode without breaking encryption
Setting the N8N_ENCRYPTION_KEY (Critical)
How the encryption key works
When n8n starts without N8N_ENCRYPTION_KEY set, it generates a random key and stores it in /home/node/.n8n/config. Every credential you save is encrypted with this key.
The problem: if your container restarts without the volume mounted, or the config file gets deleted, n8n generates a new key. Your old credentials become unreadable.
Setting the key explicitly
Always set the key as an environment variable. Generate a strong random string (32+ characters).
# generate a key
openssl rand -base64 32
# example output: K7xR2pL9mN4qW8vF3jH6tY1cB5sA0dG+rUiOeZxPwMk=
Use it in your docker run command:
docker run -d \
--name n8n \
-p 5678:5678 \
-e N8N_ENCRYPTION_KEY="K7xR2pL9mN4qW8vF3jH6tY1cB5sA0dG+rUiOeZxPwMk=" \
-e GENERIC_TIMEZONE="America/New_York" \
-e TZ="America/New_York" \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n
Or in docker-compose.yml:
version: "3.8"
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
volumes:
- ./n8n_data:/home/node/.n8n
With a .env file:
N8N_ENCRYPTION_KEY=K7xR2pL9mN4qW8vF3jH6tY1cB5sA0dG+rUiOeZxPwMk=
GENERIC_TIMEZONE=America/New_York
The N8N_ENCRYPTION_KEY_FILE bug (Docker Secrets)
If you use Docker Swarm or Kubernetes secrets, you might try N8N_ENCRYPTION_KEY_FILE to read the key from a file. This is documented to work but has known issues.
The problem: GitHub Issue #14596 reports that N8N_ENCRYPTION_KEY_FILE does not work in queue mode. Workers fail with:
Error: Failed to start worker because of missing encryption key.
Please set the N8N_ENCRYPTION_KEY env var when starting the worker
Related bug: GitHub Issue #10818 shows Docker secrets adding extra newlines to values, causing authentication failures.
Workarounds:
-
Use the direct env var instead of
_FILE:environment: - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} -
Strip newlines if you must use secrets:
command: > sh -c 'export N8N_ENCRYPTION_KEY=$(cat /run/secrets/n8n_key | tr -d "\n") && n8n start' -
For queue mode workers: Set
N8N_ENCRYPTION_KEYdirectly on both main and worker containers. Do not rely on_FILEvariant.
Recovering a lost encryption key
If you lost the key but still have the original volume:
# check inside the config file
docker exec n8n cat /home/node/.n8n/config
Look for the encryptionKey value. Copy it and set it as N8N_ENCRYPTION_KEY going forward.
If the volume is gone and you never backed up the key, your credentials are unrecoverable. Export workflows (they don’t contain secrets) and recreate credentials manually.
Docker Volume Configuration
Understanding n8n data persistence
n8n stores everything under /home/node/.n8n inside the container:
graph TB
subgraph host[Docker Host]
H1[n8n_data Directory]
H2[Environment File]
H3[Docker Engine]
end
subgraph container[n8n Container]
C1[Container Data Directory]
C2[Database File]
C3[Credentials File]
C4[Config File]
C5[Encryption Key]
end
H1 -.->|Volume Mount| C1
C1 --> C2
C1 --> C3
C1 --> C4
C1 --> C5
H2 -.->|Environment| container
classDef hostStyle fill:#e3f2fd,stroke:#1976d2
classDef containerStyle fill:#fff3e0,stroke:#f57c00
classDef criticalData fill:#ffebee,stroke:#d32f2f
class H1,H2,H3 hostStyle
class C1,C4 containerStyle
class C2,C3,C5 criticalData
Critical files inside the volume:
| File | Purpose |
|---|---|
database.sqlite | All workflows, executions, and metadata |
config | Auto-generated encryption key (if not set via env) |
| Credential data | Encrypted API keys, tokens, passwords |
Running n8n with proper volume mapping
Create a named volume and start n8n:
docker volume create n8n_data
docker run -d \
--name n8n \
-p 5678:5678 \
-e N8N_ENCRYPTION_KEY="your-key-here" \
-e GENERIC_TIMEZONE="America/New_York" \
-e TZ="America/New_York" \
-e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
-e N8N_RUNNERS_ENABLED=true \
-v n8n_data:/home/node/.n8n \
docker.n8n.io/n8nio/n8n
Key points:
GENERIC_TIMEZONEcontrols schedule nodes.TZsets the container OS time.- The volume mount
-v n8n_data:/home/node/.n8npersists all data. N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=trueadds security checks.
Open http://YOUR_HOST:5678 to complete setup.
Using bind mounts for easier backups
Bind mounts give direct filesystem access for backups:
version: "3.8"
services:
n8n:
image: docker.n8n.io/n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
env_file: [.env]
environment:
- GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
- TZ=${GENERIC_TIMEZONE}
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
volumes:
- ./n8n_data:/home/node/.n8n
With a bind mount, you can rsync or tar the folder directly without Docker commands.
Backup and Migration
Exporting workflows and credentials
Back up three things on a schedule:
- The data volume (contains SQLite DB and config)
- Workflow exports (JSON files)
- Credential exports (encrypted normally, decrypted only for key migrations)
Run CLI exports from the container:
# create a host folder for exports
mkdir -p $(pwd)/backups
# attach it temporarily and run the exports
docker run --rm \
-v n8n_data:/home/node/.n8n \
-v $(pwd)/backups:/home/node/backups \
docker.n8n.io/n8nio/n8n \
bash -lc '
mkdir -p /home/node/backups/latest && \
n8n export:workflow --backup --output=/home/node/backups/latest && \
n8n export:credentials --backup --output=/home/node/backups/latest
'
For migrations to a different encryption key, export decrypted:
# DECRYPTED export for migration only - treat as highly sensitive
n8n export:credentials --all --decrypted --output=/home/node/backups/decrypted.json
Store decrypted.json in a vault. Delete after import.
File-level backups
If using a bind mount:
rsync -aH --delete ./n8n_data/ backup-host:/srv/backup/n8n_data/
This gives file-level rollback and disaster recovery.
Restoring to a new instance
Two paths exist:
- Same encryption key: Import encrypted credentials directly.
- Different key: Import using the decrypted export.
# set the target key
export N8N_ENCRYPTION_KEY="your-32+char-random-string"
# import inside the container
n8n import:workflow --separate --input=/home/node/backups/latest/
n8n import:credentials --separate --input=/home/node/backups/latest/
If keys differ, use decrypted.json and let n8n re-encrypt with the new key.
Backup reference table
| Item | Location | Notes |
|---|---|---|
| SQLite DB | ./n8n_data/database.sqlite | Copy nightly. Keep 7-30 days. |
| Config (incl. key) | ./n8n_data/config | Store off-box and encrypt. |
| Workflows | backups/latest/*.json | Created by export:workflow. |
| Credentials | backups/latest/*.json | Encrypted for routine backups. Decrypted only for migrations. |
Scaling with Queue Mode
When to scale
Stay on a single container until you hit limits:
- Keep concurrent executions around 10-20 on modest hosts.
- Set an explicit cap to protect the event loop:
export N8N_CONCURRENCY_PRODUCTION_LIMIT=20
When you outgrow one box, move to queue mode with Redis and workers.
Queue mode architecture
graph TB
subgraph single[Single Box Mode SQLite]
S1[n8n Container with SQLite]
S2[Internal Execution Queue]
S1 --> S2
end
subgraph scaled[Queue Mode Postgres Redis]
M1[n8n Main Process]
M2[Redis Queue]
M3[Postgres Database]
W1[Worker 1]
W2[Worker 2]
W3[Worker N]
M1 --> M2
M1 --> M3
M2 --> W1
M2 --> W2
M2 --> W3
W1 --> M3
W2 --> M3
W3 --> M3
end
classDef singleMode fill:#e8f5e8,stroke:#2e7d32
classDef scaledMode fill:#e3f2fd,stroke:#1976d2
class S1,S2 singleMode
class M1,M2,M3,W1,W2,W3 scaledMode
Queue mode docker-compose
version: "3.8"
services:
postgres:
image: postgres:15
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=strongpass
volumes:
- pg_data:/var/lib/postgresql/data
redis:
image: redis:7
n8n-main:
image: docker.n8n.io/n8nio/n8n:latest
depends_on: [postgres, redis]
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=strongpass
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- GENERIC_TIMEZONE=America/New_York
- TZ=America/New_York
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
volumes:
- n8n_data:/home/node/.n8n
ports:
- "5678:5678"
n8n-worker:
image: docker.n8n.io/n8nio/n8n:latest
depends_on: [postgres, redis]
command: ["worker"]
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=strongpass
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
volumes:
n8n_data:
pg_data:
Critical: Set the same N8N_ENCRYPTION_KEY on both main and workers. Do not use N8N_ENCRYPTION_KEY_FILE in queue mode due to the bug mentioned earlier.
Troubleshooting
”Credentials could not be decrypted”
Cause: Container started with a different encryption key than the one used to encrypt credentials.
Fix:
- Find the original key in
/home/node/.n8n/configfrom your backup. - Set it as
N8N_ENCRYPTION_KEYand restart. - If lost, re-create credentials manually.
Workers fail with “missing encryption key”
Cause: Using N8N_ENCRYPTION_KEY_FILE in queue mode, or workers not receiving the env var.
Fix:
- Use
N8N_ENCRYPTION_KEYdirectly, not the_FILEvariant. - Verify the env var is set on worker containers.
- Check for newlines in secret values.
Credentials lost after container restart
Cause: Volume not mounted, or volume was recreated.
Fix:
- Always use a named volume or bind mount.
- Set
N8N_ENCRYPTION_KEYexplicitly so it survives volume loss. - Back up the key separately from the volume.
Queue mode not processing jobs
Cause: Redis or DB misconfiguration.
Fix:
- Verify
EXECUTIONS_MODE=queueon main and workers. - Check
QUEUE_BULL_REDIS_HOSTpoints to the correct Redis. - Ensure all containers share the same encryption key.
Summary
- Set
N8N_ENCRYPTION_KEYexplicitly. Never rely on auto-generation. - Avoid
_FILEvariant in queue mode. Use the direct env var. - Mount
/home/node/.n8nto a persistent volume. Bind mounts make backups easier. - Back up the key separately. Store it in a vault or secrets manager.
- Test restores monthly. A backup you can’t restore is not a backup.
The encryption key is the single point of failure. Set it, back it up, and verify you can restore before you need to.
Related Posts
n8n on Kubernetes: Helm Chart Deployment Guide
Deploy a production-ready n8n on Kubernetes with the official Helm chart: secrets, Postgres, Redis queue mode, ingress TLS, PVCs, autoscaling, and fixes.
How to Backup and Restore n8n Docker Volumes (With Cron Examples) – Step‑by‑Step Guide
Practical guide to back up and restore n8n Docker volumes (/home/node/.n8n) with copy‑paste scripts and cron jobs. Avoid data loss by saving the encryption key.
n8n N8N_ENCRYPTION_KEY: Setup, Fixes & _FILE Bug
Understand N8N_ENCRYPTION_KEY, set it safely in Docker or native installs, fix mismatched-key errors, and work around the N8N_ENCRYPTION_KEY_FILE bug without risking data loss.
GitOps Backups for n8n: 30‑Minute Schedule + One‑Click Restore
Set up two n8n flows: a Git backup every 30 minutes and a manual restore. Copy‑paste CLI, node configs, and security best practices for safe rollbacks.