n8n Node Database: SQLite vs PostgreSQL Storage Configuration
⚡ n8n Workflow Automation T4 · Database Configuration
n8n Node Database: SQLite vs PostgreSQL Storage Configuration

n8n ships with SQLite as its default database — a serverless, file‑based engine that stores everything in a single database.sqlite file within the .n8n directory. It requires zero configuration and is perfect for local development, testing, and single‑user instances handling fewer than 50 executions per day. PostgreSQL is the recommended production database: it supports row‑level locking (MVCC), concurrent writes from multiple worker processes, hot backups via pg_dump, and is mandatory for queue mode — SQLite cannot safely handle the multi‑process writes that queue mode generates and will corrupt under that load. This guide provides the definitive configuration, performance benchmark, migration, and data‑pruning reference for selecting and managing n8n’s database backend [1] [2].

SQLite (default)
Dev / Personal (<50 exec/day) [1]
PostgreSQL
Production / Queue Mode [3]
DB_TYPE=postgresdb
Switch Variable [4]
336h (14 days)
Default Execution Retention [5]
Dimension SQLite PostgreSQL
Default ✅ Yes — zero config, file‑based [1] ❌ No — requires separate server [2]
Queue Mode ❌ Not supported — will corrupt [3] ✅ Required [3]
Concurrency Model Single writer — file‑level lock [6] High — MVCC + row‑level locks [6]
Ideal Scale <50 execs/day, single user [1] 200+ execs/day, multi‑user, multi‑worker [3]
Hot Backups ❌ Inconsistent copy while running [6] pg_dump without downtime [6]
Disk Reclamation VACUUM required; space reused, not freed [1] Automatic via autovacuum [6]
WAL Mode ✅ Set DB_SQLITE_POOL_SIZE > 0 [1] N/A (built‑in WAL) [6]

Why is PostgreSQL mandatory for n8n queue mode and multi-worker deployments?

Queue mode splits n8n into a main process (UI, API, trigger listening) and multiple worker processes — each worker independently reads the database to fetch workflow definitions and writes execution results back concurrently. This architecture creates simultaneous multi-process database access, which SQLite cannot safely handle: SQLite uses a file‑level write lock, meaning only one worker can write at a time while others queue, and under sustained concurrent writes the database file will corrupt. [3] [6]

PostgreSQL uses MVCC (Multi-Version Concurrency Control) with row‑level locking, allowing multiple workers to read and write simultaneously without blocking each other. Beyond queue mode, PostgreSQL is also required for multi-main high availability setups (Enterprise feature) — running multiple main n8n instances that share a single database. Migrating a production n8n instance from SQLite to PostgreSQL once you outgrow single‑process operation is documented step‑by‑step in this guide (see the Migration section) and the official n8n database configuration reference [4]. For the complete queue mode architecture covering Redis, worker configuration, and scaling strategies, see the n8n Scaling & Queue Configuration guide.

⚡ Queue Mode Database Rule: If you are deploying queue mode, PostgreSQL is not a recommendation — it is a hard requirement. Running queue mode with SQLite will corrupt your database. Always set DB_TYPE=postgresdb before enabling EXECUTIONS_MODE=queue. If n8n falls back to SQLite despite PostgreSQL configuration, verify that DB_TYPE is spelled exactly postgresdb — any other value silently falls back. [3]

What is the real-world performance difference between SQLite and PostgreSQL in n8n?

A benchmark test using two identical n8n v1.65.0 instances on a 2‑core 4 GB VPS — one running SQLite and the other PostgreSQL 15 — simulated a common 5‑node webhook‑triggered workflow. Three test scenarios reveal where the performance gap emerges: at 5 requests/second, SQLite averages ~210 ms and PostgreSQL ~205 ms — a negligible difference explaining why personal users rarely notice issues. At 20 requests/second, SQLite spikes to 1,200 ms with a 3% failure rate, while PostgreSQL remains steady at 280 ms with 0% failures. [7] [6]

