n8n Environment Variables Explained
After debugging more than a few “it works locally but breaks in production” n8n deployments, I’ve learned that environment variables are usually the real root cause.
n8n Environment Variables Explained.
How n8n reads environment variables (and why that matters in production)
Environment variables in n8n are not “nice-to-have settings”—they are the runtime contract that decides how the editor builds URLs, how credentials get encrypted, where executions are stored, and whether scaling features like queue mode behave correctly.
The easiest way to avoid surprises is to treat your configuration like code:
- Keep one “baseline” config that never changes per environment (dev/staging/prod).
- Keep secrets out of plain-text env vars when possible.
- Document every variable you set and why you set it.
Real-world weakness you’ll run into (and the fix)
Weakness: n8n’s behavior can look “random” if a variable is set in one place (host, container, Compose, CI) but overridden somewhere else.
Fix: Pick one source of truth (a single Compose file + a single env file/secret source), then audit overrides before every deploy.
The “must-get-right” URL variables (editor, webhooks, reverse proxies)
If your n8n sits behind Nginx, Caddy, Cloudflare, AWS ALB, or any reverse proxy, URL variables become non-negotiable. The editor UI, OAuth callbacks, and external services registering webhooks all depend on correct public URLs.
Core deployment URL variables
- N8N_HOST and N8N_PORT: where n8n listens internally (default port is 5678).
- N8N_PROTOCOL:
httporhttpsfor how n8n is reached. - N8N_EDITOR_BASE_URL: the public URL people use to open the editor (also used for emails and certain auth flows).
- WEBHOOK_URL: the public base URL for webhooks when a reverse proxy is involved (critical when internal port differs from external 443).
- N8N_PROXY_HOPS: set to
1in the common “one reverse proxy” setup so n8n trusts forwarded headers correctly.
Common reverse-proxy failure (and the fix)
Weakness: You’ll see webhook URLs showing :5678 or the wrong hostname in the UI, and external services will call the wrong URL.
Fix: Set WEBHOOK_URL to your public domain and set N8N_PROXY_HOPS correctly. Also ensure your proxy forwards X-Forwarded-Host and X-Forwarded-Proto (most modern proxy presets handle this).
Copy-ready example: Reverse proxy + correct public URLs
Note: Keep WEBHOOK_URL ending with a trailing slash if you standardize that format across your stack. Consistency reduces edge-case mismatches.
Security-critical variables (credentials encryption and session safety)
n8n stores credentials encrypted at rest, but the encryption key choice determines whether you can safely redeploy, restore backups, or scale horizontally.
N8N_ENCRYPTION_KEY is not optional in serious deployments
- N8N_ENCRYPTION_KEY: set your own stable key so credentials remain decryptable across container rebuilds and migrations.
The failure you only notice after a disaster (and the fix)
Weakness: If you let n8n generate its own encryption key on first launch and later rebuild a container without preserving the key, you can end up with credentials that won’t decrypt correctly.
Fix: Always set N8N_ENCRYPTION_KEY explicitly and store it in a proper secrets system (or at minimum, a protected secret file mounted at runtime).
Database variables (SQLite vs Postgres for reliability)
SQLite is convenient, but production n8n stacks in high-value markets typically move to Postgres for better durability, concurrency, and operational control.
Typical Postgres environment variables
- DB_TYPE (commonly
postgresdb) - DB_POSTGRESDB_HOST, DB_POSTGRESDB_PORT
- DB_POSTGRESDB_DATABASE, DB_POSTGRESDB_USER, DB_POSTGRESDB_PASSWORD
- DB_POSTGRESDB_SCHEMA (optional but useful for isolating objects)
Database gotcha (and the fix)
Weakness: Postgres misconfigurations show up as “random” slowness, timeouts, or reconnect storms—especially if the container network name/host is wrong or SSL expectations don’t match.
Fix: Validate connectivity from the n8n container to Postgres, keep your DB hostnames stable, and only enable SSL settings that match your DB provider’s guidance (don’t guess).
Copy-ready example: Postgres configuration
Executions data variables (performance, retention, and surprise deletions)
Execution history is where performance silently dies if you don’t control retention. n8n can prune executions automatically, and the defaults can be too aggressive for teams that expect long retention.
High-impact execution variables you should know
- EXECUTIONS_DATA_PRUNE: enables rolling deletion of old executions.
- EXECUTIONS_DATA_MAX_AGE: how old executions can be (in hours) before deletion.
- EXECUTIONS_DATA_PRUNE_MAX_COUNT: maximum executions kept;
0means “no limit”. - EXECUTIONS_MODE:
regularorqueue. - N8N_CONCURRENCY_PRODUCTION_LIMIT: cap concurrent production executions to prevent resource collapse.
The most expensive retention mistake (and the fix)
Weakness: You set max age thinking you’re safe, but a max count limit still trims your history unexpectedly during traffic spikes.
Fix: Set EXECUTIONS_DATA_PRUNE_MAX_COUNT deliberately for your database size and compliance needs, then pair it with an age limit that matches your operational reality.
Copy-ready example: Balanced pruning settings
Queue mode variables (scaling with Redis without breaking webhooks)
Queue mode is a serious upgrade for throughput: your “main” handles UI and coordination, while workers execute jobs. Redis becomes a core dependency, so its configuration is operationally important.
Key queue variables
- EXECUTIONS_MODE: set to
queueon main and workers when you fully commit to queue mode. - QUEUE_BULL_REDIS_HOST, QUEUE_BULL_REDIS_PORT
- QUEUE_BULL_REDIS_PASSWORD (when Redis requires auth)
- QUEUE_BULL_REDIS_TLS (when using TLS-enabled Redis endpoints)
- OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS (optional, but useful when editors are slow)
The queue-mode trap (and the fix)
Weakness: You enable queue mode but forget that webhooks still need a reliable path to workers. This can look like “webhooks stuck” or “jobs queued but never processed.”
Fix: Confirm workers are running, Redis is reachable from every node, and your network policy/firewall allows Redis traffic only where it should. If you use TLS, set the TLS variable and validate certificates rather than disabling verification casually.
Where to define environment variables (npm, Docker, Compose) without confusion
If you’re deploying for U.S. customers and you care about repeatable releases, Docker Compose (or an orchestrator like ECS/Kubernetes) is typically the most controllable path.
Docker Compose: powerful, but easy to misread
Weakness: Compose variable precedence can surprise you: values may come from the shell environment, a .env file, env_file, or the environment block—and not always in the order you assume.
Fix: Keep “actual runtime values” in a single place (usually env_file + an explicit environment block for the few that must be templated). Document overrides and avoid setting the same key in multiple layers.
Secrets management for high-value deployments (don’t ship secrets as plain env vars)
Credentials and encryption keys are where security incidents start. Passing them as plain environment variables is common, but it’s not the best option when you’re operating in a high-value market.
Option 1: File-based secrets with _FILE (practical and simple)
n8n supports a pattern where you add _FILE to certain variables so the value is read from a mounted file. This is ideal with Docker Secrets or Kubernetes Secrets.
Option 2: AWS Secrets Manager (common in U.S. production stacks)
Weakness: Injecting secrets as environment variables is convenient, but it can increase blast radius if logs, crash dumps, or process inspection expose environment values.
Fix: Use least-privilege IAM policies, scope secrets per service, rotate regularly, and inject only the exact keys you need at runtime (not entire secret blobs).
Option 3: HashiCorp Vault (maximum control, higher complexity)
Weakness: Vault adds operational overhead: auth methods, policies, renewal, and templating are a real learning curve.
Fix: Use Vault Agent to render secrets into files or exported environment variables at startup, keep templates minimal, and test renewals before you ship.
Quick reference table (what to set first)
| Goal | Variables to prioritize | What usually breaks |
|---|---|---|
| Correct public editor URL | N8N_EDITOR_BASE_URL, N8N_PROTOCOL | OAuth callbacks and links in emails |
| Correct webhook URLs behind a proxy | WEBHOOK_URL, N8N_PROXY_HOPS | Webhooks show wrong host/port |
| Stable credentials across redeploys | N8N_ENCRYPTION_KEY | Credentials fail after rebuild/migration |
| Production-grade storage | DB_TYPE, DB_POSTGRESDB_HOST, DB_POSTGRESDB_DATABASE | Connection errors, slow queries |
| Prevent database bloat | EXECUTIONS_DATA_PRUNE, EXECUTIONS_DATA_MAX_AGE, EXECUTIONS_DATA_PRUNE_MAX_COUNT | UI lag, backups balloon, disk fills |
| Scaling executions | EXECUTIONS_MODE, QUEUE_BULL_REDIS_HOST, QUEUE_BULL_REDIS_PASSWORD | Jobs queue but workers don’t process |
Common mistakes checklist (the stuff that silently kills reliability)
- You set WEBHOOK_URL but forget the trailing slash consistency and end up with mismatched callback URLs in external services.
- You rely on default pruning limits and later discover execution history is missing during incident response.
- You scale to multiple containers but forget to lock down and stabilize N8N_ENCRYPTION_KEY.
- You expose Redis publicly or without TLS/auth and later wonder why queue behavior becomes unpredictable (or worse, compromised).
- You store secrets directly in a repo-backed
.envfile and accidentally leak them via logs or backups.
Troubleshooting: fast signals that point to the right variable
- Webhook URL in UI is wrong: check WEBHOOK_URL, N8N_PROXY_HOPS, and your proxy forwarded headers.
- Editor opens but assets/redirects are odd: check N8N_EDITOR_BASE_URL and N8N_PROTOCOL.
- Credentials suddenly fail after redeploy: confirm N8N_ENCRYPTION_KEY hasn’t changed.
- UI feels slower every week: review execution pruning settings and database growth.
- Queue mode enabled but nothing runs: validate Redis connectivity, credentials, and that workers are actually online.
FAQ
Should you always set WEBHOOK_URL?
Set WEBHOOK_URL when your public URL differs from the internal host/port n8n runs on (typical with reverse proxies, managed load balancers, and TLS termination). If your public URL maps 1:1 to the runtime host/port, you may not need it—but many production stacks still set it for explicitness.
What’s the single most important variable to set for production safety?
N8N_ENCRYPTION_KEY. If it changes unexpectedly, credential decryption can break at the worst time: after a restore, rebuild, or migration.
Why do executions disappear even when pruning “looks” correct?
Because pruning can be limited by both maximum age and maximum count. If count is too low for your traffic volume, you’ll lose history faster than expected during spikes.
Is SQLite acceptable for production?
SQLite can be fine for low-volume single-instance usage, but it becomes a bottleneck when concurrency, backups, and growth matter. Postgres is the safer default when uptime and performance are priorities.
How do you keep secrets out of environment variables without complicating everything?
Use the _FILE pattern and mount secrets as files. It’s simple, widely supported, and avoids exposing secrets via process environment in many common operational scenarios.
What’s the best way to scale n8n reliably?
Move to Postgres, set execution retention intentionally, then adopt queue mode with Redis once you have baseline stability. Scaling before stability usually multiplies your failure modes.
- n8n environment variables overview: https://docs.n8n.io/hosting/configuration/environment-variables/
- Docker Compose environment variables: https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/
- AWS Secrets Manager injection as env vars (ECS): https://docs.aws.amazon.com/AmazonECS/latest/developerguide/secrets-envvar-secrets-manager.html
- HashiCorp Vault Agent exporting secrets as env vars: https://developer.hashicorp.com/vault/tutorials/vault-agent/agent-env-vars
Conclusion
If you treat environment variables as a first-class part of your n8n architecture—URLs, encryption, database, retention, and scaling—you stop chasing “mystery bugs” and start shipping predictable automation at production quality.
Lock in the URL variables, pin your encryption key, move storage to Postgres when reliability matters, and set pruning intentionally—then scale with queue mode only after the basics stay stable under real traffic.

