Think of an app as one big function: it takes an input and produces an output. The trick of how we build software is that we do not hard-code the inputs. We pass them in through a file full of settings, so we can change how the app behaves without touching its code.
It is a bit like a parametric 3D model. Imagine designing an iPhone where you only define relationships: this side is one quarter of that side, the screen sits this far from the edge. Then you set one real measurement, say one side is 150 millimeters, and the whole shape snaps into place with every proportion correct. Environment variables are those parameters for your app. You set the values and the behavior takes shape, no code required.
For n8n that means you control the database, security, scaling, and networking from a single file. The one you cannot get wrong is the encryption key: it protects your stored credentials, and if you lose it, they are gone for good.
The settings that actually matter
If you only touch a handful, make it these.
| Variable | What it does | Typical value |
|---|---|---|
| N8N_ENCRYPTION_KEY | Encrypts your stored credentials. Set it before you save anything, and back it up | a 64-char hex secret |
| WEBHOOK_URL | The public https address n8n uses to build webhook URLs | https://n8n.example.com/ |
| N8N_HOST | The hostname for the instance | n8n.example.com |
| N8N_PROTOCOL | The URL scheme, https when behind a proxy | https |
| EXECUTIONS_MODE | Where workflows run: regular for one box, queue to scale out | regular or queue |
| EXECUTIONS_TIMEOUT | Hard stop per run in seconds, so a stuck loop cannot run forever | 3600 |
| N8N_CONCURRENCY_PRODUCTION_LIMIT | Caps how many executions run at once, to avoid spikes | 20 |
Back up your N8N_ENCRYPTION_KEY in a secure vault. Lose it and you lose the ability to decrypt every credential you have stored.
Database: SQLite or Postgres
n8n stores its data one of two very different ways, and the choice comes down to whether you run one instance or many.
SQLite is the default. It is a file that lives right next to the app, in the same process, with no network in between. That makes it fast and zero-setup, ideal for local development and a single small instance.
Postgres is a separate database server you reach over the network. You want it the moment you scale: as soon as you run more than one n8n instance, or you switch on queue mode with workers, every instance has to read and write the same shared database, and a file on one machine cannot do that.
When you scale that far you also bring in Redis. In queue mode, the main process drops each execution onto a Redis-backed queue and your worker processes pull jobs from it. Redis is the shared channel that lets the main process and the workers coordinate, while Postgres holds the data they all share.
| Variable | Purpose | Example or default |
|---|---|---|
| DB_TYPE | Selects the database backend | sqlite default, postgresdb for scale |
| DB_POSTGRESDB_HOST | Postgres host name | postgres |
| DB_POSTGRESDB_PORT | Postgres port | 5432 |
| DB_POSTGRESDB_DATABASE | Database name | n8n or n8n_prod |
| DB_POSTGRESDB_SCHEMA | Schema name | public |
| DB_POSTGRESDB_USER | Database user | n8n |
| DB_POSTGRESDB_PASSWORD_FILE | Password from a mounted secret file | use _FILE, never inline |
| Variable | Purpose | Typical value |
|---|---|---|
| DB_POSTGRESDB_POOL_SIZE | Max pooled connections | 10 for queue mode |
| DB_POSTGRESDB_CONNECTION_TIMEOUT | Connect timeout in ms | 30000 on slow links |
| DB_POSTGRESDB_IDLE_CONNECTION_TIMEOUT | Idle timeout in ms | 60000 for bursty load |
| DB_POSTGRESDB_SSL_ENABLED | Encrypt traffic to Postgres | true for remote or managed |
| DB_POSTGRESDB_SSL_REJECT_UNAUTHORIZED | Verify the server certificate | true, disable only for tests |
Execution: timeouts and data retention
These control how long workflows can run and how much of their history n8n keeps. In production you want strict timeouts and aggressive pruning, or the database fills up with execution logs.
| Variable | Purpose | Typical value |
|---|---|---|
| EXECUTIONS_MODE | Where jobs run | regular, or queue for scale |
| EXECUTIONS_TIMEOUT | Hard stop per run in seconds | 3600 to kill loops |
| EXECUTIONS_TIMEOUT_MAX | Max allowed per workflow in seconds | 7200 for long jobs |
| EXECUTIONS_DATA_SAVE_ON_SUCCESS | Save data from successful runs | none in prod |
| EXECUTIONS_DATA_SAVE_ON_ERROR | Save data from failed runs | all, for incident review |
| EXECUTIONS_DATA_PRUNE | Delete old execution data | true in prod |
| EXECUTIONS_DATA_MAX_AGE | How long to keep data, in hours | 168 for one week |
| N8N_CONCURRENCY_PRODUCTION_LIMIT | Max concurrent executions | 20 to prevent spikes |
Security: protect credentials and the UI
The encryption key guards your secrets, and Basic Auth keeps strangers out of the editor. Lock these down before you expose n8n to the internet.
| Variable | Purpose | Typical value |
|---|---|---|
| N8N_ENCRYPTION_KEY | Encrypt stored credentials | set before first prod boot |
| N8N_BASIC_AUTH_ACTIVE | Gate the UI with Basic Auth | true for small teams |
| N8N_BASIC_AUTH_USER | Basic Auth user name | admin or a team user |
| N8N_BASIC_AUTH_PASSWORD | Basic Auth password | a long random string |
| N8N_BLOCK_ENV_ACCESS_IN_NODE | Stop Code nodes reading env vars | true for shared installs |
| N8N_COMMUNITY_PACKAGES_ENABLED | Allow community packages | false for hardened clusters |
Network and webhooks
This is the group that makes public URLs resolve correctly behind a reverse proxy. Get WEBHOOK_URL wrong and your webhooks point at localhost.
| Variable | Purpose | Typical value |
|---|---|---|
| N8N_HOST | Host name for URLs | n8n.example.com |
| N8N_PORT | Internal port | 5678 |
| N8N_PROTOCOL | URL scheme | https behind a proxy |
| WEBHOOK_URL | Public base URL for webhooks | the public https address |
| N8N_PROXY_HOPS | Trusted proxy hop count | match your proxy chain |
Queue mode: scaling with Redis workers
When one instance is no longer enough, queue mode spreads executions across separate worker processes. The main process queues jobs in Redis, and the workers pull and run them. This is where Postgres and Redis become required, not optional.
| Variable | Purpose | Typical value |
|---|---|---|
| EXECUTIONS_MODE | Set to queue to distribute load | queue |
| QUEUE_BULL_REDIS_HOST | Redis host name | redis service name |
| QUEUE_BULL_REDIS_PORT | Redis port | 6379 |
| REDIS_PASSWORD_FILE | Redis password from a secret file | required on secured Redis |
| QUEUE_WORKER_CONCURRENCY | Jobs each worker runs at once | tune by CPU and memory |
| N8N_DISABLE_PRODUCTION_MAIN_PROCESS | Run a container as worker only | true on worker containers |
A minimal production .env
Copy this, change the secrets, and you have a sane Postgres and queue setup to start from.
# ---- Identity and URLs ----
N8N_HOST=n8n.example.com
N8N_PORT=5678
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.example.com/
# ---- Security ----
# Generate once and store in a secret manager, 64 hex chars
N8N_ENCRYPTION_KEY=YOUR_64_HEX_SECRET
N8N_BLOCK_ENV_ACCESS_IN_NODE=true
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=change-me-now
# ---- Database (Postgres) ----
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD_FILE=/run/secrets/db_password
DB_POSTGRESDB_POOL_SIZE=10
# ---- Executions and retention ----
EXECUTIONS_MODE=queue
EXECUTIONS_TIMEOUT=3600
EXECUTIONS_TIMEOUT_MAX=7200
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
N8N_CONCURRENCY_PRODUCTION_LIMIT=20
# ---- Queue and Redis ----
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
QUEUE_WORKER_CONCURRENCY=5
TZ=UTC