A third test pre‑loaded 100,000 execution history records into both databases. SQLite’s query planner degraded significantly — the workflow list page load time exceeded 5 seconds, while PostgreSQL stayed under 1 second. The root cause is architectural: SQLite’s file‑level write lock serializes all writes, while PostgreSQL’s MVCC enables concurrent access. SQLite is measurably faster for read‑heavy workloads where reads outnumber writes 100:1 — there is no network round‑trip, and data is accessed locally. But in n8n production, writes (execution logs, progress updates) and reads (editor loading, workflow fetching) interleave constantly, and the write serialization becomes the bottleneck. For the complete production deployment guide, see the n8n Docker Compose production stack guide.

📊 Performance Snapshot (n8n v1.65.0, 2‑core / 4 GB VPS): 5 requests/s: SQLite ~210 ms vs PostgreSQL ~205 ms — identical. 20 requests/s: SQLite 1,200 ms (3% failure) vs PostgreSQL 280 ms (0% failure). 100,000 history records: SQLite list‑page load >5 s vs PostgreSQL <1 s. Benchmark used a 5‑node webhook‑triggered workflow with 100 ms simulated processing per node. [7]

How do you configure n8n to use PostgreSQL via the DB_TYPE environment variable?

Set DB_TYPE=postgresdb — the value must be exactly postgresdb. Using "postgres" or "postgresql" causes n8n to silently fall back to SQLite without any error. This is the single most common misconfiguration. Then configure the six DB_POSTGRESDB_ variables: DB_POSTGRESDB_HOST, DB_POSTGRESDB_PORT (default 5432), DB_POSTGRESDB_DATABASE (default n8n), DB_POSTGRESDB_USER, and DB_POSTGRESDB_PASSWORD. [4] [2]

For SSL-encrypted connections, set DB_POSTGRESDB_SSL_ENABLED=true along with optional DB_POSTGRESDB_SSL_CA, DB_POSTGRESDB_SSL_CERT, and DB_POSTGRESDB_SSL_KEY. For connection pooling, configure DB_POSTGRESDB_POOL_SIZE (default: 2). Increase to 10‑20 for single‑instance production, and 20‑50 for queue mode with multiple workers. For SQLite, no configuration is required — n8n defaults to SQLite and stores the database file at ~/.n8n/database.sqlite. To enable WAL mode for better SQLite concurrency, set DB_SQLITE_POOL_SIZE to a value greater than zero (e.g., 4); this allows concurrent reads with a single writer. For the complete database environment variables reference, see the n8n Credential Nodes guide.

Variable Required Default Example Production Value
DB_TYPE sqlite postgresdb
DB_POSTGRESDB_HOST localhost postgres (Docker service name)
DB_POSTGRESDB_PORT 5432 5432
DB_POSTGRESDB_DATABASE n8n n8n_production
DB_POSTGRESDB_USER postgres n8n_user
DB_POSTGRESDB_PASSWORD strong_random_password
DB_POSTGRESDB_POOL_SIZE 2 20 (production single‑instance)
DB_POSTGRESDB_SSL_ENABLED false true (managed cloud databases, RDS, Cloud SQL)

How do you migrate an existing n8n instance from SQLite to PostgreSQL without data loss?

The safest migration path uses n8n’s built‑in export/import CLI commands (available since v1.67, November 2025). Step 1: Export entities — run n8n export:entities --type=all --output=export/ while the SQLite instance is stopped (exporting while running can capture an inconsistent state if a webhook fires mid‑export). Step 2: Set up PostgreSQL with a dedicated user and database. Step 3: Update your .env file with DB_TYPE=postgresdb and all DB_POSTGRESDB_* variables. Step 4: Start n8n with PostgreSQL and run n8n import:entities --input=export/ --type=all. [2] [8]

