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:
- Breaking changes — usually listed explicitly. Renamed environment variables, node behaviour changes, dropped Node.js versions.
- 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.
- 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 withdocker 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:2is fine to pull routinely; it’s stable and config-compatible within the major. - The host OS:
sudo apt update && sudo apt upgrademonthly, and enableunattended-upgradesfor 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 upgradethe host while you’re there - Glance at
docker compose logsand 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.