Your n8n credentials are only as safe as your N8N_ENCRYPTION_KEY. Treat it like production database root access guard it, back it up, and test recovery.
Encryption key basics
What you’ll learn:
- How N8N_ENCRYPTION_KEY protects credentials
- Why the same key must persist
- Where decryption happens in n8n
Losing the key means losing every stored secret. Keeping it safe keeps your automation alive.
Think of it as a master safe combination. If it changes or disappears, the contents stay locked.
How it works
- Encrypts at rest: N8N_ENCRYPTION_KEY encrypts sensitive fields before storage
- Decrypts in memory: n8n decrypts only at run time in process memory, then calls the node
- Persists across nodes: Self-hosted deployments must supply the same key across restarts and replicas
What the key does
- Protects secrets: API keys, tokens, passwords stored in the database
- Must match everywhere: Identical key for main process and all workers
- Rotation is explicit: Rotation requires a deliberate re-encryption step
If credentials fail to decrypt after a restart, the running key likely differs from the one used to encrypt.
Cloud vs self-hosted
| Deployment | Key owner | Your focus |
|---|---|---|
| n8n Cloud | Provider | App hardening and backups |
| Self-hosted | You | Key generation, storage, backups, rotation |
One rule holds everywhere: keep the same key for the lifetime of the credentials.
flowchart TD
A[Store secret] --> B[Encrypt with key]
B --> C[Save to DB]
D[Run workflow] --> E[Load secret]
E --> F[Decrypt in memory]
F --> G[Call service]
classDef trigger fill:#e1f5fe,stroke:#01579b
classDef process fill:#fff3e0,stroke:#ef6c00
classDef action fill:#e8f5e8,stroke:#2e7d32
class A,D trigger
class B,E,F process
class C,G action
With the fundamentals clear, let’s set up a strong key and deliver it safely.
Setup guides
What you’ll learn:
- How to generate a strong key
- How to configure Docker, systemd, Kubernetes
- How to run queue mode with the same key
Generate a strong key
# 32 bytes base64 (~256-bit)
openssl rand -base64 32 > n8n.key
chmod 600 n8n.key
- Store in a vault: Keep n8n.key in a secrets manager or secure store
- Avoid shell leaks: Write to files with 0600 permissions to prevent history exposure
- Prefer managed KMS: A key management service (KMS) or hardware-backed HSM (hardware security module) improves security
Consistency matters more than cleverness. Pick a format and stick to it.
Docker compose
# .env (chmod 600)
N8N_ENCRYPTION_KEY=$(cat n8n.key)
N8N_PORT=5678
# docker-compose.yml
version: "3.9"
services:
n8n:
image: n8nio/n8n:latest
env_file: [.env]
environment:
- TZ=UTC
ports:
- "5678:${N8N_PORT}"
volumes:
- n8n_data:/home/node/.n8n
- n8n_local_files:/files
restart: unless-stopped
volumes:
n8n_data:
n8n_local_files:
- Persist data dir: Keep /home/node/.n8n to avoid accidental key regeneration on rebuilds
- Keep secrets out of Git: Inject via CI or secrets manager
- Use Docker secrets: For Swarm, prefer secrets over env vars
Compose first, then promote to Swarm or Kubernetes once secrets handling is solid.
Optional: Swarm secret
echo -n "$(cat n8n.key)" | docker secret create n8n_encryption_key -
# snippet inside the service
secrets:
- source: n8n_encryption_key
target: n8n_encryption_key
uid: "1000"
mode: 0400
environment:
- N8N_ENCRYPTION_KEY_FILE=/run/secrets/n8n_encryption_key
Reading from a file keeps the key out of env inspection tools.
Linux service (systemd)
# /etc/n8n/n8n.env (chmod 600)
N8N_ENCRYPTION_KEY=$(cat /root/n8n.key)
# /etc/systemd/system/n8n.service
[Unit]
Description=n8n Automation Server
After=network.target
[Service]
User=n8n
WorkingDirectory=/opt/n8n
EnvironmentFile=/etc/n8n/n8n.env
ExecStart=/usr/local/bin/n8n start
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now n8n
sudo systemctl status n8n
- Keep env in root-owned file: Do not put secrets in unit files
- Restrict read access: Use least privilege
- Scrub logs: Do not echo the key anywhere
Kubernetes (Secrets and Deployment)
kubectl create secret generic n8n-encryption \
--from-literal=N8N_ENCRYPTION_KEY="$(cat n8n.key)"
apiVersion: apps/v1
kind: Deployment
metadata:
name: n8n
spec:
replicas: 1
selector: { matchLabels: { app: n8n } }
template:
metadata: { labels: { app: n8n } }
spec:
containers:
- name: n8n
image: n8nio/n8n:latest
env:
- name: N8N_ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: n8n-encryption
key: N8N_ENCRYPTION_KEY
volumeMounts:
- name: n8n-data
mountPath: /home/node/.n8n
volumes:
- name: n8n-data
persistentVolumeClaim:
claimName: n8n-pvc
- Use Secrets: Never store the key in a ConfigMap
- Encrypt etcd: Enable Secret encryption at rest
- Limit access: Use RBAC (role-based access control) to restrict Secret reads
Queue mode (main and workers)
# Main (API and UI)
export N8N_ENCRYPTION_KEY="$(cat n8n.key)"
export N8N_EXECUTIONS_MODE=queue
n8n start
# Worker processes same key required
export N8N_ENCRYPTION_KEY="$(cat n8n.key)"
export N8N_EXECUTIONS_MODE=queue
n8n worker
- Same key everywhere: All processes must share the same key
- Mismatch breaks runs: Decryption failures surface on worker execution
- Keep workers stateless: Mount only what is required
flowchart TD
A[Choose setup] --> B[Docker]
A --> C[Systemd]
A --> D[Kubernetes]
B --> E[Env file]
B --> F[Swarm secret]
C --> G[Env file]
D --> H[Cluster Secret]
classDef trigger fill:#e1f5fe,stroke:#01579b
classDef process fill:#fff3e0,stroke:#ef6c00
class A trigger
class B,C,D,E,F,G,H process
With your key delivered safely, reduce risk with secure storage and backups.
Security practices
What you’ll learn:
- How to store and back up the key
- How to deliver secrets with lower leak risk
- What mistakes to avoid
Store and back up the key
- Use a secrets manager: Prefer a managed KMS or HSM-backed store
- Keep two offline backups: Separate regions or facilities
- Document break-glass steps: Owners, locations, and procedures
A secret that cannot be recovered is downtime waiting to happen.
Secret delivery patterns
| Pattern | Location | Tradeoff |
|---|---|---|
| Env var | File on disk | Simple but easy to leak via logs and tools |
| File mount | In-memory fs | Avoids env introspection, must manage permissions |
| KMS bootstrap | External KMS | Central rotation and audit with added dependency |
Prefer file-based or KMS bootstrap for production.
Examples: external managers
AWS Secrets Manager to env file at start:
aws secretsmanager get-secret-value \
--secret-id n8n/encryption-key \
--query SecretString --output text > /etc/n8n/n8n.key
chmod 600 /etc/n8n/n8n.key
export N8N_ENCRYPTION_KEY="$(cat /etc/n8n/n8n.key)"
HashiCorp Vault with agent template to file:
# /etc/vault.d/n8n.hcl
exit_after_auth = false
auto_auth { method "approle" { mount_path = "auth/approle" role_id = "..." secret_id_file = "/etc/vault.d/secret_id" } }
sinks { file { path = "/run/vault/token" } }
# Template renders key to a file with 0400
consul-template -template \
"/etc/templates/n8n.ctmpl:/run/secrets/N8N_ENCRYPTION_KEY:0400" \
-once
Centralized control makes rotation and audit tractable.
Avoid common mistakes
- Do not commit keys: Never push keys to Git, even private repos
- Do not log env: Avoid printing environment at startup
- Do not drop data volumes: Rebuilds must keep persistent data
- Do not mix keys: Never run multiple keys against one database
Tip: add a preflight that halts startup if the expected key source is missing or the data directory is not mounted.
Now that storage is solid, prepare for recovery before you need it.
Recovery playbook
What you’ll learn:
- How to diagnose decryption errors
- How to restore or rebuild safely
- How to migrate and drill
Diagnose decryption failures
# 1) Check the active key source
printenv | grep -E '^N8N_ENCRYPTION_KEY'
ls -l /home/node/.n8n || true
# 2) Inspect recent deploys for key changes
sudo ls -lt /etc/n8n
sudo git -C /infra/env-config log -n 3 -- .
# 3) Look for decryption errors
journalctl -u n8n --since "-2h" | grep -i decrypt || true
- Restore the old key if it differs from last known good
- Check mounts first if the key vanished
- Document actions to speed future fixes
flowchart TD
A[Decrypt errors] --> B[Have old key]
B -->|Yes| C[Restore key]
B -->|No| D[Rebuild creds]
C --> E[Test workflows]
D --> F[Rotate at providers]
E --> G[Fix process gap]
classDef alert fill:#f3e5f5,stroke:#7b1fa2
classDef action fill:#e8f5e8,stroke:#2e7d32
class A alert
class B alert
class C,E,G,F action
You have the old key
# Stop services
docker compose down || true
systemctl stop n8n || true
# Restore the known-good key
cp /secure-backups/n8n.key /etc/n8n/n8n.key
chmod 600 /etc/n8n/n8n.key
# Start services
systemctl start n8n || docker compose up -d
- Validate on staging when possible
- Run critical workflows end to end
- Capture a post-mortem for process improvements
You do not have the key
- Crypto is working: n8n cannot decrypt without the original key
- Re-create credentials: Assign new credential identifiers
- Rotate providers: Use provider consoles to rotate API keys and OAuth tokens
# Triage worksheet (CSV)
printf "workflow_id,credential_name,owner,provider,rotation_status\n" > triage.csv
Accept the loss quickly to minimize downtime and risk.
Safe migration to new servers
# 1) Freeze writes to old instance
systemctl stop n8n || docker compose down
# 2) Back up database and data dir
pg_dump -h db -U n8n n8n > n8n.sql
tar czf n8n_home.tgz -C /home/node .n8n
# 3) Restore on target before first start
scp n8n.sql target:
scp n8n_home.tgz target:
psql -h db -U n8n -d n8n < n8n.sql
tar xzf n8n_home.tgz -C /home/node
export N8N_ENCRYPTION_KEY="$(cat /home/node/.n8n/key || cat /etc/n8n/n8n.key)"
# 4) Start and test
n8n start
- Do not start without the original key in place
- Validate decryption on a non-production clone
- Cut over with DNS after tests pass
Practice a quarterly drill
#!/usr/bin/env bash
set -euo pipefail
# Recover into ephemeral env and run smoke tests
EXPORT_KEY=$(cat /secure-backups/n8n.key)
export N8N_ENCRYPTION_KEY="$EXPORT_KEY"
docker compose -f docker-compose.test.yml up -d
sleep 10
curl -fsS http://localhost:5679/rest/healthz >/dev/null
printf "[%s] DR drill OK\n" "$(date)" >> recovery.log
- Schedule the drill and track results
- Fail the build if the drill fails
- Keep recent artifacts for faster audits
Case study and checklists
What you’ll learn:
- A real outage timeline and fix
- Hardening steps before production
- Operations and rotation practices
Background and timeline
- Monday 02:00 host patched, containers rebuilt from scratch
- 02:05 data volume missing, n8n generated a fresh key silently
- 08:10 decryption errors reported in alerts
One missing volume mount became a full morning incident.
Symptoms and investigation
- Workflow failures: First node using credentials failed
- Visible but locked: Credentials list visible but unusable
- Log evidence: Repeated decryption errors after redeploy window
Root cause was a changed compose file without the data volume.
Recovery actions
# Stop prod and clone to staging to test recovery
docker compose down
# Mount the correct volume and restore the last known-good key
docker volume create n8n_data
docker run --rm -v n8n_data:/data -v /secure-backups:/bk alpine \
sh -c 'cd /data && tar xzf /bk/n8n_home_2025-11-30.tgz'
# Start with the original key injected
export N8N_ENCRYPTION_KEY="$(cat /secure-backups/n8n.key)"
docker compose up -d
# Smoke test critical workflows
n8n execute --id 12 --raw
n8n execute --id 34 --raw
- Validated in staging to avoid compounding risk
- Repeated steps in production after checks passed
- MTTR under 90 minutes after diagnosis
Lessons learned
- Key in vault and volume mounted with guardrails
- CI checks prevent merges that drop persistence
- Startup preflight blocks boot without the expected key source
erDiagram
Workflow ||--o{ Execution : runs
Workflow }o--o{ Credential : uses
Workflow {
int id
string name
datetime created_at
}
Credential {
int id
string name
string data_enc
datetime created_at
}
Execution {
int id
int workflow_id
datetime created_at
}
Checklists
Pre-production hardening
- Generate a 256-bit key and store it in a vault
- Deliver secrets by file or KMS with 0400 permissions
- Persist /home/node/.n8n across restarts
- Block logging of environment and startup config
- Document break-glass owners and backup locations
Operations and rotation
- Drill recovery quarterly in a clean environment
- Alert on container restarts without the expected volume
- Rotate provider API keys on a schedule and re-encrypt as needed
- Restrict Secrets read permissions with least privilege
- Test queue workers with the same key before scaling out
Next steps
- Adopt a central secrets manager if you have not already
- Automate a preflight that fails boot without the key
- Add a migration runbook with explicit ordering
Keep the playbook near the keyboard, not buried in a wiki.
Bottom line: persist one strong key, deliver it securely, and rehearse recovery. Do that and your n8n credentials will stay safe even on your worst day.