n8n Environment Variables: The Production Setup That Doesn't Leak Secrets

Sean

Platform Writer

Jun 09, 2026
11 min read

n8n environment variables are how you keep API keys, database credentials, and host config out of your workflow JSON. The simplest production pattern is a single .env file consumed at container start, a clear rule that secrets stay in the host environment and never live in workflow nodes, and a deployment surface that lets you rotate a credential without redeploying the workflow.

If you have ever opened a workflow and seen an API key in plaintext in a HTTP Request node, this post is the cleanup. If you are about to ship n8n to production, it is the setup that will save you from doing that in the first place.

n8n environment variables: the production setup that does not leak secrets

Table of contents

The direct answer

Create a .env file in the directory you start n8n from, set the variables you need (database URL, encryption key, host, port, webhook URL, executor settings), and start n8n with n8n start or via Docker. n8n reads the file at boot. The variables control everything from the database connection to the encryption key that protects stored credentials.

For Docker:

docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  --env-file .env \
  -v n8n_data:/home/node/.n8n \
  n8nio/n8n

For a self-hosted install on a VM:

n8n start

The --env-file flag (or the .env file in the working directory) is the only piece most guides skip, and it is the most important one. n8n does not load .env automatically the way a Next.js or Rails app does. You wire it up explicitly with Docker, or with a process manager (PM2, systemd, supervisord) that exports the variables before launching n8n.

The rest of this post is the variables that matter, the variables that look important but are usually left at defaults, and the production patterns that hold up when a credential leaks at 2am.

The three places n8n reads config from

It helps to know where n8n looks for configuration, in order of precedence:

  1. Process environment. Variables exported in the shell, set in the container, or loaded by the process manager. Highest priority.
  2. .env file in the working directory. Loaded by Docker with --env-file, or by your process manager with a dotenv loader. Same priority as the process environment in most setups.
  3. n8n’s internal database. Workflow-level settings, credential storage (encrypted), and execution history. This is not a config source in the same sense — it is where n8n stores what it learned from running.

When in doubt, check the process environment. n8n does not have a hidden config file or a “magic” defaults location. If a variable is set in the container and a workflow is not picking it up, the workflow is probably reading from the credential store, not the environment.

The .env file that actually works

A minimal production .env for self-hosted n8n with Postgres:

# Host
N8N_HOST=automation.example.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://automation.example.com/

# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=[REDACTED]

# Encryption
N8N_ENCRYPTION_KEY=replace-with-a-random-32-byte-base64-string

# Execution
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
N8N_METRICS=true

# Security
N8N_SECURE_COOKIE=true
N8N_BLOCK_ENV_ACCESS_IN_NODE=true
N8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n/files

# Logging
N8N_LOG_LEVEL=info
N8N_LOG_OUTPUT=console,file
N8N_LOG_FILE_LOCATION=/home/node/.n8n/logs/n8n.log

The variables worth flagging:

  • N8N_ENCRYPTION_KEY encrypts the credentials stored in the n8n database. If you lose it, every stored credential is unreadable. If it leaks, every stored credential is readable. Treat it like a database root password.
  • WEBHOOK_URL is the public URL n8n advertises to webhook callers. If it is wrong, inbound webhooks break silently.
  • N8N_BLOCK_ENV_ACCESS_IN_NODE=true is the security flag most installs forget. It stops a Code node from reading the host environment, which means a leaked workflow or a malicious npm package inside a Code node cannot exfiltrate N8N_ENCRYPTION_KEY or your database password.
  • N8N_RESTRICT_FILE_ACCESS_TO limits where the Read/Write File nodes can touch. Without it, a Code node can read /etc/passwd.

Secrets inside workflows: the right way

The single most common n8n security mistake is putting secrets in workflow nodes. The pattern usually looks like this:

  1. Engineer adds an HTTP Request node.
  2. Engineer pastes the API key into the header.
  3. Engineer exports the workflow JSON.
  4. Engineer commits the JSON to git.
  5. Credential is in git history forever.

The right pattern has three parts:

1. Use n8n credentials, not literal values. Every common integration in n8n (HTTP Request, Postgres, Slack, Stripe, etc.) has a credential type. Add the credential once via the n8n UI, reference it from the node, and the value lives in the encrypted database, not in the workflow JSON.

2. For HTTP Request nodes that hit a custom API, use a generic credential type. The “Generic Credential Type” or a custom credential type stores the value once and references it across workflows. The workflow JSON only contains the credential ID, not the value.

3. For one-off or temporary secrets, use environment variables and read them in a Code node:

// In a Code node
const apiKey = process.env.MY_API_KEY;
if (!apiKey) {
  throw new Error('MY_API_KEY is not set in the n8n environment');
}
const items = $input.all();
return items.map((item) => ({
  json: {
    ...item.json,
    headers: {
      Authorization: `Bearer ${apiKey}`,
    },
  },
}));

