Fundamentals
n8n Environment Variables Explained (The Ones That Matter)
A practical reference to the n8n environment variables that actually matter when self-hosting: URLs and webhooks, database, executions pruning, security.
n8n is configured almost entirely through environment variables — there are hundreds of them, and the official reference lists them all with one-line descriptions. What it doesn’t tell you is which fifteen actually decide whether your instance works, which ones people set wrong constantly, and which defaults will quietly fill your disk.
This is that guide: the variables that matter for a self-hosted instance, grouped by job, with the failure mode each one prevents. It doubles as the annotated explanation of every variable in our production Compose stack.
Identity & URLs — where webhooks are born
N8N_HOST=n8n.yourdomain.com
N8N_PROTOCOL=https
N8N_PORT=5678
WEBHOOK_URL=https://n8n.yourdomain.com/
N8N_PROXY_HOPS=1
N8N_HOST/N8N_PROTOCOL— the public identity of your instance. n8n uses these to construct the URLs it shows you and registers with external services.WEBHOOK_URL— the single most misunderstood variable in n8n. Behind a reverse proxy, n8n cannot see its own public URL — it sees itself ashttp://n8n:5678.WEBHOOK_URLtells it what the outside world should call. Set it wrong (or forget it) and every webhook you register with Stripe/GitHub/Telegram points at a URL that doesn’t resolve. If your webhooks “worked in test but not in production,” start here — then see the troubleshooting guide.N8N_PROXY_HOPS=1— tells n8n it sits behind exactly one reverse proxy (Caddy), so it trusts theX-Forwarded-*headers and correctly reconstructs client IPs and the https scheme. Symptoms of missing it include cookie/auth weirdness behind the proxy.
The one you must never lose
N8N_ENCRYPTION_KEY=<openssl rand -hex 32>
Every credential you save is encrypted with this key. Database backups are useless without it; whoever holds both can rebuild your instance anywhere. Set it explicitly (never rely on the auto-generated one buried in a container volume) and store a copy in your password manager. This is important enough that the backup guide spends its first section on it.
Related, from the hardening checklist:
N8N_BLOCK_ENV_ACCESS_IN_NODE=true # workflows can't read the instance's env (your DB password lives there)
N8N_PUBLIC_API_DISABLED=true # remove the REST API surface if you don't use it
N8N_SECURE_COOKIE=true # HTTPS-only auth cookies (default; don't turn it off to dodge a proxy issue)
Database — Postgres, explicitly
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=<openssl rand -hex 24>
Without DB_TYPE, n8n defaults to SQLite in the data volume — fine for experiments, but Postgres is what you want the day workflows matter (concurrency, reliable backups via pg_dump, queue mode later). DB_POSTGRESDB_HOST is the Compose service name, not localhost — inside a container, localhost means the container itself, another classic first-hour mistake.
Time — schedules fire in this timezone
GENERIC_TIMEZONE=Europe/Madrid
TZ=Europe/Madrid
GENERIC_TIMEZONE is what Schedule/Cron nodes consult; TZ sets the container’s system clock (log timestamps, Code-node new Date()). Set both to the same value and never think about it again — leave them out and everything runs in UTC, which is how a “daily 8:00 digest” like this one arrives at 9:00 or 10:00 depending on the season.
Executions — the disk-filler
n8n saves execution history — inputs and outputs of every node, every run — to the database. By default it keeps a lot. On a busy instance this is the number-one cause of ballooning databases and 2 GB backups.
# Prune old executions automatically:
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168 # hours; keep 7 days
# Choose what gets saved at all:
EXECUTIONS_DATA_SAVE_ON_SUCCESS=all # or "none" for high-volume workflows
EXECUTIONS_DATA_SAVE_ON_ERROR=all # keep errors — you'll want them
EXECUTIONS_DATA_SAVE_ON_PROGRESS=false
A sane production policy: save everything for 7 days, always save errors, and for very chatty workflows override per workflow (workflow settings → save successful executions: no). Your future self, staring at last Tuesday’s failure, will thank you for SAVE_ON_ERROR=all.
Related: if workflows pass files around (images, PDFs), keep binary data out of the database:
N8N_DEFAULT_BINARY_DATA_MODE=filesystem
Runners & performance
N8N_RUNNERS_ENABLED=true
Task runners execute Code nodes in a separate, sandboxed process instead of inside the main n8n process — better security and stability (a runaway script can’t take down the editor). Newer versions expect this on; set it explicitly and you’ll also silence the deprecation warning in your logs.
For genuinely high volume, n8n scales horizontally with queue mode (EXECUTIONS_MODE=queue plus Redis and worker containers). That’s beyond this article’s scope — just know the ceiling exists and is much higher than one container.
Logs & diagnostics
N8N_LOG_LEVEL=info # debug | info | warn | error
N8N_METRICS=true # optional: Prometheus metrics at /metrics
N8N_LOG_LEVEL=debug is your first move when something misbehaves and the troubleshooting guide’s second suggestion after checking WEBHOOK_URL. Turn it back down afterwards; debug is noisy.
The complete production baseline
Pulling it together — this is our annotated .env for a single-server production instance:
# Identity
N8N_HOST=n8n.yourdomain.com
N8N_PROTOCOL=https
WEBHOOK_URL=https://n8n.yourdomain.com/
N8N_PROXY_HOPS=1
# Time
GENERIC_TIMEZONE=Europe/Madrid
TZ=Europe/Madrid
# Secrets
N8N_ENCRYPTION_KEY=... # backed up in the password manager
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=...
# Executions hygiene
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168
EXECUTIONS_DATA_SAVE_ON_ERROR=all
N8N_DEFAULT_BINARY_DATA_MODE=filesystem
# Security & runtime
N8N_RUNNERS_ENABLED=true
N8N_BLOCK_ENV_ACCESS_IN_NODE=true
N8N_PUBLIC_API_DISABLED=true
Copy it, adjust the domain and timezone, generate the secrets, and you’ve encoded everything this article explained. When a future n8n release renames or deprecates one of these, the release notes will say so — which is why you read them before updating.