← All guidesGitHub
Workflow Automation

Migrate n8n from SQLite to PostgreSQL on DigitalOcean

Move a self-hosted n8n instance from SQLite to DigitalOcean Managed PostgreSQL with a planned cutover, restricted database access, and a recovery test.

Jump to stepsGitHub: TylorMayfield/n8n-digitalocean-postgres-migration

Decide whether to migrate

SQLite works for a quiet n8n installation. Use PostgreSQL when several people administer workflows, execution history matters, or an outage has a real cost. It moves n8n data out of the Droplet. It does not make a risky workflow safe or replace a recovery plan.

An empty PostgreSQL database can let n8n start without your workflows, credentials, or history. Schedule a maintenance window, keep the n8n encryption key, and leave the SQLite data alone until the team accepts the cutover.

You need DigitalOcean administrator access, SSH access to the Linux Droplet running n8n, and a separate place to test a restore. A Droplet is the virtual server. A trusted source is a network rule that allows a server or administrator address to connect to the database.

  • A tested recovery archive and its matching n8n encryption key.
  • A maintenance window that pauses incoming webhooks and scheduled production work.
  • A named owner who can verify credentials and approve the final cutover.

Capture a recovery point before changing the database

Start with a backup you can restore. Record the n8n version, Compose image tag, public URL, encryption-key location, volume name, and final backup timestamp. Read one encrypted archive in the isolated recovery environment from the private n8n guide. Keep the same n8n version for the migration. Do not combine a database move with an n8n upgrade.

If you used the DigitalOcean Marketplace n8n app from the companion recovery guide, the deployment directory is usually /opt/n8n-docker-caddy. If you use another setup, find the Compose file before editing anything. The service name from docker compose config --services replaces n8n in the later commands.

Open a root shell on the Droplet using the action below before these commands. Keep that terminal for the migration and replace N8N_APP_DIR if your path differs.

On the n8n Droplet: enter a root shellConnected Droplet
sudo -i

Before continuing: Inspect the returned paths before changing directories. The command only lists candidate Compose files.

Find the Compose directory and n8n service on the DropletOn the n8n server
sudo find /opt /srv /home -maxdepth 4 -type f \( -name compose.yml -o -name docker-compose.yml \) -print
N8N_APP_DIR=/opt/n8n-docker-caddy
cd "$N8N_APP_DIR"
sudo docker compose config --services

Create a restricted PostgreSQL database

Create the cluster first. Do not change n8n yet. This guide uses a Standard Edition PostgreSQL cluster because its connection details include a CA certificate that n8n can mount. If you choose Advanced Edition, stop here and adapt the TLS setup to its system trust store instead of copying the CA-file steps below.

Then return to the n8n Droplet's SSH terminal. Set the actual local data-volume and backup paths in migration.env. The command uses a subshell so your terminal remains in the deployment directory. Check pwd before exporting.

  1. In your DigitalOcean project, open Databases and click Create Database. You can also use Create, then Managed Database. On the Create Database Cluster page, select PostgreSQL and a version. The engine is fixed. Supported major-version upgrades are possible later, after compatibility checks and a separate recovery plan.
  2. Choose the same datacenter region as the n8n Droplet. Select the plan and storage that fit the current workload, enter n8n-postgres as the cluster name, choose the project that contains n8n, then click Create Database Cluster. Wait for the cluster status to become Online.
  3. Open the new cluster, then select Users & Databases. In Databases, enter n8n in Add new database and click Save. In Users, enter n8n_app in Add new user and click Save. Use n8n_app for n8n. Leave doadmin for administration and recovery work.
  4. Select Network Access, click Add Trusted Sources, choose Quick select Droplets, and select the n8n Droplet. Click Add Trusted Sources. For a one-time command-line check, add your own current IPv4 address from the same dialog. Remove that address after the check. Do not add 0.0.0.0/0.
  5. Return to the cluster Overview and open Connection Details. Copy the host, port, database name, n8n_app user name, and password into a password manager. Download the CA certificate and copy it to the n8n Droplet later. Do not paste any of these values into Git, workflow notes, or screenshots.
Run non-destructive preflight checks from the companion repositoryOn the n8n server
(
  cd /root
  git clone https://github.com/TylorMayfield/n8n-digitalocean-postgres-migration.git
  cd n8n-digitalocean-postgres-migration
  install -m 600 migration.env.example migration.env
)
  1. On the n8n Droplet: set paths and PostgreSQL URLFile editor
    sudo nano /root/n8n-digitalocean-postgres-migration/migration.env
Check the recovery files, then return to the appConnected Droplet
sudo /root/n8n-digitalocean-postgres-migration/scripts/preflight.sh /root/n8n-digitalocean-postgres-migration/migration.env
cd "$N8N_APP_DIR"
pwd

Perform a planned cutover

Block inbound traffic and disable scheduled work before starting this sequence. Leave n8n running long enough for the script to read its UID and GID; the script then stops it before export. Keep the same n8n image and encryption key throughout. Export must finish successfully before changing database settings.

The .env file sits beside the Compose file. It holds values that Compose passes into the n8n container. Add the PostgreSQL values there. Then add the matching environment entries and a read-only CA-certificate mount to the n8n service in compose.yml or docker-compose.yml. A .env file by itself does not give a running container new variables unless the Compose file references them.

