n8n environment variables vs credentials look like the same kind of thing from a distance: both hide a value from the workflow canvas, and both eventually connect a workflow to something external. They are not the same system, and n8n’s own documentation treats them very differently. An environment variable configures the n8n instance itself, set at the server or container level, and only takes effect on the next startup. A credential authenticates one connection to one external service, created inside n8n’s UI, encrypted in n8n’s own database, and editable at runtime without restarting anything. The confusion shows up at the edges: a team that stores an OAuth client secret through n8n’s environment variable credential overwrite, when n8n’s own documentation explicitly warns against exactly that approach, or a team that pastes a vault reference into a node’s regular parameter field and finds it silently does nothing, because that reference only resolves inside a credential field.
n8n Environment Variables vs Credentials: Two Different Storage Systems
Per n8n’s own basic configuration documentation, the mechanism behind the first system is stated in one line: “You can change n8n’s settings using environment variables.” Every N8N_... prefixed variable, plus a handful of unprefixed ones for database and deployment config, falls into this category: N8N_ENCRYPTION_KEY, N8N_BLOCK_ENV_ACCESS_IN_NODE, N8N_INSTANCE_OWNER_EMAIL, DB_POSTGRESDB_PASSWORD, and similar. These are read once, when the n8n process starts, from wherever the hosting environment provides them: an export command, a Docker -e flag, a docker-compose.yaml environment block, or a hosting platform’s own environment variable panel.
The second system is described just as plainly in n8n’s documentation on creating and editing credentials: “Credentials are securely stored authentication information used to connect n8n workflows to external services such as APIs, or databases.” A credential is created inside the n8n UI itself, not on the server, and it authenticates one connection, not the whole instance: a single API key for a single Slack workspace, a single OAuth token for a single Google account, a single database login. n8n also lets a workflow set credential field values dynamically at runtime; its own documentation confirms “you can use expressions to set credentials dynamically as your workflow runs”, toggled per field from inside the credential modal, a capability environment variables simply don’t have, since an environment variable’s value is fixed for the life of the process.
Where n8n Environment Variables Live and What They Control
Environment variables apply to the entire n8n instance, not to a single workflow or a single user. Setting N8N_TEMPLATES_ENABLED=false or N8N_BLOCK_ENV_ACCESS_IN_NODE=true changes that setting for every workflow on that instance, for every project, for every user, with no per-workflow override. Changing one also means changing infrastructure, not clicking a button in the editor. That restart requirement holds for ordinary configuration variables as standard process behaviour, and n8n’s own documentation makes the same read-once-at-startup pattern explicit for one specific group of settings: its documentation on managing settings from environment variables describes an activation variable that “n8n reapplies the settings from environment variables on every startup,” and notes that when the matching flag is off, “n8n ignores the related environment variables, even if you set them.” In practice, editing an environment variable and restarting or redeploying the n8n process are the same action; there is no live reload path from the UI.
That instance-wide reach cuts the other way too, and it’s the part teams most often miss. Per n8n’s own security environment variable reference, N8N_BLOCK_ENV_ACCESS_IN_NODE defaults to false, and its description says exactly what that means: “Whether to allow users to access environment variables in expressions and the Code node (false) or not (true).” Read literally, the default lets any user who can edit an expression or open the Code node read every environment variable on the instance through $env, which n8n’s own metadata reference confirms “contains n8n instance configuration environment variables” and is available inside the Code node. A database password or an SSO client secret set as an environment variable for infrastructure reasons is not automatically hidden from workflow builders; it stays readable until an admin deliberately sets that variable to true.
Where n8n Credentials Live and What They Control
Credentials take the opposite shape on every axis. n8n’s own documentation on setting a custom encryption key explains what actually protects them: “n8n creates a random encryption key automatically on the first launch and saves it in the ~/.n8n folder. n8n uses that key to encrypt the credentials before they get saved to the database.” That key is itself an environment variable, N8N_ENCRYPTION_KEY, which is the one legitimate place the two systems overlap: an environment variable protects the database where credentials live, but the credential values themselves are not environment variables. In queue mode, the same documentation notes, “you must specify the encryption key environment variable for all workers,” since every worker decrypting a credential needs the same key.
Credentials are also scoped narrower than an environment variable ever is. Per n8n’s own footnote on projects in its credentials documentation, “n8n projects allow you to separate workflows, variables, and credentials into separate groups for easier management,” so a credential created inside one project isn’t automatically visible to every workflow on the instance the way an environment variable is. And because a credential lives in the database rather than the process environment, changing it, rotating an API key, updating an expired token, takes effect the moment it’s saved, with no restart and no infrastructure access required, just permission to edit that credential in the UI.
| Aspect | Environment variable | Credential |
|---|---|---|
| Where it’s set | Server, container, or hosting platform config | n8n’s own UI (Create menu, Overview or project page, or inline on a node) |
| Where it’s stored | The host or container’s process environment | n8n’s database, encrypted with N8N_ENCRYPTION_KEY |
| Who can change it | Whoever has server or infrastructure access | Any user with edit permission on that credential |
| When a change takes effect | Next instance startup or restart | Immediately, no restart |
| Scope | The whole n8n instance | A single credential, shared or project restricted |
| Typical use | Encryption key, feature flags, SSO client details | API keys, OAuth tokens, database logins for a node |
The $secrets Trap: Why External Secrets Only Resolve in Credential Fields
n8n does have a documented bridge between an external vault and a credential: external secrets. Per n8n’s own external secrets documentation, availability is limited to “n8n Cloud: Enterprise” and “Self-hosted: Enterprise” only. Once a vault (AWS Secrets Manager, Azure Key Vault, GCP Secrets Manager, HashiCorp Vault, 1Password Connect Server, or Infisical) is connected, a credential field can reference a secret with an expression: {{ $secrets.<vault-name>.<secret-name> }}. n8n’s own expression reference confirms what $secrets actually returns: “The secrets from an external secrets vault, if configured. Secret values are never displayed to the user. Only available in credential fields.”
That last sentence is the trap. The external secrets documentation states it from the other direction too: “Credentials stored in an external secrets store only resolve in credential fields, not in any other fields supporting expressions.” A {{ $secrets.production.api-key }} expression works perfectly when typed into a credential’s password field, and does nothing, silently, when pasted into a normal node’s parameter field, an HTTP Request node’s URL, or a Set node’s value, because those are exactly the “other fields” the documentation rules out. n8n’s documentation doesn’t describe an error for this specific mismatch; the expression simply does not resolve the way it does inside a credential.
Where Teams Get This Wrong
The first common mistake is loading OAuth client secrets through the CREDENTIALS_OVERWRITE_DATA environment variable because it looks like the fastest way to pre-configure credentials globally. n8n’s own documentation describes exactly what that variable is for, that “credential overwrites let you set credential data globally… to enable OAuth login using a ‘Connect’ button without exposing client secrets,” but it also warns directly against the environment variable route into it: “This approach isn’t recommended. Environment variables aren’t protected in n8n, so the data can leak to users.” The documented alternative is a REST endpoint instead: “the recommended approach is to load the data using a custom REST endpoint,” set via CREDENTIALS_OVERWRITE_ENDPOINT and sent the overwrite payload directly, keeping the secret out of the process environment entirely.
The second common mistake is assuming an environment variable is automatically hidden from anyone who can edit a workflow. By default, N8N_BLOCK_ENV_ACCESS_IN_NODE is false, which n8n’s own documentation confirms means environment variable access “in expressions and the Code node” is allowed, not blocked. A database password or an internal API key set as an environment variable for legitimate infrastructure reasons is readable through $env by any workflow editor on that instance until an admin sets that variable to true, and most self-hosted instances never touch it.
The third common mistake is treating $secrets like a general-purpose expression variable and pasting a vault reference into a node’s own parameter field instead of a credential field, expecting it to resolve the same way $json or $vars would. It doesn’t: n8n’s own documentation is explicit that external secrets resolve only inside credential fields, and only on an Enterprise plan in the first place, so a Community or Pro instance can’t use $secrets there at all, in any field.
The fourth common mistake is reaching for an environment variable to hold a value that genuinely belongs to a single connection, such as one client’s API key in a multi-tenant setup. An environment variable is instance-wide by nature: changing it changes it for every workflow reading it, and doing so needs server or container access plus a restart. A credential scoped to a specific project, editable in the UI the moment a key rotates, with no infrastructure access needed, is the documented shape for exactly that kind of per-connection, per-service value.
Related Reading
For the wider configuration and setup work these variables and credentials sit inside, see n8n Consultancy. For the CRM side these credentials often connect to, see HubSpot Consultancy. For the broader integration pattern most n8n and HubSpot builds are based on, see HubSpot n8n Integration Guide.
Go deeper: Advanced n8n Webhook Listeners for Real-Time SaaS and RevOps Automation · Advanced n8n Error Handling Strategies for Resilient SaaS Workflows · n8n vs Zapier RevOps Automation
Frequently Asked Questions
Should I store an API key as an n8n environment variable or a credential?
As a credential in almost every case. n8n’s own documentation on credential overwrites warns directly against putting credential data in an environment variable, since “environment variables aren’t protected in n8n, so the data can leak to users.” A credential is encrypted in n8n’s database, editable without a restart, and can be scoped to a single project rather than the whole instance.
Why doesn’t my $secrets expression work outside a credential field?
Because n8n’s own documentation states plainly that external secrets “only resolve in credential fields, not in any other fields supporting expressions.” The same reference confirms $secrets itself is “only available in credential fields.” Pasting a vault reference into a node’s regular parameter field, rather than a credential’s field, will not resolve it, and n8n’s documentation doesn’t describe an error message for this case.
Can other n8n users read my environment variables?
By default, yes, inside expressions and the Code node. N8N_BLOCK_ENV_ACCESS_IN_NODE defaults to false, which n8n’s own documentation confirms allows environment variable access there. Any value set as an environment variable, including one intended only for infrastructure, stays readable through $env to workflow builders until an admin explicitly sets that variable to true.
Does changing an n8n environment variable take effect immediately?
No. n8n’s own documentation on managing settings from environment variables confirms it “reapplies the settings from environment variables on every startup,” meaning a changed value only takes effect the next time the n8n process restarts. A credential change, by contrast, applies the moment it’s saved in the UI, with no restart involved.
Are n8n’s custom Variables the same as environment variables?
No, they’re a third, separate system. n8n’s own documentation describes custom variables as “read-only variables that you can use to store and reuse values in n8n workflows,” created and edited in the UI rather than the server, and available only on Cloud Pro and Enterprise or self-hosted Business and Enterprise plans, unlike environment variables.
Can Community edition n8n use external secrets?
No. n8n’s own documentation states external secrets availability as “n8n Cloud: Enterprise” and “Self-hosted: Enterprise” only. A Community or Pro instance cannot connect a vault or use $secrets in any field, including credential fields, so a plain credential or the CREDENTIALS_OVERWRITE_ENDPOINT route is the only option available.