“Secure your n8n credentials” sounds like a single task. It is three. A self-hosted n8n instance holds three different kinds of secret, and they have nothing in common except the word “key” in their names. They are stored differently, reached differently, and rotated differently. Advice that does not say which one it means is, at best, ambiguous, and at worst points you at a feature that does not exist.
This guide keeps the three apart and walks each on its own terms, for operators running self-hosted n8n in production on a mix of Community and paid tiers. The facts here are anchored to n8n 2.23.x, the current stable line as of June 2026. Where a control is paid-only, it is marked inline, because a large share of self-hosters run Community edition and deserve to know which advice they can act on.
Three secrets people conflate
Before anything else, separate these:
- The n8n public API key. Authenticates calls to n8n’s own REST API. A user issues it, it is scoped at the instance level, and you rotate it by deleting and recreating it.
- The encryption key (
N8N_ENCRYPTION_KEY). Protects every stored credential at rest. One value per instance. Historically un-rotatable; now rotatable through a dedicated feature. - The stored third-party credentials. The actual OAuth tokens, API keys, and passwords your workflows use. Encrypted in the database, scoped through RBAC and projects, and rotated by OAuth refresh, by hand, or by an external vault.
Everything below hangs on this split. When a guide tells you to “rotate your n8n credentials,” ask which of the three it means, because the answer changes the entire procedure.
How n8n stores credentials
When you save a credential, n8n encrypts it with the instance encryption key and writes it to the configured database. The default store is SQLite; in production you run Postgres. Credentials never sit in plaintext in the database.
Storage is also display-aware. Once you save a credential, the editor does not show you the stored secret values again. Reopen an API-key credential and the field is masked or blank, not refilled with the key. Users who can use a shared credential cannot read or edit its underlying details. This is the behavior you should expect, and it is why “just look it up in the UI” is not a recovery path for a lost secret.
There is an anti-pattern that defeats all of this: pasting a secret directly into an HTTP Request node or a Code node instead of attaching a credential. A hardcoded secret bypasses the encrypted store entirely. From there it leaks through workflow exports, through execution data, and through any database backup, all in plaintext. If you want one rule from this section, it is: secrets go in credentials, never in node parameters.
If you do not set an encryption key, n8n generates a random one on first launch and writes it to the ~/.n8n directory. That is fine for a single-container test instance and a problem in production, where you want the key under your own control. Set it explicitly:
export N8N_ENCRYPTION_KEY=REPLACE_WITH_STRONG_RANDOM_STRINGIn queue mode, every instance, the main process and all workers, must share the identical N8N_ENCRYPTION_KEY. A worker that boots with a different key cannot decrypt the credentials a workflow needs, and executions fail at the point of use rather than at startup. A compact example of pinning the same key across main and worker, sourced from a file rather than an inline value:
services: n8n-main: image: n8nio/n8n:2.23.4 environment: - N8N_ENCRYPTION_KEY_FILE=/run/secrets/n8n_encryption_key - EXECUTIONS_MODE=queue secrets: - n8n_encryption_key
n8n-worker: image: n8nio/n8n:2.23.4 command: worker environment: - N8N_ENCRYPTION_KEY_FILE=/run/secrets/n8n_encryption_key - EXECUTIONS_MODE=queue secrets: - n8n_encryption_key
secrets: n8n_encryption_key: file: ./secrets/n8n_encryption_key.txtThe _FILE suffix tells n8n to read the value from the named file rather than the literal environment variable, which keeps the key out of docker inspect output and shell history. Watch for trailing whitespace in the file: a stray newline produces a different key than you intended, and you are back to the silent decrypt failure above. The example targets n8nio/n8n:2.23.4; adjust the tag to your pinned version.
Reference: Set a custom encryption key.
The encryption key, and how to rotate it
N8N_ENCRYPTION_KEY is a single instance-wide key that gates access to every credential you store. For a long time it could not be rotated without exporting all credentials, swapping the key, and re-importing, which meant downtime and risk. n8n now ships a proper rotation feature. It works through a two-layer key model; understand the model before you enable anything, because every constraint below follows from it.
Once the feature is on, the instance key (N8N_ENCRYPTION_KEY) no longer encrypts credential data directly. Instead it protects a data encryption key, which n8n stores encrypted in the database and which does the actual credential encryption. The instance key never changes. The data key is the thing you rotate. When you rotate, n8n generates a new data key and uses it for all future writes. Records encrypted with the previous key stay readable, and n8n silently re-encrypts each one to the new key the next time you update that record.
Enable it on every instance, main and workers, then restart:
export N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION=trueOn startup n8n generates the initial data key. You then rotate from Settings > Data Encryption Keys > Rotate key, or through the API by posting to /encryption/keys with the encryptionKey:manage scope. n8n never returns key material in API responses, only metadata.
The constraints are strict and one-directional, so read them before you touch production:
Warning: Enabling encryption key rotation is a one-way change with no rollback. n8n begins writing data in a new format that older versions and instances without the flag cannot read. Do not disable the flag after any data has been written in the new format, or that data becomes permanently inaccessible. Do not downgrade your n8n version. Take a full database backup first, enable on staging, verify decryption, and only then enable on production. The feature is self-hosted only and only the instance owner can enable it.
Note what this feature does and does not do. It rotates the key that protects your credentials. It does not rotate the credentials themselves. Those are different secrets, and the next sections cover them.
Reference: Encryption key rotation. If your identity provider supports it, there is also a separate, narrower preview feature for JWE token decryption of OAuth 2.0 credentials, available from 2.21.0; it is out of scope here.
Scoping, layer one: RBAC and projects
Who can reach a stored credential is governed by two orthogonal ideas that n8n keeps deliberately separate.
Account types are instance-wide: Owner, Admin, Member. Every account has exactly one. Project roles apply per project: Admin, Editor, Viewer, plus custom roles. A single account can hold different project roles in different projects. Projects are the container that groups workflows and credentials together. The full two-axis model is walked end to end in the permissions deep-dive; this section needs only the parts that touch credentials.
The practical permissions for the built-in project roles:
- Project Admin: full control of the project, including members and settings, plus create, read, update, and delete on every workflow and credential in it.
- Project Editor: create, read, update, delete, and execute every workflow and credential in the project, but cannot manage members or the project itself.
- Project Viewer: read-only across workflows, credentials, and executions. A Viewer cannot manually execute workflows.
The edition gating matters and is easy to get wrong:
- RBAC and projects are available on every plan except Community.
- Project Editor requires Pro Cloud or self-hosted Enterprise.
- Project Viewer requires self-hosted Enterprise or Cloud Enterprise.
- Custom roles, which let instance owners and admins assign granular credential scopes (view, edit, create, share, unshare, transfer, delete), require Enterprise. They were introduced in n8n 1.122.0.
References: RBAC role types, RBAC projects, Custom project roles.
Scoping, layer two: the credential-sharing trap
n8n follows least privilege, with one sharp exception that deserves its own section because it surprises people.
Sharing a workflow grants its editors the use of every credential that workflow references, including credentials that were never explicitly shared with them. An editor cannot open and read those credentials’ secret values, and cannot edit nodes that use credentials they lack, but they can run the workflow, which uses the credentials. In other words, workflow access is an implicit credential-use grant. If you share a workflow that talks to your production database, you have effectively handed the recipient the ability to talk to your production database.
A second trap sits next to the first. Moving a workflow or credential between projects or users revokes all prior individual sharing. A move that looks like simple housekeeping can silently break a workflow if the destination project does not already hold the credentials it needs. n8n warns you at move time, and when you move a workflow you can choose to bring along the credentials you have permission to share, but the default mental model of “moving is harmless” is wrong.
Both credential sharing and workflow sharing are paid features: credential sharing is available on all Cloud plans and on Business and Enterprise self-hosted, and workflow sharing on Pro and Enterprise Cloud and Enterprise self-hosted. Community operators do not have multi-user sharing at all, which sidesteps this trap but also removes the collaboration it enables. Where you are separating whole teams or clients rather than individual users, roles stop being the right tool at some point - the RBAC and tenant isolation piece covers that boundary.
References: Workflow sharing, Credential sharing, and the “Moving revokes sharing” warning in RBAC projects.
Scoping, layer three: the n8n public API key
The third secret is its own surface. The n8n public API key authenticates calls to n8n’s own REST API, sent in an X-N8N-API-KEY header. You create one under Settings > n8n API with a label and an expiration, and you rotate it by deleting it and creating a new one. There is no in-place rotation; delete-and-recreate is the procedure, and setting an expiration is how you bound a key’s lifetime in advance.
curl -X GET \ 'https://YOUR_INSTANCE/api/v1/workflows?active=true' \ -H 'accept: application/json' \ -H 'X-N8N-API-KEY: REPLACE_WITH_YOUR_API_KEY'The important security fact: scopes that limit what a key can touch are Enterprise-only. On any non-Enterprise plan, an API key has full access to the account’s resources. There is no middle ground of a read-only key on Community or Pro; the key can do everything the account can. Treat a non-scoped key as equivalent to the account itself.
API-key scopes are also a different system from project role scopes. API-key scopes act at the instance level on the public API. Project role scopes act inside a project on the UI and internal operations. They share naming conventions but are not the same mechanism, and a permission granted in one does not carry to the other.
If you do not use the public API, turn it off:
export N8N_PUBLIC_API_DISABLED=trueReferences: API authentication, Disable the public REST API.
Pushing secrets outside n8n: external secrets
The strongest control is to not hold the secret at all. The external secrets feature lets you store the real value in a dedicated vault and reference it from a credential through a $secrets expression, so n8n resolves it at runtime instead of storing it:
{{ $secrets.VAULT_NAME.SECRET_NAME }}Supported providers are 1Password (through a self-hosted Connect Server), AWS Secrets Manager, Azure Key Vault, GCP Secrets Manager, and HashiCorp Vault. From n8n 2.10.0 you can connect multiple vaults per provider; before that, one per provider. Note two deprecations and limits worth knowing: Infisical is deprecated and cannot be newly connected from 2.10.0, and n8n’s external secrets support plaintext values only, not JSON objects.
Vaults can be global or scoped to a single project. Project-scoped vaults arrived in 2.11.0. By default, in personal projects only instance owners and admins can use secrets from global vaults. From 2.13.0, instance owners and admins can flip on Enable external secrets for project roles, after which project editors can use a project’s secrets in credentials and project admins can also manage the project’s vaults. Before 2.13.0, or with that toggle off, only owners and admins can resolve secrets at runtime, which produces a classic failure: a secrets expression that previews fine for an admin but fails in production when it runs as another user.
This is the feature that gives you honest rotation. The vault owns the secret’s lifecycle, and n8n always reads the current value, so rotating in the vault rotates everywhere n8n uses it, with no n8n-side step.
External secrets is an Enterprise feature, available on Enterprise self-hosted and Enterprise Cloud. Community operators do not have it, and should look instead at the companion approach in the article on managing secrets in self-hosted n8n without Vault, which covers the controls that do exist on Community.
Reference: External secrets.
Rotation: the three real stories
Here is the correction the word “rotation” needs: n8n has no native scheduler that rotates the third-party API keys and tokens you store. There is no setting that re-issues your Stripe key every 90 days. What exists is four separate things, only some of which are rotation in the sense people mean:
- OAuth credentials rotate themselves, partly. Access tokens refresh automatically using the stored refresh token - in the standard flow. Two failure modes break that assumption: the refresh triggers on an HTTP 401, so a provider that signals an expired token with a 403 instead never triggers it (#18517, closed as not planned), and some providers never return a refresh token for n8n to store in the first place (#17212). Refresh tokens can also expire under provider-specific rules, but that is the provider’s behavior, not an n8n feature, and the intervals vary too much to state generally. Treat refresh-token expiry as something to read in each provider’s docs.
- Static secrets, API keys and passwords, do not rotate. There is no native mechanism. You either edit the credential by hand or move the secret to an external vault that rotates it for you.
- The encryption key rotates through its own feature, covered above, which is unrelated to rotating the secrets it protects.
- The encryption key and external vaults aside, operational rotation without breakage is a pattern, not a feature. Create a new versioned credential alongside the old one, point a single canary workflow at it, confirm it works, then roll the rest of the workflows forward and retire the old credential. This lets you rotate a static secret while workflows keep running. It is field practice you implement, not a button n8n offers.
To find what needs rotating or retiring, run the security audit. Its credentials report surfaces three categories, in n8n’s own wording: credentials not used in any workflow, credentials not used in any active workflow, and credentials not used in recently executed workflows. “Recently” is governed by N8N_SECURITY_AUDIT_DAYS_ABANDONED_WORKFLOW, which defaults to 90 days. Run the audit from the CLI, the public API, or the n8n node:
n8n auditReferences: Security audit, External secrets, Encryption key rotation.
When a key leaks: incident response
The whole storage model rests on one assumption: that nobody untrusted can read N8N_ENCRYPTION_KEY. Anyone who can read it can decrypt every credential in the database. That is not theoretical. In early 2026, CVE-2026-25049, a critical expression-escape flaw, let an authenticated user with workflow-edit permission run system commands on the host. Host access means reading the encryption key from the environment and decrypting the entire credential store. It was fixed in n8n 1.123.17 and 2.5.2.
A separate, credential-specific flaw, CVE-2026-33663, affected Community edition only: a member-role user could steal plaintext secrets from generic HTTP credentials belonging to other users by abusing a name-based resolution path that did not enforce ownership. Native integration credential types were not affected, and Enterprise had independent permission gates that blocked the chain. It was fixed in 1.123.27, 2.13.3, and 2.14.1. The lesson is the one this whole article has been building: on Community, the scoping layers that would have contained this simply are not there.
On suspected exposure of the encryption key or a credential, the response is layered, and order matters:
- Patch first. Upgrade to a fixed version before anything else, or you are cleaning up while the door is still open.
- Rotate the encryption key, now feasible with the rotation feature, so a leaked key no longer decrypts future writes.
- Rotate the affected third-party secrets at their source. This is the step that closes the exposure, because the leaked values are already out. Re-issue the API keys, reset the passwords, revoke the OAuth grants at the provider.
- Tighten editor permissions to shrink the blast radius for next time: fewer people with workflow-edit rights, scoped API keys where you have Enterprise, least-privilege project roles.
Steps two and three are not interchangeable. Rotating the encryption key protects new data; it does nothing for secrets an attacker already exfiltrated. Only rotating at the source does that.
Reference: Securing n8n for the recommended controls, and the linked advisories for the specific fixes.
Decision matrix and operator checklist
The three secrets at a glance:
| Secret | Where it lives | Who can reach it | How it rotates | Edition |
|---|---|---|---|---|
| n8n API key | User-issued, sent as X-N8N-API-KEY | Instance-level; full access unless scoped | Delete and recreate; set an expiration | All tiers; scopes Enterprise-only |
| Encryption key | Env var or ~/.n8n; protects a DB-stored data key | Anyone with host or env access | Encryption key rotation feature | Self-hosted; owner-only |
| Stored credential | Encrypted in the database | RBAC, projects, sharing | OAuth auto-refresh, manual, or external vault | All tiers store; scoping and vaults paid |
Control availability by edition:
| Control | Community | Pro | Enterprise |
|---|---|---|---|
| RBAC and projects | No | Yes | Yes |
| Project Editor role | No | Pro Cloud only | Yes |
| Project Viewer role | No | No | Yes |
| Custom roles | No | No | Yes |
| API key scopes | No | No | Yes |
| External secrets | No | No | Yes |
| Credential sharing | No | Cloud yes; self-hosted needs Business | Yes |
| Encryption key rotation | Self-hosted, owner-only | Self-hosted, owner-only | Self-hosted, owner-only |
| Security audit command | Yes | Yes | Yes |
The operator checklist:
- Set an explicit
N8N_ENCRYPTION_KEYand back it up somewhere you can recover it. Without it you cannot decrypt your own credentials. - Never hardcode a secret in an HTTP Request or Code node. Use a credential.
- Keep one encryption key across all queue-mode workers, and watch for trailing whitespace in any
_FILEsource. - Scope API keys where you have Enterprise, and set expirations regardless.
- Apply least privilege through projects and roles. Remember that sharing a workflow grants use of its credentials.
- Move static secrets into an external vault wherever you have it, so the vault owns rotation.
- Run
n8n auditon a schedule to surface stale credentials, then rotate or delete them. - Keep a written leak-response drill: patch, rotate the encryption key, rotate secrets at the source, tighten permissions.