FlowKit

N8N_ENCRYPTION_KEY: the key that protects your n8n credentials — and how to never lose it

Published 3 August 2026 · 9 min read

It's usually the same story that brings people here. You've just migrated your n8n instance to a new server, or reinstalled it after an incident. The database restored fine: the workflows are there, the execution history too, everything looks in order. Then you run a workflow, and every credential returns the same message: "Credentials could not be decrypted". Nothing is corrupted, nothing has vanished — the encryption key simply changed, and without it, the credentials stored in the database are unreadable. By design.

This guide explains how n8n protects your credentials, why the N8N_ENCRYPTION_KEY variable is the most critical — and most overlooked — piece of any self-hosted setup, and how to organize yourself so you never live through that scenario, or get out of it cleanly if it's already too late.

What n8n encrypts, and why

An n8n instance quickly accumulates dozens of credentials: OpenAI API keys, Slack tokens, SMTP passwords, PostgreSQL accesses… If all of that were stored in plain text in the database, anyone able to dump it — a contractor, a backup sitting on a misconfigured bucket, one access too many — would walk away with every one of your accesses.

n8n avoids that risk by encrypting credentials before writing them to the database. A SELECT on the credentials table only reveals unreadable blobs. To decrypt them, n8n needs the encryption key, which it obtains in one of two ways:

  • Supplied explicitly via the N8N_ENCRYPTION_KEY environment variable — the recommended case, more on that below.
  • Generated automatically on first startup if the variable is absent, then stored in the configuration file inside n8n's data directory (~/.n8n/config, which lives in a Docker volume on containerized installs).

That second case is the default mode, and it's the one that traps everybody: the key exists, it works, but nobody knows it's there. The day the Docker volume is lost, the server is reinstalled, or a migration only copies the database, the new instance generates a new key — and every credential encrypted with the old one becomes undecryptable.

It's worth stating plainly, because it's counter-intuitive: a backup of the database alone does not protect your credentials. The database without the key contains perfectly restorable workflows and perfectly unusable credentials.

The golden rule: set the key explicitly from day one

The fix comes down to two moves, done once and for all at install time (or right now if your instance is already running — see below).

1. Generate a strong key. A long random value, produced by a real generator:

openssl rand -hex 24

2. Set it in the configuration, via the .env referenced by your docker-compose.yml:

# docker-compose.yml (excerpt)
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    environment:
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    # ... rest of the configuration
# .env
N8N_ENCRYPTION_KEY=the_value_generated_by_openssl

3. Store it in a password manager (Bitwarden, 1Password, Vaultwarden…), the same way you'd store the server's root password. Not in a text file on the server itself — if the server dies, the key dies with it.

One important caveat: if your instance is already running with an auto-generated key, do not set a new value in N8N_ENCRYPTION_KEY — you'd trigger the exact error this guide is trying to prevent. Recover the existing key from the ~/.n8n/config file inside the container (for instance docker exec -it n8n cat /home/node/.n8n/config), and that's the value you carry over into the .env. The key doesn't change; it simply becomes explicit, storable in your secrets manager, and impossible to lose to a volume accident.

This habit echoes an old lesson from security research: cryptography rarely fails on the mathematics, and almost always on the human handling of keys. The classic study by Whitten and Tygar, Why Johnny Can't Encrypt: A Usability Evaluation of PGP 5.0 (USENIX Security, 1999 — see on Google Scholar), already showed that most participants failed to correctly use an encryption tool designed for the general public — not because the algorithm was weak, but because key handling escaped them. Twenty-five years later, the "Credentials could not be decrypted" of an n8n instance migrated without its key is the exact same illustration: the algorithm did its job perfectly; it's the key that didn't come along.

A complete backup = database + key

A proper n8n backup therefore has two inseparable parts:

  • The database, which holds workflows, encrypted credentials and history — the full procedure is in our PostgreSQL backup and restore guide.
  • The encryption key, without which the credentials half of that database is noise.

If the key is set in the .env as recommended, including that file (or its contents, via the password manager) in the backup procedure is enough. If it isn't, then the Docker volume containing ~/.n8n must be backed up alongside the database — and that's precisely the volume most backup scripts forget, because the thinking is "the data lives in PostgreSQL" and nobody sees what could be irreplaceable about a config directory. It holds the only copy of the key.

Test the restore at least once: mount the restored database and the key on a throwaway instance, open a credential, run a workflow. A backup that's never been tested is a hypothesis, not a backup.

Migration: restore the database AND reuse the same key

Every instance migration — a server change, a move from SQLite to PostgreSQL, or a migration from n8n Cloud to self-hosted — follows the same rule: the new instance must start with the old instance's key, supplied via N8N_ENCRYPTION_KEY, before its first launch. Order matters: if the new instance starts once without the key, it generates its own, and you'll have to reconfigure it properly before the restored credentials become readable again.

The credentials side of the migration checklist:

  1. Recover the source instance's key (.env, or ~/.n8n/config if it was auto-generated).
  2. Put it in the target instance's .env.
  3. Restore the database.
  4. Start up, open an existing credential to check it decrypts, run a test workflow.

The same principle applies to a plain version update: the key doesn't change between versions, but an update done by recreating containers without preserving the volume or the .env can make it disappear — our guide on updating n8n without breaking anything covers the procedure that avoids that kind of collateral damage.

Queue mode: one key for main and every worker

