Back to Article List

Rotate the n8n encryption key: N8N_ENCRYPTION_KEY explained

Rotate the n8n encryption key: N8N_ENCRYPTION_KEY explained - Rotate the n8n encryption key: N8N_ENCRYPTION_KEY explained

N8N_ENCRYPTION_KEY is the string n8n uses to encrypt credential data, OAuth tokens and a few other sensitive fields before they're written to the database. Workflows, users and execution history are stored in the clear; credentials are the only thing the key protects, and they're unreadable without it. n8n either takes the key from that environment variable or, if the variable is absent on first launch, generates one and saves it in a file inside the data directory. Everything below is about that one value: where it is, how to set it, what the startup error about it means, how the official rotation feature added in 2.x works and what to do if you're on a version without it.

Where n8n stores the encryption key

The file is ~/.n8n/config, a small JSON document with an encryptionKey field. In the Docker image that's /home/node/.n8n/config, so it lives in whatever volume you mounted at /home/node/.n8n, alongside the SQLite database if you use one. A generated key is 24 random bytes encoded as base64, which comes out as 32 characters. Since 2.0 n8n enforces 0600 permissions on the file (N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS defaults to true) and logs a line about changing permissions if it finds them wider.

To read the key from a running container:

docker compose exec -u node n8n cat /home/node/.n8n/config

If you have never set N8N_ENCRYPTION_KEY, what that command prints is the only copy of your key in existence. Docker volumes get pruned, servers get rebuilt, and the number of forum threads that start with "restored my database but all credentials say they can't be decrypted" is a good argument for the next section.

Set N8N_ENCRYPTION_KEY in compose from the first start

Any string works as the key, and the custom encryption key docs only ask that it be set before the first start. n8n doesn't check length or character set. A short one defeats the purpose, so generate 32 bytes and be done with it:

openssl rand -base64 32

Put the result in the .env file next to the compose file and reference it from the service:

services:
  n8n:
    image: n8nio/n8n:2.38.5
    environment:
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    volumes:
      - n8n_data:/home/node/.n8n

On first launch n8n writes that same value into the config file, so from then on the two agree. The reasons to do this on day one rather than later: a queue mode setup needs the identical key on every worker and every webhook processor, which is far easier when it's a variable; a restore onto a new server needs the key before the database is useful; and a key you set is a key you know to back up. I set it on every instance including throwaways, because the throwaway that becomes production is a real pattern.

Mind the dotenv rules that came with 2.0: a value containing a backtick has to be quoted, and # starts a comment. A base64 key contains +, / and =, none of which are special, so it's fine unquoted. Hex from openssl rand -hex 32 is equally fine and avoids the question entirely.

"Mismatching encryption keys" on startup

The full message is "Mismatching encryption keys. The encryption key in the settings file /home/node/.n8n/config does not match the N8N_ENCRYPTION_KEY env var." n8n refuses to start when the two sources disagree, on purpose, because starting with the wrong key would let you save new credentials that the old ones can't coexist with.

The usual way to get there: an instance ran for months with a generated key, then someone added N8N_ENCRYPTION_KEY to compose with a fresh value from openssl, restarted and got the error. The fix is to decide which key is the real one. If credentials already exist, it's the one in the file, so copy that value into the env var and restart. If the instance is empty, either value will do; delete the credentials you may have created with the other key and move on. Don't edit the file to match a new env value while credentials exist unless you're following the manual re-encryption steps further down, because that's how you end up with a database full of undecryptable rows.

Queue mode: the same key on every process

Main, every worker started with n8n worker and every webhook processor started with n8n webhook must carry the same N8N_ENCRYPTION_KEY. The n8n queue mode with Redis workers guide puts the key in a shared environment block that every service references, which is the layout to copy. Workers don't share the main's volume, so the config file route doesn't help them; the variable is the only way. A worker with a different key will pick up jobs and fail them at the first node that uses a credential.

Back up the key separately from the database

A database dump and the key are two halves of one backup and neither is useful alone, so they need seperate homes. The n8n backup and restore guide has the full restore drill including the order to bring things back in; the short version is that the dump holds the encrypted credentials and the key opens them. Keep the key in the password manager or vault your team already uses, plus in the encrypted off-site copy of the .env file. What you don't want is the key sitting unencrypted in the same bucket as the dump, since that pairing is equivalent to storing the credentials in plain text.