This works only if N8N_BLOCK_ENV_ACCESS_IN_NODE is false for that specific install, which is a security trade-off. The safer default is to set it to true and never read env vars from inside a node. Use n8n credentials instead.

Common n8n environment variables reference

This is the set that matters in production. The full list is in the n8n environment variables documentation, but these are the ones that come up in real deployments.

VariablePurposeDefaultProduction value
N8N_HOSTPublic hostnamelocalhostYour domain
N8N_PORTListen port56785678 or behind a reverse proxy
N8N_PROTOCOLhttp or httpshttphttps
WEBHOOK_URLPublic webhook base URLderivedfull https URL
N8N_ENCRYPTION_KEYEncrypts stored credentialsnonerandom, persisted, secret
DB_TYPEsqlite or postgresdbsqlitepostgresdb
DB_POSTGRESDB_*Postgres connectionnonefull connection
EXECUTIONS_DATA_PRUNEAuto-delete old executionstruetrue
EXECUTIONS_DATA_MAX_AGEHours to keep executions336168 (7 days)
N8N_METRICSEnable /metrics endpointfalsetrue
N8N_BLOCK_ENV_ACCESS_IN_NODEBlock env reads in Code nodesfalsetrue
N8N_SECURE_COOKIESet Secure flag on cookiesfalsetrue
N8N_RESTRICT_FILE_ACCESS_TOComma-separated dir whitelistnoneyour data dir
N8N_LOG_LEVELerror, warn, info, debuginfoinfo in prod, debug to debug
N8N_LOG_OUTPUTconsole, file, or bothconsoleboth
N8N_USER_FOLDERn8n data directory~/.n8npersistent volume
GENERIC_TIMEZONEDefault timezone for date nodessystemUTC or your region
N8N_DEFAULT_BINARY_DATA_MODEfilesystem or s3filesystems3 for multi-instance
N8N_BINARY_DATA_STORAGE_PATHWhere to store binary dataderivedpersistent volume or S3 bucket

The two variables most teams get wrong on day one are N8N_ENCRYPTION_KEY and DB_TYPE. SQLite is the default, and it works for a single-user install. The moment you scale to multiple users, a queue worker, or a queueing system, switch to Postgres. SQLite does not survive concurrent writes and was never meant to.

Database, queue, and storage variables

For a real production n8n install, you will set:

# Postgres (required for production)
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres.internal
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=[REDACTED]
DB_POSTGRESDB_SCHEMA=public
DB_POSTGRESDB_SSL_CA=/home/node/.n8n/certs/ca.pem

# Queue mode (for multiple workers)
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis.internal
QUEUE_BULL_REDIS_PORT=6379
QUEUE_BULL_REDIS_PASSWORD=[REDACTED]
QUEUE_BULL_REDIS_DB=0

# Binary data in S3 (for multi-instance)
N8N_DEFAULT_BINARY_DATA_MODE=s3
N8N_S3_BUCKET=n8n-binary-data
N8N_S3_REGION=us-east-1
N8N_S3_ACCESS_KEY=[REDACTED]
N8N_S3_SECRET_ACCESS_KEY=[REDACTED]

EXECUTIONS_MODE=queue is the switch that lets you run multiple n8n instances behind a load balancer. Without it, n8n is single-instance. With it, the main process handles the API and the webhooks, and worker processes pull jobs from a Redis queue. This is the same pattern Sidekiq, Celery, and BullMQ use, and it is how you scale n8n horizontally.

N8N_DEFAULT_BINARY_DATA_MODE=s3 is the switch that lets binary data (file uploads, attachments) survive across multiple instances. Without it, binary data is stored on the local filesystem of whichever instance handled the request, and the next instance cannot find it.

For a hosted n8n setup where the database, queue, and storage are all on the same platform, the RunxBuild n8n hosting docs cover the standard service shape: a service for the n8n app, a managed database, and a persistent volume for binary data.

Self-hosted vs n8n Cloud: the rule

The n8n cloud product handles most of the variables above for you. Self-hosting is the right call when:

  • You need the data to live in a specific region (compliance).
  • You are running n8n as part of a larger automation platform and want one set of credentials across services.
  • You have a workload that benefits from colocating the database, the workers, and the storage.
  • You want to integrate n8n with private services that are not exposed to the public internet.

For everything else, n8n cloud is faster to start with. Self-hosting is faster to customize.

The biggest mistake teams make is starting on n8n cloud, outgrowing it in six months, and trying to migrate an active workflow environment. If you think you will self-host eventually, start there. If you think you will stay on cloud, the migration cost is real but the early velocity is worth it.

Rotation: what happens when a credential leaks

