9 min read

n8n Docker Setup: Encryption Key, Volumes & Backup Guide


đź’ˇ

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 _FILE variant 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:

  1. Use the direct env var instead of _FILE:

    environment:
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
  2. Strip newlines if you must use secrets:

    command: >
      sh -c 'export N8N_ENCRYPTION_KEY=$(cat /run/secrets/n8n_key | tr -d "\n") && n8n start'
  3. For queue mode workers: Set N8N_ENCRYPTION_KEY directly on both main and worker containers. Do not rely on _FILE variant.

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:

FilePurpose
database.sqliteAll workflows, executions, and metadata
configAuto-generated encryption key (if not set via env)
Credential dataEncrypted 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_TIMEZONE controls schedule nodes. TZ sets the container OS time.
  • The volume mount -v n8n_data:/home/node/.n8n persists all data.
  • N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true adds 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:

  1. The data volume (contains SQLite DB and config)
  2. Workflow exports (JSON files)
  3. 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:

  1. Same encryption key: Import encrypted credentials directly.
  2. 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

ItemLocationNotes
SQLite DB./n8n_data/database.sqliteCopy nightly. Keep 7-30 days.
Config (incl. key)./n8n_data/configStore off-box and encrypt.
Workflowsbackups/latest/*.jsonCreated by export:workflow.
Credentialsbackups/latest/*.jsonEncrypted 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:

  1. Find the original key in /home/node/.n8n/config from your backup.
  2. Set it as N8N_ENCRYPTION_KEY and restart.
  3. 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:

  1. Use N8N_ENCRYPTION_KEY directly, not the _FILE variant.
  2. Verify the env var is set on worker containers.
  3. Check for newlines in secret values.

Credentials lost after container restart

Cause: Volume not mounted, or volume was recreated.

Fix:

  1. Always use a named volume or bind mount.
  2. Set N8N_ENCRYPTION_KEY explicitly so it survives volume loss.
  3. Back up the key separately from the volume.

Queue mode not processing jobs

Cause: Redis or DB misconfiguration.

Fix:

  1. Verify EXECUTIONS_MODE=queue on main and workers.
  2. Check QUEUE_BULL_REDIS_HOST points to the correct Redis.
  3. Ensure all containers share the same encryption key.

Summary

  1. Set N8N_ENCRYPTION_KEY explicitly. Never rely on auto-generation.
  2. Avoid _FILE variant in queue mode. Use the direct env var.
  3. Mount /home/node/.n8n to a persistent volume. Bind mounts make backups easier.
  4. Back up the key separately. Store it in a vault or secrets manager.
  5. 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.

đź“§