Skip to content
SelfHostingN8N

Self-hosting

How to Update n8n Safely: Pinning, Rollbacks & Breaking Changes

A safe update routine for self-hosted n8n: version pinning, reading release notes, database migrations, and how to roll back when an upgrade goes wrong.

n8n ships fast — minor releases most weeks, patches in between. That pace is great for features and security fixes, and it’s exactly why image: n8nio/n8n:latest plus a casual docker compose pull is how self-hosted instances break. One morning a workflow that ran for months starts failing, and you discover you jumped four versions overnight, one of which changed behaviour you depended on.

Updating safely isn’t hard. It’s a 10-minute routine with three rules: pin your version, back up before you jump, know how to roll back. Here’s the whole thing.

Rule 1 — Pin the version

In docker-compose.yml, never run production on :latest:

  n8n:
    # ❌ image: docker.n8n.io/n8nio/n8n:latest
    image: docker.n8n.io/n8nio/n8n:1.99.1   # ✅ explicit version

With a pinned tag, docker compose pull is a no-op until you edit that line. Updates happen when you decide, with release notes read and a backup taken — not whenever a container restarts and re-resolves latest.

To see what you’re currently running:

docker compose exec n8n n8n --version

Rule 2 — Read before you jump

Two minutes on the release notes before any update. You’re scanning for exactly three things:

  1. Breaking changes — usually listed explicitly. Renamed environment variables, node behaviour changes, dropped Node.js versions.
  2. Database migrations — larger releases migrate the DB schema on first boot. Migrations are one-way: once a newer n8n has touched the database, older versions may refuse to run against it. This is why the backup in Rule 3 isn’t optional.
  3. Deprecations — warnings about things that will break next time. Note them now, fix them calmly.

Special case: major version jumps (e.g. crossing from 1.x to a 2.x line). Never do these blind, and never skip across many minors in one leap if you can help it — walking up through a couple of intermediate versions makes it obvious which change broke something.

The update routine

This is the whole ceremony. With practice it takes under ten minutes.

1. Back up. Database + .env. If you followed our backup guide this is one command:

~/n8n/backup.sh

2. Note the current version (your rollback target):

docker compose exec n8n n8n --version   # e.g. 1.99.1

3. Edit the pin in docker-compose.yml to the new version:

    image: docker.n8n.io/n8nio/n8n:1.100.1

4. Pull and recreate. Volumes persist; only the container is replaced:

cd ~/n8n
docker compose pull n8n
docker compose up -d n8n

5. Watch the boot. This is where migrations run and where problems announce themselves:

docker compose logs -f n8n

Healthy output shows migrations completing and the editor coming up. Errors here almost always mean a failed migration or a config variable the new version treats differently.

6. Smoke-test. Open the editor, then manually run one workflow that touches a credential (an API call, a database query). Check that webhook-triggered workflows still fire — webhooks are the most common casualty of config regressions.

7. Commit the change. Your docker-compose.yml should live in a private git repo; the version bump is a one-line diff with history:

git add docker-compose.yml && git commit -m "n8n 1.99.1 -> 1.100.1"

Rolling back when it goes wrong

Two scenarios, very different severity.

The new version misbehaves but the database is compatible (most cases — patch/minor hops): revert the image tag and recreate.

# docker-compose.yml: set image back to the previous tag
docker compose up -d n8n

If it boots clean and workflows run — done. This is the payoff of noting the version in step 2.

The old version refuses to start (log lines about migrations or schema mismatch): the new release migrated the database past the old version’s understanding. Now the rollback is a restore:

docker compose down
docker volume rm n8n_postgres_data          # drop the migrated DB
docker compose up -d postgres
gunzip -c backups/n8n-db-$(date +%F).sql.gz | \
  docker compose exec -T postgres psql -U n8n -d n8n
docker compose up -d

Same encryption key, pre-update dump, old image tag → you’re back exactly where you were before the jump. Executions that ran between backup and rollback are lost from history; workflow definitions are whatever the dump contains.

Don’t forget the rest of the stack

n8n isn’t the only image in the Compose file:

  • Postgres: pin the major (postgres:16) and let patch updates flow with docker compose pull. Jumping majors (16 → 17) is a separate, deliberate operation: dump with the old, restore into the new — never just change the tag, the data directory format differs.
  • Caddy: caddy:2 is fine to pull routinely; it’s stable and config-compatible within the major.
  • The host OS: sudo apt update && sudo apt upgrade monthly, and enable unattended-upgrades for security patches — covered in Securing Self-Hosted n8n.

A calendar, not a vibe

The failure mode of self-hosted software isn’t one bad update — it’s no updates for a year, followed by a forced, terrifying one. Put 15 minutes on the calendar every two weeks:

  • Skim release notes since your pinned version
  • Run backup, bump pin, pull, recreate, smoke-test
  • apt upgrade the host while you’re there
  • Glance at docker compose logs and disk space (df -h)

Do this and version updates stay what they should be: a one-line diff and a coffee. Skip it for six months and you’ve built yourself a migration project.

If an update does leave you with mysterious breakage — webhooks that stopped firing, “connection lost” banners — work through our troubleshooting guide before rolling back; most post-update issues are config, not corruption.