Export SQLite before changing configurationOn the n8n server
cd "$N8N_APP_DIR"
set -euo pipefail
sudo apt-get update
sudo apt-get install -y unzip
if [[ -f compose.yml ]]; then COMPOSE_FILE=compose.yml; elif [[ -f docker-compose.yml ]]; then COMPOSE_FILE=docker-compose.yml; else echo 'No Compose file'; exit 1; fi
sudo cp .env .env.sqlite-backup
sudo chmod 600 .env .env.sqlite-backup
sudo cp "$COMPOSE_FILE" "$COMPOSE_FILE.sqlite-backup"
N8N_UID=$(sudo docker compose exec -T n8n id -u)
N8N_GID=$(sudo docker compose exec -T n8n id -g)
sudo install -d -o "$N8N_UID" -g "$N8N_GID" -m 700 migration-entities
sudo install -d -m 700 secrets
sudo tee compose.migration.yml > /dev/null <<'YAML'
services:
  n8n:
    volumes:
      - ./migration-entities:/migration
YAML
sudo docker compose stop n8n
sudo docker compose -f "$COMPOSE_FILE" -f compose.migration.yml run --rm --no-deps n8n export:entities --outputDir=/migration
sudo test -s migration-entities/entities.zip
sudo unzip -t migration-entities/entities.zip
# Continue only after export exits successfully and the directory contains the exported entities.

Before continuing: This changes the database n8n uses at startup. Preserve the existing .env and Compose files, keep the existing encryption key, and use the new database only after the entity export succeeds.

  1. Edit the environment fileFile editor
    sudo nano .env
  2. Edit the Compose file
    sudo nano $COMPOSE_FILE

Before continuing: Export SQLite first. Run this import only once against a new, empty PostgreSQL database. It writes the exported entities and disables workflows before startup.

Import into PostgreSQL and disable workflowsOn the n8n server
set -euo pipefail
# Save the DigitalOcean CA certificate as secrets/do-postgres-ca.crt first.
sudo chmod 644 secrets/do-postgres-ca.crt
# The CA is public verification material; keep database passwords in protected .env.
sudo docker compose -f "$COMPOSE_FILE" -f compose.migration.yml run --rm --no-deps n8n import:entities --inputDir=/migration
# Disable imported schedules/webhooks before the first application start.
sudo docker compose -f "$COMPOSE_FILE" run --rm --no-deps n8n update:workflow --all --active=false
sudo docker compose -f "$COMPOSE_FILE" up -d

Prove the result before reopening production traffic

Sign in as an administrator. Open a known workflow, decrypt a known credential, and complete one harmless manual run. Keep production workflows disabled until these checks pass. If a credential fails to decrypt, restore the old service. Do not reset credentials during the investigation.

Then test database recovery in the control panel. Open Databases, select the n8n cluster, choose Actions, then Restore from backup. Choose a recent point, give the restored cluster a test name, and select Restore to New Cluster. When it is online, add only your administrator address under Network Access, copy its new connection details, and run a read-only psql check. This proves the database recovery point, not that every workflow is safe to run.

Before continuing: Use the restored cluster's connection details, not the production connection, for the recovery check.

Verify the cutover and the restored test clusterOn the n8n server
Replace every highlighted value before running this command.
# On the n8n Droplet, check the running service and its startup log.
sudo docker compose ps
sudo docker compose logs --tail=100 n8n
# On an administrator machine with the PostgreSQL client installed, use the restored cluster's new Connection Details.
psql "postgresql://n8n_app:YOUR_PASSWORD@RESTORED_CLUSTER_HOST:RESTORED_CLUSTER_PORT/n8n?sslmode=require" \
  -c 'select current_database(), current_user, now();'
# Then open one workflow, decrypt one credential, and run one harmless manual test. Record the result.

Roll back to SQLite before resuming traffic

Use this rollback only while production traffic is paused and no new production data has been accepted in PostgreSQL. It restores the saved configuration and original SQLite volume. Keep incoming work blocked during verification. If production writes have resumed, stop and plan reconciliation; this rollback would lose the new data.

Restore the saved SQLite configuration during the maintenance windowConnected Droplet
cd "$N8N_APP_DIR"
sudo docker compose -f "$COMPOSE_FILE" stop n8n
sudo test -f .env.sqlite-backup
sudo test -f "$COMPOSE_FILE.sqlite-backup"
sudo cp .env.sqlite-backup .env
sudo cp "$COMPOSE_FILE.sqlite-backup" "$COMPOSE_FILE"
sudo docker compose -f "$COMPOSE_FILE" run --rm --no-deps n8n update:workflow --all --active=false
sudo docker compose -f "$COMPOSE_FILE" up -d n8n
sudo docker compose -f "$COMPOSE_FILE" ps

Know what Managed PostgreSQL does not cover

Managed PostgreSQL runs the database and provides backups and infrastructure recovery. You still own access rules, workflow logic, credentials, and the decision to run a workflow after recovery. Review that split after the first restore test, not during an outage.

If n8n handles important customer data or irreversible actions, document a recovery-time target and acceptable data loss. The database is now part of an operational system, not a cosmetic upgrade.

Check your result

Expected result
An administrator can open a known workflow, decrypt a known credential, complete one harmless manual test, and connect to the new PostgreSQL database as the dedicated application user.
Stop if
Stop if the backup cannot be read, the encryption key is unavailable, the database permits unrestricted access, credentials fail to decrypt, or any production workflow can create new state before validation completes.
Next step
Record the cutover and recovery-test result, remove temporary administrator database access, and retain the old SQLite recovery point for the approved period.