IAnswerable

Move n8n from SQLite to Postgres

28 Nov 2025

n8n defaults to SQLite. That’s fine for trying it out and dead wrong for anything you actually depend on. SQLite locks the entire database file on writes, so once you have more than a couple of workflows running concurrently — or one workflow with heavy execution logging — you start seeing SQLITE_BUSY errors and workflows that silently stop saving execution data. Postgres fixes this. Here’s the migration without losing your existing workflows.

Why SQLite breaks first

n8n writes to its database constantly: every workflow execution, every node’s output (if you have execution data saving turned on), every credential change. SQLite handles one writer at a time. Two workflows finishing around the same moment is enough to trigger lock contention. It gets worse under queue mode, where multiple workers all try to write execution results back to the same SQLite file — that setup basically requires Postgres to function correctly at all.

1. Stand up Postgres

If you’re running n8n via Docker Compose, add a Postgres service:

services:
  postgres:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: your_secure_password
      POSTGRES_DB: n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

2. Back up the existing SQLite database first

Before touching anything, copy the current database file. If the migration goes sideways, this is your rollback:

docker compose exec n8n cp /home/node/.n8n/database.sqlite /home/node/.n8n/database.sqlite.bak
docker cp $(docker compose ps -q n8n):/home/node/.n8n/database.sqlite.bak ./database.sqlite.bak

3. Export your workflows and credentials

n8n’s own export is more reliable than trying to move the database file directly, because it doesn’t depend on both databases having identical internal schemas:

docker compose exec n8n n8n export:workflow --all --output=/home/node/.n8n/backup/workflows.json
docker compose exec n8n n8n export:credentials --all --output=/home/node/.n8n/backup/credentials.json

Copy both files out of the container to somewhere safe.

4. Point n8n at Postgres

Set the database environment variables on the n8n service:

services:
  n8n:
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: your_secure_password
    depends_on:
      - postgres

Restart n8n. On startup, it runs its own migrations against the new, empty Postgres database — no manual schema work needed.

docker compose up -d
docker compose logs -f n8n

Watch the logs for migration errors before moving on. A clean start shows a series of Migration X completed lines.

5. Re-import workflows and credentials

docker compose exec n8n n8n import:workflow --input=/home/node/.n8n/backup/workflows.json
docker compose exec n8n n8n import:credentials --input=/home/node/.n8n/backup/credentials.json

Credentials are encrypted with n8n’s encryption key — if you changed or lost N8N_ENCRYPTION_KEY between the old and new setup, decryption fails on import and every credential comes across broken. Keep the same key.

6. Verify before you delete anything

Open the UI, confirm workflows are present, and manually trigger one to make sure execution logging actually writes to Postgres:

docker compose exec postgres psql -U n8n -d n8n -c "SELECT COUNT(*) FROM execution_entity;"

A non-zero count after a test run confirms the new database is live and working. Only then remove the old SQLite file and its backup copies from anywhere you don’t need them.

What you get once Postgres is in place

Concurrent workflow executions stop fighting over a file lock. It also unlocks queue mode — running n8n with separate worker processes pulling jobs off a shared queue — which SQLite fundamentally cannot support since workers on different processes can’t safely share a single SQLite file the way they can share a network database connection. If you’re planning to scale past a single n8n instance, this migration is the prerequisite step, not an optional one.