An alternative manual method uses DBeaver — connect to both the old SQLite and new PostgreSQL databases simultaneously, then export tables directly. The migration order that works reliably: credentials_entity → workflows_entity → folder → user → project → project_relation → tag_entity → workflows_tags → webhook_entity → shared_credentials → shared_workflow → execution_entity. After migrating core tables, delete the default user on the new instance, then migrate execution history. The N8N_ENCRYPTION_KEY must be identical between the old and new instances — if you change it, all stored credentials become permanently unrecoverable. For a full production‑ready deployment with Docker Compose, see the n8n Docker Compose production stack guide.

🔄 Migration Checklist: (1) Stop n8n before the export. (2) Export entities: n8n export:entities --type=all --output=export/. (3) Set up PostgreSQL with a separate database for n8n. (4) Update .env: DB_TYPE=postgresdb + all DB_POSTGRESDB_* variables. (5) Start n8n with PostgreSQL. (6) Import entities: n8n import:entities --input=export/ --type=all. (7) Verify workflows, credentials, and webhook URLs. Never delete the old SQLite database file until the new PostgreSQL setup has been running successfully for at least 48 hours. [2]

How does n8n purge execution data and reclaim disk space on SQLite versus PostgreSQL?

n8n implements a two‑stage pruning process: soft‑deletion marks records for removal and hard‑deletion permanently deletes them after a safety buffer. Pruning triggers when either condition is met — the execution finished more than EXECUTIONS_DATA_MAX_AGE hours ago (default: 336 hours / 14 days), or the total execution count exceeds EXECUTIONS_DATA_PRUNE_MAX_COUNT (default: 10,000). Only finished (non‑active) executions are eligible. [5] [9]

The critical difference: SQLite marks deleted rows as reusable within the database file but does not release that space back to the operating system — the database.sqlite file grows indefinitely. To reclaim disk space, you must run VACUUM, which rebuilds the entire database file — a blocking operation requiring downtime for large files. Set DB_SQLITE_VACUUM_ON_STARTUP=true to VACUUM automatically on restart. PostgreSQL uses autovacuum to reclaim and release disk space continuously without downtime. For reducing saved data before pruning, set EXECUTIONS_DATA_SAVE_ON_SUCCESS=none to skip saving successful execution data, and EXECUTIONS_DATA_SAVE_ON_PROGRESS=false to skip intermediate node‑level progress snapshots. For database sizing guidance, a small n8n instance (100 executions/day) generates roughly 1 GB of execution data per month without pruning. For the complete binary data and external storage configuration, see the n8n Node Execution Hub.

How do you enable SQLite WAL mode and troubleshoot the silent PostgreSQL fallback?

SQLite defaults to a rollback journal mode that allows only one writer at a time — any concurrent write attempt returns a SQLITE_BUSY error. Enable Write‑Ahead Logging (WAL) by setting DB_SQLITE_POOL_SIZE to an integer greater than zero (recommended: 4). WAL mode allows concurrent reads and a single writer to coexist — readers don’t block writers and vice versa. This dramatically reduces SQLITE_BUSY errors on read‑heavy workloads. n8n v2.0 shipped a new SQLite pooling driver that can be up to 10× faster for reads. [1] [10]

The most common PostgreSQL configuration failure: silent fallback to SQLite. n8n does not throw an error when it cannot connect to PostgreSQL — it silently reverts to SQLite. Verify PostgreSQL is active by checking the pod or container logs; the startup should mention “Connected to database: postgresdb” or similar. If the logs show SQLite or contain “database migration” followed by SQLite mentions, restart from a fresh state and validate the db:postgresdb ENV var is being read. A second common issue: PostgreSQL trigger flooding — a Postgres Trigger set to fire on every row change can flood n8n with executions, causing MaxClientsInSessionMode errors and workflow slowdowns. The fix is to move n8n to PostgreSQL, reduce trigger concurrency, or add buffering/batching to the trigger design. For the complete self‑hosted security blueprint, see the n8n Node Security Hardening guide.

References

This guide is for informational purposes only. Database defaults, environment variable names, and performance characteristics may change across n8n versions. Always refer to the official n8n database documentation for the most current configuration reference.

Leave a Reply

Your email address will not be published. Required fields are marked *