Official rotation: N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION

n8n 2.x has a built-in rotation feature, available on every self-hosted edition and not on n8n Cloud. It doesn't touch N8N_ENCRYPTION_KEY. Instead it introduces a second layer: your instance key becomes a master key whose only job is to protect a set of data encryption keys, which are stored in the database and are what encrypt the credential rows. Rotating means creating a new data encryption key and making it active. The instance key never changes.

Enable the feature and rotate

Take a full database backup first. Then set the flag on main and on every worker and restart all of them:

N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=true

In the editor go to Settings > Data Encryption Keys and select Rotate key. The equivalent API call is a POST to /encryption/keys, which needs the encryptionKey:manage scope. Existing records stay readable under the previous key and n8n re-encrypts each one to the new key the next time that record is updated. There is no documented way to force everything across at once, so a credential nobody has touched since the rotation is still under the old data key, still readable, still protected by the same master key.

One way, no rollback

Enabling the flag changes the storage format and the rotation docs are blunt about the consequences: there's no rollback path, no tool to convert data back to the legacy format, and the only way out is the backup you took before enabling. Don't turn the flag off after anything has been written in the new format, and don't downgrade n8n afterwards because older versions can't read it. n8n 3.0, due in October 2026, turns rotation on by default, so every self-hoster will be on this format within a couple of months of upgrading anyway. I'd enable it now on a test instance, rotate once, restart, confirm a few credentials still work, then do production.

Where this feature does not help: a leaked N8N_ENCRYPTION_KEY. The master key wraps the data keys, so whoever holds the master key plus a database copy can unwrap everything. For that case you still need the manual procedure.

Changing N8N_ENCRYPTION_KEY itself: export, replace, import

This is the pre-2.x method and it's still the only way to replace the instance key. Every command below is an n8n CLI command run inside the container from the host, and it works on any version. The steps:

docker compose exec -u node n8n n8n export:credentials --all --decrypted --output=/home/node/.n8n/creds-plain.json
docker compose down

Now set the new value in .env and change the encryptionKey field in the config file inside the volume to match it (both, or you get the mismatch error). Start n8n and import:

docker compose up -d
docker compose exec -u node n8n n8n import:credentials --input=/home/node/.n8n/creds-plain.json
docker compose exec -u node n8n rm /home/node/.n8n/creds-plain.json

The exported file is every API token, password and OAuth refresh token you own, in plain JSON. Write it inside the volume rather than to a bind mount that gets synced somewhere, delete it the moment the import finishes, and check the workflows that use OAuth credentials afterwards because a refresh token that was mid-cycle during the downtime can need reconnecting. The old article on this page told you to export without --decrypted; that produces an encrypted export that only the old key can open, which is useless for this purpose.

What I haven't measured is how long the import takes on a large instance. Mine has under 60 credentials and the round trip is seconds. With several thousand on Postgres I couldn't tell you, and I'd schedule it in a window either way because the workflows are down between the down and the import.

If the encryption key is lost

Credentials are gone. n8n starts fine with a new generated key, the workflows are all there, the users can log in, the execution history is intact and every node that uses a stored credential fails at runtime with a decryption error until you open that credential and re-enter it. The one exception that isn't obvious: OAuth credentials need a full reconnect through the provider's consent screen, since the refresh token was part of what got encrypted. On an instance with a handful of credentials it's an afternoon. On an agency instance with two hundred client connections it's a week of emails, which is the real cost of skipping the backup section above.

External secrets as the alternative

Enterprise licenses can point n8n at an external secret store, and then the database holds references instead of the secrets themselves. Supported providers as of today are 1Password Connect, AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault and Infisical, with more than one vault per provider allowed since 2.10.0. The instance key still exists and still protects whatever you store directly, but the blast radius of a leaked key or a leaked dump shrinks to the credentials you chose not to move. The vault integration isn't available on a community instance, and the n8n security best practices checklist treats the key as one item on a longer list running from the proxy binding to task runner isolation. On a community instance, the key hygiene in this article is the whole story.

Automate faster, for less

Bring your winning ideas to life with AMD power, NVMe speed and unmetered bandwidth.