Managing Secrets and Credentials in n8n for Enterprise
TL;DR — n8n 1.78 supports external secrets via Vault and Infisical out of the box. Wire it up day one. SSO via Keycloak 26 or Auth0 is also first-party. The real work is the rotation story, automated key rollover with workflow-aware grace periods.
The first n8n deployment at any company stores credentials in Postgres encrypted with N8N_ENCRYPTION_KEY. That works. It satisfies “credentials are not in plaintext.” It does not satisfy “credentials are rotatable”, “credentials are auditable”, “credentials follow our org’s SSO and RBAC story”, or any of the other questions a serious security team will ask. Those questions are the difference between a tool that platform teams use and a tool that gets the rubber-stamp ban from compsec.
This article is the bridge. We’ll wire n8n 1.78 to HashiCorp Vault for secrets, set up Keycloak 26 (or Auth0) for SSO, build a rotation story for credentials that flows through Vault into running workflows, and lay out the audit logging side. The deployment assumes the queue-mode architecture from the advanced n8n architecture article.
Opinions up front. The Postgres-encrypted credential store is fine for development, not production. SSO is mandatory in enterprise, not optional. Static API tokens with no expiry are a security debt you’ll regret. Rotate everything. And the worst credential management strategy is the one where you don’t know what’s stored.
1. The Built-in Credential Encryption
Before the external store, understand what’s there. n8n encrypts credential values at rest in Postgres using N8N_ENCRYPTION_KEY. The encryption is AES-256-GCM. The key is loaded once at process start and held in memory.
+-----------------+
| Credential UI |
+--------+--------+
| save
v
+--------+--------+
| n8n process |
| - AES-GCM enc |
+--------+--------+
| encrypted blob
v
+--------+--------+
| Postgres table |
| credentials_ |
| entity.data |
+-----------------+
The key concerns I have with this:
- The key is a static string in the n8n config. Rotation requires re-encrypting every row.
- If the key leaks, every credential is compromised. There’s no per-secret KEK.
- There’s no audit trail of who accessed which credential when.
- There’s no way to mount a credential that lives in another system.
External secrets fix all four. The built-in encryption stays as the default, and external secrets become the source of truth for sensitive values.
2. Wiring up HashiCorp Vault
n8n 1.78 supports Vault as an external secrets provider. The setup is two parts. Configure Vault auth on the n8n side, then reference Vault paths from credentials.
Vault side
Enable the AppRole auth method and a KV v2 engine.
vault auth enable approle
vault secrets enable -path=n8n -version=2 kv
# Policy: n8n can read /n8n/* but not write
cat > n8n-policy.hcl <<EOF
path "n8n/data/*" {
capabilities = ["read"]
}
path "n8n/metadata/*" {
capabilities = ["list", "read"]
}
EOF
vault policy write n8n-read n8n-policy.hcl
# AppRole bound to that policy
vault write auth/approle/role/n8n \
token_policies=n8n-read \
token_ttl=1h \
token_max_ttl=24h \
secret_id_ttl=720h
vault read auth/approle/role/n8n/role-id # role-id
vault write -f auth/approle/role/n8n/secret-id # secret-id
Drop a secret in:
vault kv put n8n/billing/api-token value=sk-live-xxxxx
n8n side
# enable external secrets, requires self-hosted enterprise or community-built feature
export N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL=300
export N8N_EXTERNAL_SECRETS_PREFER_GET=true
# Vault config (via UI or env)
export VAULT_ADDR=https://vault.acme.internal:8200
export VAULT_ROLE_ID=...
export VAULT_SECRET_ID=...
In the n8n UI, Settings > External Secrets, you add the Vault provider with those values. Once active, you can reference Vault paths from any credential field using ={{ $secrets.vault.billing['api-token'].value }}.
The refresh interval polls Vault every 5 minutes (configurable via N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL). New secrets show up in the picker. Updated secret values flow into running workflows on the next execution, no restart needed.
3. Wiring up Infisical as an Alternative
Some teams prefer Infisical for the developer-friendly UI. n8n 1.78 supports it natively too.
export N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL=300
# Infisical config
export INFISICAL_HOST=https://app.infisical.com
export INFISICAL_TOKEN=st.xxxx
export INFISICAL_PROJECT_ID=...
export INFISICAL_ENVIRONMENT=production
The reference syntax in credentials is ={{ $secrets.infisical.BILLING_API_TOKEN }}. The flat namespace is convenient for small projects. For larger orgs, the Vault hierarchy plays better with policy and RBAC.
Pick one. Don’t run both, you’ll end up with secrets in two places and a real chance one of them goes stale.
4. SSO with Keycloak 26
n8n 1.78 supports SAML and OIDC SSO. For a self-hosted enterprise, Keycloak 26 with OIDC is the cleanest path.
Keycloak side
In Keycloak, create a realm (or use existing), create an OIDC client.
Client ID: n8n
Client authentication: ON (confidential)
Standard flow: ENABLED
Direct access grants: DISABLED
Root URL: https://n8n.acme.internal
Valid redirect URIs: https://n8n.acme.internal/rest/sso/oidc/callback
Web origins: https://n8n.acme.internal
Grab the client secret from the Credentials tab. Configure a group mapper that puts the user’s group membership into a groups claim.
n8n side
export N8N_USER_MANAGEMENT_JWT_SECRET=long-random-string
export N8N_SSO_ENABLED=true
export N8N_SSO_OIDC_ENABLED=true
export N8N_SSO_OIDC_DISCOVERY_ENDPOINT=https://keycloak.acme.internal/realms/acme/.well-known/openid-configuration
export N8N_SSO_OIDC_CLIENT_ID=n8n
export N8N_SSO_OIDC_CLIENT_SECRET=...
Users authenticate via the Keycloak login on the n8n sign-in page. n8n provisions the user record on first login (just-in-time provisioning).
Mapping groups to roles
n8n has a role model with global:owner, global:admin, global:member. To map Keycloak groups to n8n roles, you need the group claim and a small mapping. n8n 1.78 doesn’t fully auto-map from OIDC group claims yet. The workaround is a periodic sync via the n8n REST API.
# Sync script (cron, or n8n workflow)
curl -s -H "Authorization: Bearer $N8N_API_TOKEN" \
https://n8n.acme.internal/api/v1/users | \
jq -r '.data[] | select(.email | endswith("@acme.com")) | .id' | \
while read id; do
curl -s -X PATCH -H "Authorization: Bearer $N8N_API_TOKEN" \
-H "Content-Type: application/json" \
https://n8n.acme.internal/api/v1/users/$id/role \
-d '{"newRoleName":"global:member"}'
done
For finer-grained access including per-workflow permissions, the n8n RBAC model on the enterprise tier supports it directly. The official n8n SSO docs walk through both SAML and OIDC.
5. SSO with Auth0
For SaaS orgs already on Auth0, the wiring is similar. Create an Application in Auth0 (Regular Web App), enable OIDC, add the callback URL.
export N8N_SSO_OIDC_ENABLED=true
export N8N_SSO_OIDC_DISCOVERY_ENDPOINT=https://acme.us.auth0.com/.well-known/openid-configuration
export N8N_SSO_OIDC_CLIENT_ID=...
export N8N_SSO_OIDC_CLIENT_SECRET=...
Add a Rule or Action that injects role information into the ID token claims. Auth0 has better off-the-shelf MFA and adaptive auth than Keycloak, which matters for security teams that want device trust and step-up.
6. Credential Rotation Without Downtime
This is the part most teams skip. They wire up Vault, then leave the credential static for two years. That’s better than no Vault, but it’s not what Vault is for.
The pattern is dual-keyed credentials with overlapping validity.
+-----------------------+
| Vault |
| n8n/billing/api-token |
| value: v2-current |
| prev: v1-grace |
+-----------+-----------+
|
+-----------v-----------+
| n8n |
| reads value or prev |
+-----------+-----------+
|
+-----------v-----------+
| Billing API |
| accepts v1 and v2 |
| during grace window |
+-----------------------+
Steps:
- Generate v2 in the destination system (Billing API). Keep v1 active.
- Write v2 to Vault under
value, move v1 toprev. - Wait one refresh interval (5 minutes) for n8n to pick up the new value.
- Verify the next execution uses v2. Monitor for v1 usage in the destination’s audit logs.
- After 24 hours of no v1 usage, revoke v1 at the destination.
Most APIs (Stripe, Auth0, modern internal systems) support multiple active tokens per service account. For those that don’t, the rotation requires a brief overlap window where workflows are paused.
A small n8n workflow can drive this rotation: trigger on a Vault secret-write webhook, validate the new secret with a test API call, alert on failure.
7. Audit Logging
Every credential access should be auditable. n8n 1.78 ships a basic audit log to the database, but the rich view comes from Vault’s own audit device.
vault audit enable file file_path=/var/log/vault/audit.log
Each request to Vault is logged with the requesting AppRole, the secret path, and the timestamp. Ship this to your SIEM. The query that matters is “list all secrets accessed by n8n in the last 24 hours” and “list any access to credential X.”
On the n8n side, enable the audit log feature.
export N8N_AUDIT_LOGS_ENABLED=true
export N8N_AUDIT_LOGS_DESTINATION=file
export N8N_AUDIT_LOGS_FILE_PATH=/var/log/n8n/audit.log
The combination of Vault’s secret-access log and n8n’s workflow-execution log is what auditors want. The schemas don’t line up by default. I usually pipe both through a small Logstash or Vector pipeline that adds a common correlation_id field per execution.
Common Pitfalls
Four traps.
Storing the Vault AppRole secret-id in a plain file on the n8n host. Defeats half the point of Vault. Use a Kubernetes secret (mounted at runtime), or better, Vault’s Kubernetes auth method that binds to the pod’s service account. Static AppRole secrets are a stop-gap.
Using the same encryption key across environments. If dev has the prod key, a dev incident becomes a prod incident. Different N8N_ENCRYPTION_KEY per environment, rotated when team members leave.
Skipping the refresh on credential rotation. You rotate the secret in Vault but the running workflows still use the cached old value because the refresh interval is set to 1 day or unset. Set N8N_EXTERNAL_SECRETS_UPDATE_INTERVAL to 5 minutes and verify with a manual rotation.
Treating SSO as a check-the-box for compliance without RBAC. Everyone has global:owner because that was the default at install. Audit your user list, downgrade most to global:member, and lock administrative actions to a small group. The default isn’t safe.
Troubleshooting
Three failure modes.
n8n can’t reach Vault and silently uses cached values. Outage in Vault means workflows keep running with the last-known secret. Eventually the cache expires and credentials fail with cryptic errors. Set up alerting on Vault unreachability separately from credential failure alerts.
SSO works but new users get global:owner. The default role for SSO-provisioned users is owner unless N8N_SSO_OIDC_LOGIN_LABEL and the group claim mapping are configured. Set the default role explicitly via the API after first login or use the JIT provisioning hooks in enterprise.
Credentials test passes but workflows fail with 401. The credential is correct but the API token was revoked at the source between the test and the workflow execution. The rotation didn’t complete the cleanup. Look at the source system’s audit log to confirm the token is revoked, then update the Vault path with a fresh one.
Wrapping Up
Enterprise n8n means credentials live in Vault or Infisical, not in n8n’s database. SSO routes through Keycloak or Auth0, not n8n’s local users. Rotation is automated with a grace window. And audit logs from both Vault and n8n flow to a SIEM that the security team actually reads.
The next article in this series gets into error handling and retries for production workflows, because all the auth and persistence in the world doesn’t help if your workflows can’t survive a transient API blip. After that we’ll get into the Kubernetes packaging for the whole stack.