What changes with n8n 3.0 and why act now
The official n8n 3.0 breaking changes documentation is unambiguous: "Self-hosted n8n will require a Docker-based deployment. n8n 3.0 will no longer support installations run using npm or npx n8n." This is not a gradual deprecation warning — it is a hard deadline. In October 2026, any instance launched via npx n8n or a global npm package will no longer be updatable. It will be frozen on the last 2.x version, without security patches.
Risks of delaying migration to October
- Migration under pressure — migrating in an emergency while critical automations are running exposes you to configuration errors that are difficult to diagnose.
- SQLite data loss — GitHub issue #22341 documents cases where Docker containers updated without precautions caused a database regression: workflows and executions "go back in time" to the state of an old backup.
- No security patches — an npm instance frozen at 2.x no longer receives security patches or stability fixes published for the 3.x branch.
- Growing incompatibility — integrations, community nodes and webhooks rely on APIs that evolve; staying on a dead version creates growing incompatibility debt.
- Unpredictable duration — a well-prepared migration takes one hour; an improvised migration can take a full day, during which your workflows are stopped.
Prerequisites before starting
This procedure targets an existing n8n instance in production. If you are starting from scratch, refer to the dedicated article on installing n8n on a VPS.
What you need
- VPS with root access — Ubuntu 22.04 or Debian 12 recommended, minimum 2 vCPU and 2 GB RAM for n8n alone, 4 GB if you add PostgreSQL on the same host.
- Docker Engine and Docker Compose v2 — verify with
docker --versionanddocker compose version(no-hyphen syntax, v2 plugin). - PostgreSQL recommended — n8n supports both SQLite and PostgreSQL, but SQLite on Docker carries data loss risks during poorly managed updates (cf. issue #22341); PostgreSQL is the target for any instance that matters.
- Access to the current npm instance — the migration requires exporting workflows via the REST API before stopping the old instance.
- A domain name and a TLS certificate — n8n in production is not exposed over raw HTTP; Nginx acts as a reverse proxy with Let's Encrypt.
- A planned maintenance window — even a short one avoids losing in-progress executions.
Detect your current launch mode
Before anything else, identify precisely how your n8n instance is launched. The command to use depends on the supervision mode.
Detect, export, deploy and validate
Identify the n8n process
Look for the running executable: which n8n shows the path if n8n is installed globally via npm. Then check if a system service supervises it: systemctl status n8n or systemctl status n8n.service. If no systemd service exists, look for an active process: ps aux | grep n8n. A result containing node .../n8n/bin/n8n or npx n8n confirms an npm installation.
Locate the configuration file and database
The default data directory is ~/.n8n/. Check its contents: ls -la ~/.n8n/. The database.sqlite file indicates an SQLite database. Note the full path — you will need it for the export. If the N8N_USER_FOLDER variable is set in the process environment (cat /proc/$(pgrep -f n8n)/environ | tr '\0' '\n' | grep N8N), that path takes precedence.
Export all your workflows via the REST API
The n8n REST API allows exporting workflows as JSON. First retrieve an API key from the interface (Settings → API → Create API Key), then export: curl -s -H 'X-N8N-API-KEY: YOUR_KEY' http://localhost:5678/api/v1/workflows | python3 -m json.tool > workflows-export-$(date +%Y%m%d).json. Verify the file contains your workflows: python3 -c "import json; d=json.load(open('workflows-export-*.json')); print(len(d['data']), 'workflows exported')". Keep this file safe before any operation.
Cleanly stop the npm instance
If supervised by systemd: systemctl stop n8n && systemctl disable n8n. If launched manually in a terminal or via a startup script, identify the PID (pgrep -f n8n) then kill -SIGTERM <PID>. Wait a few seconds for n8n to finish in-progress executions before forcing the stop. Once stopped, back up ~/.n8n/database.sqlite if you want to preserve execution history.
Create the docker-compose.yml file with PostgreSQL
Create a dedicated directory: mkdir -p /opt/n8n && cd /opt/n8n. Then create the docker-compose.yml file with the following content — adapt passwords and domain:
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: CHANGE_THIS_PASSWORD
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:2.38.4
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
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_HOST: your-domain.com
N8N_PORT: 5678
N8N_PROTOCOL: https
WEBHOOK_URL: https://your-domain.com/
N8N_BASIC_AUTH_ACTIVE: "true"
N8N_BASIC_AUTH_USER: admin
N8N_BASIC_AUTH_PASSWORD: CHANGE_THIS_AUTH_PASSWORD
volumes:
- n8n_data:/home/node/.n8n
ports:
- "127.0.0.1:5678:5678"
volumes:
postgres_data:
n8n_data:Note: the version is pinned to 2.38.4 (stable as of 2026-09-09). Never use :latest — see the tip below.
Start the stack and import workflows
Start the stack: docker compose up -d. Wait for both containers to be healthy: docker compose ps. Once n8n is accessible at http://127.0.0.1:5678, import your workflows via the API: curl -s -X POST -H 'X-N8N-API-KEY: YOUR_NEW_KEY' -H 'Content-Type: application/json' -d @workflows-export-YYYYMMDD.json http://127.0.0.1:5678/api/v1/workflows. Verify in the interface that your workflows, connections and credentials are present.
Configure Nginx as a reverse proxy with TLS
Install Nginx and Certbot if not already done: apt install nginx certbot python3-certbot-nginx -y. Create the Nginx configuration in /etc/nginx/sites-available/n8n with a proxy_pass to http://127.0.0.1:5678, including WebSocket upgrade headers and a 300s read timeout. Enable the site and obtain the certificate: ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/ && certbot --nginx -d your-domain.com.
Validate that the migration succeeded
Perform these checks in order: 1) access https://your-domain.com — the login page appears without a TLS warning; 2) log in and verify your workflows are present and active; 3) manually trigger a simple workflow to validate end-to-end execution; 4) check webhooks: if third-party services point to your old URL or old port, update them in n8n (Settings → Webhooks); 5) let it run for 24 hours and review the logs: docker compose logs n8n --since 24h | grep -i error.
Always pin a version, never :latest
Using n8nio/n8n:latest in your docker-compose.yml exposes you to uncontrolled automatic updates during a docker compose pull. On an SQLite database, a major version jump without prior migration can trigger the scenario described in issue #22341: data appears present in the volume but the database reverts to an earlier state. Always pin a specific version (n8nio/n8n:2.38.4) and plan your upgrades. To move to a new version, read the release notes first, then: docker compose pull && docker compose up -d.
Troubleshooting: common issues after migration
Here are the most frequently encountered problems during this transition.
Issues and solutions
- Empty workflows after import — verify that the exported JSON format matches what the import API expects; some n8n versions export an
{ data: [] }object, others a direct array. Adapt thecurlcommand accordingly. - Webhooks no longer responding — the
WEBHOOK_URLvariable must match exactly the public URL of your instance (withhttps://). Incorrect setup generates wrong webhook URLs in the interface. - Inaccessible credentials — credentials are encrypted with the
N8N_ENCRYPTION_KEY. If you do not set it explicitly and start from a newn8n_datavolume, old credentials are lost. Retrieve the key from~/.n8n/.n8n_encryption_keyon the npm instance and set it as an environment variable. - SQLite database regression (issue #22341) — if you chose to keep SQLite temporarily, ensure the Docker volume is mounted persistently and that you are not using
--rmor an aggressive restart policy. Migration to PostgreSQL remains the definitive resolution. ECONNREFUSEDerror on PostgreSQL — thedepends_on.postgres.condition: service_healthycondition and thepg_isreadyhealthcheck ensure n8n waits for PostgreSQL to be ready. Without this condition, n8n starts before PostgreSQL and fails.
A migration to do now, not in October
The stable version of n8n at the time of this article is 2.38.4. You have several weeks to conduct this migration properly: cleanly export your workflows, test the Docker stack on a test server, then switch production with a real rollback plan. In October, when n8n 3.0 is available, you will only need to bump the version number in your docker-compose.yml — a five-minute step. The difference between five minutes and a stressful day starts now.