Why Self-Hosted n8n Is a Single Point of Failure
The moment you move off n8n Cloud, your automations stop being someone else's problem. Lead scoring, invoice chasing, Telegram alerts, the client-delivery pipeline you built last month — all of it lives in one Postgres database on one VPS. A provider doing a maintenance reboot, a disk filling up at 3 AM, an expired SSL certificate on the reverse proxy, or a well-meaning docker compose down -v turns every workflow you own into a memory.
And the recovery is not "rebuild it and hope." Every workflow you hand-built took hours of node-by-node fiddling. Credentials — OAuth tokens for Google, your Telegram bot token, the Stripe key — took an afternoon of resetting passwords and re-consenting to scopes. Rebuilding from scratch is possible but brutal, and it is the kind of afternoon that happens at the worst possible time.
So: three things to back up, one of which everybody forgets.
The Three Things That Actually Matter
A complete self-hosted n8n backup is small — measured in megabytes, not gigabytes, if you exclude execution history. It is made of exactly three artifacts, and two of them are useless without the third:
- Workflows — the node graphs themselves, exportable as JSON, one file per workflow.
- Credentials — encrypted in the database; exportable as ciphertext that needs the key below.
- The encryption key —
N8N_ENCRYPTION_KEY, or the generated key file in the~/.n8nfolder. This is the one people skip, and losing it makes every stored credential permanently unreadable. There is no reset, no master key, no support ticket that recovers it.
The failure mode to internalise
Restore a database dump onto a new server with a different encryption key and n8n starts up perfectly. The UI loads, every workflow is listed, the editor opens. Then the first run that touches a Google credential fails with a decryption error. Nothing looks wrong until it matters, and the only fix is the original key. Back up the dump and the key separately — they are two halves of one backup.
Exporting Workflows and Credentials with the CLI
n8n ships a server CLI that talks straight to the database and works even while n8n is stopped. The --backup flag is the one to learn: it expands to --all --pretty --separate, which is exactly what you want for versioning (one readable file per workflow instead of one unreadable blob).
On a bare-metal or npm install, the n8n command is directly available. On Docker, run it inside the container as the node user:
docker exec -u node n8n n8n export:workflow --backup --output=/home/node/.n8n/backups/workflows docker exec -u node n8n n8n export:credentials --backup --output=/home/node/.n8n/backups/credentials
Two details in that snippet matter. --separate writes one JSON file per entity, so a diff on a workflow you edited yesterday shows exactly which node changed. And note there is no --decrypted flag: credentials stay encrypted, which is correct — the export is safe to store, and only becomes readable with the matching key.
If you need a plain-text copy to migrate to an instance you cannot give the key to, the CLI does support export:credentials --all --decrypted. Treat that file as a live password file: it contains raw API keys and OAuth tokens, and it must never land in a public Git repo.
Scheduling It So You Never Think About It
A backup you run by hand is a backup that does not exist. Wire it into cron, and — critically — make it alert you when it stops working. A silent cron job that started failing eight weeks ago is the most common way people discover their backup was never really running.
0 3 * * * /opt/n8n-backup.sh >> /var/log/n8n-backup.log 2>&1 # and, in the same script, ship the output somewhere you will actually notice
Inside the script, the minimum viable backup is three commands:
- Export workflows and credentials to a dated directory, as above.
- Dump the database —
pg_dumpfor Postgres, or copydatabase.sqlitewhile n8n is stopped if you are still on SQLite. SQLite plus a running instance is a corrupt-backup generator; Postgres is the reason the official docs push you off it. - Copy the encryption key and push everything to off-site storage — S3, Backblaze, or a private git repo for the JSON exports.
The unskipped step: prove the restore
A backup you have never restored from is a hypothesis. Once a quarter, stand up a throwaway instance, import the exports, and confirm a workflow actually runs end to end with a live credential:
n8n import:workflow --separate --input=./backups/workflows n8n import:credentials --separate --input=./backups/credentials
Ten minutes of work, and it converts "I think I backed it up" into a fact you can rely on when a VPS disappears.
Restore Order Matters
Restoring is not the same commands in reverse — the order is what makes it work. Follow the documented sequence:
- Stop n8n first. Importing into a live instance writes into a database that is actively being read.
- Restore the database from your dump, or place
database.sqliteback into~/.n8n. - Restore the
.n8nfolder to its original location, including any community nodes you installed viaN8N_CUSTOM_EXTENSIONS. - Restore the deployment configuration — the database connection variables, the same
N8N_ENCRYPTION_KEY, and any external storage settings. Without these the instance starts and then cannot decrypt anything. - Start n8n and open one workflow that uses a credential.
If you are using workflow JSON exports rather than a full database dump, import credentials with the original key in place, and remember that imported workflows land inactive — you still have to publish or activate them before their schedules fire.
Version Control Your Workflows While You Are At It
Once the export produces one JSON file per workflow, put them in a git repo. Every workflow change becomes a reviewable commit with a diff, you can roll back a broken edit in seconds instead of reconstructing it, and a new instance — a client's server, a rebuild, a second VPS for redundancy — is an import:workflow --separate away instead of a fortnight of manual reconnection.
Keep credentials out of that repo. Version the workflow graphs, store the encrypted credential export and the encryption key somewhere access-controlled and separate, and the whole thing becomes a recovery plan instead of a pile of hope.
The Five-Minute Checklist
- Workflows exported to JSON, one file per workflow, on a schedule.
- Credentials exported encrypted, stored off-site.
- Encryption key backed up separately from the database dump.
- Database dumped (pg_dump on Postgres, stopped instance on SQLite).
- A restore actually rehearsed, with the backup job's failure reported to you.
Get those five right and a dead VPS costs you an afternoon of restoring instead of a month of rebuilding.