The credential rotation flow is the part most guides skip, because it is not a n8n-specific problem. The pattern is:

  1. Identify the leaked credential. Git history, an alert, a vendor disclosure. Find the scope: which workflow, which service, which environment.
  2. Generate a new credential in the upstream service. Stripe, Slack, the database, the API. The new credential has the same scope as the old one.
  3. Update the n8n credential store. In the n8n UI, open the credential, paste the new value, save. Do not edit the workflow.
  4. Trigger a test execution. Run the affected workflow manually. Confirm the new credential works.
  5. Revoke the old credential. In the upstream service, disable or delete the old credential. Confirm the old value no longer authenticates.
  6. Audit. Check the execution history for unauthorized use of the leaked credential between leak time and rotation time.

The step that fails in real incidents is step 5. Teams rotate the n8n-side credential and assume the upstream credential is dead. It is not, until you revoke it. The leaked credential is still in git history, in logs, in screenshots, in chat threads.

If you have set N8N_ENCRYPTION_KEY correctly and the encryption key itself has not leaked, rotating a stored credential is a 60-second operation. If the encryption key has leaked, you have to rotate every credential. Which is the strongest argument for keeping the encryption key out of any system that could leak.

Hosting n8n in production

For a production n8n install, the deployable surface is:

  • A long-running service (n8n app + main process)
  • A managed database (Postgres)
  • A queue/cache (Redis, for queue mode)
  • Persistent storage for binary data (volume or S3)
  • A reverse proxy for HTTPS termination
  • A custom domain with TLS

The reverse proxy and the custom domain are the part most self-hosted installs do badly. n8n’s webhooks need a stable public URL, and the WEBHOOK_URL variable has to match the actual URL callers use. A wrong WEBHOOK_URL is the most common cause of “my webhook returns 404 even though the workflow is active.”

For a hosted n8n service where the database, the storage, and the route are all on the same platform, the RunxBuild n8n hosting guide walks through the service definition, the environment variables, the managed database, and the custom domain setup. The pattern is the same one you would use for any long-running backend service.

For cost modeling, a production n8n install is a small service plus a database plus a worker. The hosting cost is usually modest compared to the time saved by the automations. The RunxBuild hosting calculator is a useful sanity check before you commit to a specific instance shape.

FAQ

Where does n8n read environment variables from?

From the process environment. If you run n8n in Docker, use --env-file to load a .env file. If you run it on a VM, set the variables in your process manager (PM2, systemd, supervisord) or export them before launching. n8n does not auto-load .env from the working directory.

Can I set environment variables inside a workflow node?

Yes, by reading process.env.MY_VAR in a Code node. The security flag N8N_BLOCK_ENV_ACCESS_IN_NODE controls whether this is allowed. The default is false. For production, set it to true and use n8n credentials instead, so a leaked workflow cannot read host secrets.

What is the most important n8n environment variable?

N8N_ENCRYPTION_KEY. It encrypts every credential stored in the n8n database. If you lose it, every stored credential is unreadable. If it leaks, every stored credential is readable. Generate it once, store it in your secret manager, and never rotate it without rotating every stored credential at the same time.

Should I use SQLite or Postgres for n8n?

SQLite for a single-user local install. Postgres for anything else. The default is SQLite, and it is the right default for trying n8n out, but it does not survive concurrent writes and was never meant to. The first time you add a second user, a webhook that runs while you are editing, or a second n8n instance, switch to Postgres.

How do I rotate an n8n credential without breaking workflows?

Open the credential in the n8n UI, update the value, save. Do not edit the workflow. n8n updates the encrypted value in the database and the workflow picks up the new credential on the next execution. The upstream rotation (revoking the old credential in the third-party service) is a separate step and the one most teams forget.

Can n8n read AWS Secrets Manager or HashiCorp Vault?

Not natively. The common pattern is to bootstrap secrets into the process environment at container start, using the cloud provider’s SDK or a Vault agent. The n8n process sees them as regular environment variables. The .env file or the container’s env block is the source of truth, but the actual values come from the secret manager, not from a checked-in file.

Does n8n cloud handle all of this for me?

Yes. n8n cloud manages the database, the encryption, the backups, the upgrades, and the env vars. The trade-off is that you do not control the underlying infrastructure. For most teams, n8n cloud is the right starting point. For teams with data residency or compliance requirements, self-hosting is the path.

What is the difference between n8n credentials and n8n environment variables?

Credentials are stored in the n8n database (encrypted) and referenced from workflow nodes by credential ID. Environment variables live in the process environment and are read by the n8n process itself (database URL, host, port) or, if N8N_BLOCK_ENV_ACCESS_IN_NODE is false, by Code nodes. The rule of thumb: credentials are for values the workflow needs, environment variables are for values n8n needs to run.