If your instance runs in queue mode with Redis and workers, the constraint extends: the main process and every worker read the same encrypted credentials from the same database, so all of them must start with exactly the same N8N_ENCRYPTION_KEY. A worker given a different key (or no key at all, hence its own auto-generated one) will accept jobs but fail when it tries to decrypt the credentials needed to run them.

In practice, that's one more argument for the shared .env: a single file, referenced by the n8n, n8n-worker and related services in the docker-compose.yml, and inconsistency becomes impossible.

Lost key: what's recoverable, what isn't

If the key is definitively lost — deleted volume, dead server, no backup of the config file — the honest answer is: the credentials are unrecoverable. No support team, no tool, no database access can decrypt them without the key; that's exactly the guarantee encryption is meant to provide, and it cuts against you too.

What remains intact:

  • The workflows, in full: logic, nodes, connections, settings.
  • The execution history and the rest of the database.

The way forward: generate a new key, set it in N8N_ENCRYPTION_KEY, back it up (this time), then re-enter every credential by hand. It's tedious, but it's also an unexpected opportunity to clean house: regenerate the API keys on the service side rather than pasting the old ones back in, delete orphaned credentials no workflow uses anymore, and note who has access to what — the full approach is in our guide to securing your API credentials in n8n. Plenty of instances carry accesses granted "just to test" two years earlier; the forced inventory has at least that virtue.

Changing the key on purpose: rotation, without illusions

Can you rotate the key as a precaution, the way you'd rotate a password? Technically, n8n offers no native mechanism to re-encrypt the database with a new key. Changing the value of N8N_ENCRYPTION_KEY re-encrypts nothing: it simply makes the existing credentials unreadable.

In practice, a rotation therefore amounts to re-creating the credentials: export or inventory what exists while the old key still works, switch to the new key, then re-enter the credentials one by one. It's the same project as a lost key, just planned. Short of a confirmed key compromise, it's rarely worth it for a small instance; if you do commit to it, run it on a test instance first, and check the official n8n documentation for the current state of what the tool supports.

What N8N_ENCRYPTION_KEY is not

Two recurring confusions:

  • It's not a configuration variable like the others. n8n's environment variables get changed, tested and tuned over time — the webhook URL, the timezone, execution limits. N8N_ENCRYPTION_KEY is the only one whose modification functionally destroys data. You set it once, you back it up, you never touch it again.
  • It's not the interface's authentication. The encryption key protects the credentials stored in the database against a direct read of the database. It does not protect access to the n8n interface itself (user accounts, SSO, reverse proxy): those are two distinct layers, and you need both.

Common pitfalls

  • Letting n8n generate the key without ever writing it down: the default mode works perfectly… until the first migration or volume loss. Set N8N_ENCRYPTION_KEY explicitly, from install day.
  • Backing up the database but not the key: the backup script covers PostgreSQL and ignores the ~/.n8n volume — the credentials half of the backup is unusable when the day comes.
  • Setting a new value on an existing instance thinking you're "regularizing" things: the credentials encrypted with the old auto-generated key become unreadable. Recover the existing key from ~/.n8n/config first.
  • Starting the new instance before the key is in place during a migration: an auto-generated key settles in, and "Credentials could not be decrypted" appears despite a correctly restored database.
  • Queue-mode workers with different keys: jobs get dispatched, decryption fails at execution time. One .env shared by all services.
  • Storing the key only on the server it protects: keep it in the server's .env and in an external password manager, or the key disappears with the machine.
  • Committing the .env to a Git repository: the key is a secret on par with a database password — out of version control, always.

In summary

N8N_ENCRYPTION_KEY is the most critical variable of a self-hosted n8n install: it encrypts every credential in the database, and without it, a backup or a migration only restores workflows with no accesses. The discipline comes down to three moves — generate a strong key (openssl rand -hex 24), set it explicitly in the .env from install day, keep it in a password manager — and one rule: the database and the encryption key always travel together, in backups as in migrations. If you inherit a lost key, the workflows survive, the credentials get re-entered, and the forced access inventory that follows is exactly the kind of project the Compliance & Audit Pack (€149) formalizes: who has access to what, where the secrets live, what gets logged — managing the encryption key is only the first line of it.

FAQ

Frequently asked questions

Why do my credentials show "Credentials could not be decrypted" after migrating or reinstalling n8n?

Because the new instance isn't using the same encryption key as the old one. Credentials are stored encrypted in the database, and n8n needs the exact key that encrypted them to read them back. If you never set N8N_ENCRYPTION_KEY, the key lived in the config file inside the old instance's .n8n directory; the new instance generated a fresh one on first startup, and decryption fails. The fix: recover the original key (the ~/.n8n/config file from the old server or its backup) and supply it via N8N_ENCRYPTION_KEY before restarting.

I've permanently lost my n8n encryption key: is everything gone?

No, but the credentials are. Without the key, no tool can decrypt the credentials stored in the database — that's precisely what the encryption is for. The database itself remains usable: workflows, their execution history and their settings are intact. Set a new N8N_ENCRYPTION_KEY, back it up this time, then re-enter every credential by hand (and regenerate the API keys on the service side while you're at it — it's a good opportunity to inventory and clean up your accesses).

Do all workers in queue mode need the same N8N_ENCRYPTION_KEY?

Yes, without exception. The main process and every worker read the same encrypted credentials from the same database, so they must share exactly the same key. A worker started with a different key will fail to decrypt credentials when it executes the workflows handed to it. Put the key in a shared .env file referenced by every service in the docker-compose.

Bundle FlowKit Complet

€269