n8n on SQLite or PostgreSQL? Choosing the Database for Production

Every n8n instance stores its workflows, credentials and execution history in a database. Out of the box, n8n uses SQLite: a single file on disk, with nothing extra to install. That is convenient for a first test. For a production instance that runs webhooks and scheduled jobs all day, the choice deserves a closer look. This guide compares the two supported options, shows the correct PostgreSQL configuration, and covers the two things that decide whether your instance stays healthy over time: execution data pruning and backups.
What n8n keeps in the database
Workflows and their versions: small, change rarely.
Credentials: encrypted with the instance's encryption key.
Execution data: the input and output of every node for every saved run. This is the part that grows, and on a busy instance it is almost all of the database size.
So the real question is not "which database is faster", but "which database handles a steady stream of writes and a growing execution table, and is easy to back up".
SQLite: where it is fine and where it hurts
SQLite is a solid database. For a personal instance, a test environment or a few workflows that run a handful of times a day, it works well. There is no separate service to secure or monitor. The database lives in ~/.n8n/database.sqlite, or /home/node/.n8n/database.sqlite inside the official Docker image.
The limits appear as load grows:
One writer at a time. SQLite locks the database for each write. Many parallel executions and webhook calls end up waiting for each other, and you may see
SQLITE_BUSY: database is lockedin the logs.The file does not shrink by itself. Deleted executions leave free pages inside the file. The disk usage stays high until you run a vacuum. You can set
DB_SQLITE_VACUUM_ON_STARTUP=true, but on a large file that makes every restart slow.No queue mode. Scaling n8n with separate worker processes needs a database that several processes can share. n8n's documentation recommends PostgreSQL for this setup.
Backups need care. Copying the file while n8n is writing to it can give you a corrupt copy.
PostgreSQL: the production default
PostgreSQL handles many concurrent writes, reclaims space with autovacuum, supports online backups with pg_dump, and is the database n8n recommends for queue mode. It costs a little more memory (as a rule of thumb, plan a few hundred MB for a small PostgreSQL instance) and one more service to run. For any instance that a team or a client depends on, that trade is worth it.
MySQL and MariaDB support was deprecated in n8n 1.0, so for new production setups the practical choice is between SQLite and PostgreSQL.
Configure n8n for PostgreSQL
n8n reads its database settings from environment variables. This Docker Compose file runs PostgreSQL and n8n together:
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: change-this-password
POSTGRES_DB: n8n
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: change-this-password
N8N_ENCRYPTION_KEY: replace-with-a-long-random-string
EXECUTIONS_DATA_PRUNE: "true"
EXECUTIONS_DATA_MAX_AGE: 168
EXECUTIONS_DATA_PRUNE_MAX_COUNT: 10000
GENERIC_TIMEZONE: UTC
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
volumes:
pg_data:
n8n_data:
A few details matter here:
DB_TYPEmust be exactlypostgresdb, notpostgresorpostgresql. If the variable is missing, n8n starts on SQLite without warning, so check the startup logs.DB_POSTGRESDB_HOSTis the service name,postgres. Inside a container,localhostis the container itself, which givesError: connect ECONNREFUSED 127.0.0.1:5432.POSTGRES_PASSWORDonly applies the first time the data volume is created. If you change it later, you getpassword authentication failed for user "n8n". Change the password inside PostgreSQL withALTER USER, or start with a fresh volume.Port 5678 is bound to
127.0.0.1, so n8n is reachable only through a reverse proxy with HTTPS, and PostgreSQL is not published at all.In production, move passwords and the encryption key into an
.envfile with restricted permissions instead of the compose file.
Generate a strong encryption key once and keep it safe:
openssl rand -hex 32
Prune execution data before it fills the disk
Pruning is enabled by default in n8n 1.x. It deletes executions older than EXECUTIONS_DATA_MAX_AGE hours (default 336, which is 14 days) and keeps at most EXECUTIONS_DATA_PRUNE_MAX_COUNT executions (default 10,000). Set both to match how far back you really debug. Seven days is enough for many teams.
You can also save less in the first place:
EXECUTIONS_DATA_SAVE_ON_SUCCESS=none
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=false
N8N_DEFAULT_BINARY_DATA_MODE=filesystem
Saving only failed runs keeps the information you need for debugging and drops the rest. Filesystem mode stores binary data such as files and images on disk instead of in memory and the database. If you need a full audit trail for one workflow, override the save settings in that workflow's settings rather than for the whole instance.
To see how big the execution data is in PostgreSQL:
docker compose exec postgres psql -U n8n -d n8n -c "SELECT pg_size_pretty(pg_total_relation_size('execution_data'));"
Back up the database and the key
For PostgreSQL, pg_dump makes a consistent backup while n8n keeps running:
docker compose exec -T postgres pg_dump -U n8n -Fc n8n > n8n-$(date +%F).dump
To restore, stop n8n first (docker compose stop n8n), then run:
docker compose exec -T postgres pg_restore -U n8n -d n8n --clean --if-exists < n8n-2026-10-25.dump
For SQLite, use the built-in online backup instead of copying the file:
sqlite3 /home/node/.n8n/database.sqlite ".backup '/home/node/.n8n/backup.sqlite'"
Two rules apply to both databases. First, back up the encryption key. Without N8N_ENCRYPTION_KEY (or the generated key in /home/node/.n8n/config), the credentials in your backup cannot be decrypted. Second, keep a copy off the server and test a restore at least once. A backup you have never restored is a guess.
Moving from SQLite to PostgreSQL
The simplest path is to export workflows and credentials with the n8n CLI, point n8n at PostgreSQL, and import them again. Use the same encryption key on the new setup so the credentials stay readable.
docker compose exec n8n n8n export:workflow --all --output=/home/node/.n8n/workflows.json
docker compose exec n8n n8n export:credentials --all --output=/home/node/.n8n/credentials.json
# switch DB_TYPE to postgresdb, restart, then:
docker compose exec n8n n8n import:workflow --input=/home/node/.n8n/workflows.json
docker compose exec n8n n8n import:credentials --input=/home/node/.n8n/credentials.json
Execution history is not moved this way, which is usually fine. Check that workflows are active after the import, and activate them again if needed.
The short answer
Use SQLite for learning, testing and light personal use. As a rule of thumb, move to PostgreSQL once a team depends on the instance, you handle more than a few thousand executions a day, or you plan to use queue mode. Whatever you choose, prune execution data and back up both the database and the encryption key.
Run it on IM HOST
IM HOST n8n Hosting comes with n8n pre-installed on PostgreSQL with HTTPS, so you start on the production setup described above. There is no limit on workflows or executions, and you keep full root access to tune pruning and backups yourself. If you prefer to build your own stack, Cloud VPS plans offer dedicated vCPU and RAM, a free automatic backup every 3 days, and upgrades anytime. Our engineers are available 24/7, and there are no setup fees.
More from our blog
Discover more practical guides and product insights from the IM Host team.
View all articles