n8n with Postgres instead of SQLite: when and how to switch

Why production n8n instances should run on PostgreSQL, and how to configure and migrate safely in Docker.

n8n ships with SQLite by default, which is perfect for trying it out. Once workflows matter to your business, PostgreSQL is the better backend: it handles concurrent executions, larger execution history and proper backups much more gracefully.

Signs you should switch

  • Workflows run frequently and execution history grows large
  • You see “database is locked” errors under load
  • You want queue mode with separate worker containers
  • You need reliable, consistent backups while n8n is running

A Compose setup with Postgres

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: change-me
      POSTGRES_DB: n8n
    volumes: [pg_data:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 10s
  n8n:
    image: n8nio/n8n:latest
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: change-me
      N8N_ENCRYPTION_KEY: a-long-random-string
      WEBHOOK_URL: https://n8n.example.com/
    depends_on:
      postgres: { condition: service_healthy }
    volumes: [n8n_data:/home/node/.n8n]
volumes:
  pg_data:
  n8n_data:

The encryption key is critical

n8n encrypts stored credentials with N8N_ENCRYPTION_KEY. If you lose it or it changes, every saved credential becomes unreadable. Set it explicitly, store it in a password manager, and use the same value when migrating.

Migrating from SQLite

  1. Export workflows and credentials using the n8n CLI (n8n export:workflow --all and n8n export:credentials --all) from the old instance.
  2. Note the existing encryption key from the old .n8n/config file.
  3. Start the new instance with Postgres and the same encryption key.
  4. Import with n8n import:workflow and n8n import:credentials.
  5. Test a few workflows, then switch the webhook domain.

Prune execution data

Execution history grows fast. Enable pruning:

EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=168

This keeps a week of history and stops the database from ballooning.

Webhook URL

Set WEBHOOK_URL to the public HTTPS address. Otherwise n8n generates webhook URLs with internal hostnames and integrations fail to call back.

Backups

Back up Postgres with pg_dump nightly and keep the encryption key separately. Without the key, a database backup restores workflows but not credentials.

Scaling later

With Postgres in place, you can enable queue mode with Redis and run separate worker containers when a single process is no longer enough.

Common errors after switching

  • Connection refused on start: n8n started before Postgres was ready. The health check and depends_on condition above fix this.
  • Credentials show as invalid: the encryption key differs from the old instance. Restore the original key rather than re-entering every credential.
  • Webhooks return 404: the workflow is not active, or the proxy strips the path. Check that test and production webhook URLs both point at the public domain.
  • Slow editor with big executions: large binary data is stored in the database. Set N8N_DEFAULT_BINARY_DATA_MODE=filesystem so files go to the volume instead.

A step by step deployment including domain and SSL is in this guide to deploying n8n with PostgreSQL.

Disclosure: I work on Peon, which has an n8n template, but the configuration above is plain Docker Compose.


Write a comment