How to back up and restore n8n
A complete n8n backup is the .n8n folder, which holds the config file with the encryption key and, by default, the SQLite database database.sqlite, plus a pg_dump if you run n8n on PostgreSQL. Without the encryption key, a restored instance cannot decrypt any saved credential. n8n export:workflow and n8n export:credentials add readable JSON copies, but they leave out users, execution history and variables, so they don't replace the full backup.
What a complete n8n backup includes
n8n's own guide splits a complete backup into two parts: the .n8n folder, and the PostgreSQL database if you use one instead of SQLite.
| What | Back up? | Why |
|---|---|---|
.n8n/config | Yes, privately | A small JSON file with encryptionKey, the key every saved credential is encrypted with. |
.n8n/database.sqlite | Yes, with VACUUM INTO | The default database: workflows, credentials, users, variables, settings and execution history. |
| PostgreSQL database | Yes, with pg_dump | Takes the place of the SQLite file when DB_TYPE=postgresdb. |
.n8n/nodes/ | Yes | Community nodes, installed from the editor or with npm. |
.n8n/binaryData/, .n8n/storage/ | Yes, if present | Execution files, when binary or execution data is kept on the filesystem. |
N8N_CUSTOM_EXTENSIONS directories | Yes | Custom nodes kept outside the folder. Workflows that use them break without them. |
Compose file and .env | Yes | The image version, the database connection and any custom N8N_ENCRYPTION_KEY. |
| S3 or Azure binary data storage | In the bucket | Not on the server. |
On an npm install the folder is ~/.n8n of the user who runs n8n. N8N_USER_FOLDER changes the parent: with N8N_USER_FOLDER=/opt/n8n, the folder is /opt/n8n/.n8n. In Docker it is a volume mounted at /home/node/.n8n. n8n's docker run example names it n8n_data; Compose files use other names and add the project name in front, so look yours up. Replace n8n with your container's name from docker ps throughout:
docker inspect --format '{{json .Mounts}}' n8nThen get the volume's path on the host, which the commands below read from. With Docker's default data directory, a volume named n8n_data is at /var/lib/docker/volumes/n8n_data/_data:
docker volume inspect --format '{{ .Mountpoint }}' n8n_dataSave the encryption key
n8n encrypts each credential before writing it to the database. On first start it generates a random key and saves it in .n8n/config, unless N8N_ENCRYPTION_KEY is set. Without that key, a restored database or credential export cannot be decrypted, and the key cannot be recovered from the data. Print it:
docker exec n8n cat /home/node/.n8n/configStore the encryptionKey value in your password manager, apart from the backups. Three rules from n8n's docs and source:
- If both the file and
N8N_ENCRYPTION_KEYexist, they must match. Otherwise n8n refuses to start withMismatching encryption keys. - If the file is missing and the variable is not set, n8n generates a new key without asking, and every existing credential then fails to decrypt.
- In queue mode, the main instance and every worker need the same
N8N_ENCRYPTION_KEY.
Set N8N_ENCRYPTION_KEY in your Compose .env to the value already in config. The key then lives in your deployment files too, so losing the volume doesn't lose the key.
If you turn on n8n's encryption key rotation (N8N_ENV_FEAT_ENCRYPTION_KEY_ROTATION), take a full backup first. n8n documents it as one-way: the only way back is a database backup from before you enabled it.
Copy the SQLite database safely
Since n8n 2.0, the SQLite driver always uses write-ahead log mode: recent writes sit in database.sqlite-wal until a checkpoint moves them into database.sqlite. n8n's docs say to stop n8n before copying the folder, or to use a tool that takes a consistent snapshot of the SQLite file. SQLite's own shell is that tool. Install it on the host and run it as root against the file in the volume:
apt install sqlite3install -d -m 700 /var/backups/n8nsqlite3 /var/lib/docker/volumes/n8n_data/_data/database.sqlite ".timeout 10000" "VACUUM INTO '/var/backups/n8n/database.sqlite'"VACUUM INTO writes a compacted copy from one consistent read while n8n keeps running. It suits n8n better than .backup, which starts over whenever another connection writes during the copy, and n8n writes execution records all day. The target file must not exist. .timeout 10000 waits up to 10 seconds for a lock. Check the result; a healthy copy prints ok:
sqlite3 /var/backups/n8n/database.sqlite "PRAGMA quick_check;"Execution history is most of a busy database. By default n8n deletes executions older than 336 hours (14 days) and keeps at most 10,000; EXECUTIONS_DATA_MAX_AGE and EXECUTIONS_DATA_PRUNE_MAX_COUNT change that. SQLite backups covers the copy methods in detail.
Back up PostgreSQL instead
With DB_TYPE=postgresdb, everything except the key and local files is in PostgreSQL. In n8n's withPostgres Compose example the database service is called postgres and has POSTGRES_USER and POSTGRES_DB in its environment. From the Compose project folder:
docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' > /var/backups/n8n/n8n-$(date +%F).dump-Tturns off the pseudo-terminal thatdocker compose execallocates by default, which would mangle the binary dump.sh -cin single quotes makes the variables expand inside the container, where they are set.-Fcwrites the compressed custom format thatpg_restorereads.
Back up the .n8n volume as well: it still holds the key. The Docker Compose database guide and the pg_dump guide cover the details.
Export workflows and credentials with the CLI
n8n's CLI writes workflows and credentials as JSON: handy for keeping workflows in git, moving them to another instance, or getting one back without a full restore. --backup is shorthand for --all --pretty --separate, one readable file per item. In Docker, run it as the node user, write into the container, then copy the files out:
docker exec -u node n8n n8n export:workflow --backup --output=/tmp/n8n-export/workflows/docker exec -u node n8n n8n export:credentials --backup --output=/tmp/n8n-export/credentials/docker cp n8n:/tmp/n8n-export ./n8n-export-2026-10-03docker exec -u node n8n rm -rf /tmp/n8n-export- On an npm install, run the same commands without
docker exec. Use a separate folder for each type. - Credentials stay encrypted with the instance key and are useless without it.
--publishedexports each workflow's published version instead of the current draft.- The exports leave out users and roles, execution history, variables and instance settings, including the key. They are not a full backup.
--decrypted writes every credential in plain text: API keys, OAuth tokens, database passwords. Use it only to move credentials to an instance with a different key, and delete the file as soon as the import is done.
docker exec -u node n8n n8n export:credentials --all --decrypted --output=/tmp/decrypted.jsonAutomate the full backup
This root script copies the SQLite database with VACUUM INTO, checks it, packs it with the rest of the .n8n volume, and encrypts the archive to a GPG public key, because the archive holds both the encrypted credentials and the key that opens them. Encrypting backups shows how to set up the key. The n8n version goes into the file name, for the restore.
#!/usr/bin/env bash
set -euo pipefail
umask 077
CONTAINER="n8n"
VOLUME="n8n_data"
BACKUP_DIR="/var/backups/n8n"
KEY="<gpg-key-fingerprint>"
KEEP_DAYS=14
DATA="$(docker volume inspect --format '{{ .Mountpoint }}' "$VOLUME")"
VERSION="$(docker exec "$CONTAINER" n8n --version 2>/dev/null || echo unknown)"
OUT="$BACKUP_DIR/n8n-$(date +%Y-%m-%d_%H%M)-$VERSION.tar.gz.gpg"
STAGE="$(mktemp -d)"
trap 'rm -rf "$STAGE"; rm -f "$OUT.partial"' EXIT
mkdir -p "$BACKUP_DIR"
sqlite3 "$DATA/database.sqlite" ".timeout 10000" "VACUUM INTO '$STAGE/database.sqlite'"
result=$(sqlite3 "$STAGE/database.sqlite" "PRAGMA quick_check;")
if [ "$result" != "ok" ]; then
echo "quick_check failed: $result" >&2
exit 1
fi
chown --reference="$DATA/database.sqlite" "$STAGE/database.sqlite"
tar -czf - --numeric-owner -C "$STAGE" database.sqlite \
-C "$DATA" --exclude='./database.sqlite*' . \
| gpg --batch --encrypt --recipient "$KEY" --output "$OUT.partial"
mv "$OUT.partial" "$OUT"
find "$BACKUP_DIR" -name 'n8n-*.tar.gz.gpg' -type f -mtime +"$KEEP_DAYS" -deletechmod 755 /usr/local/bin/n8n-backup.sh40 2 * * * root /usr/local/bin/n8n-backup.sh >> /var/log/n8n-backup.log 2>&1chown --referencegives the database copy the owner of the original, the image'snodeuser (UID 1000), so n8n can open it after a restore.--numeric-ownerstores that number rather than a name.- The exclude pattern skips the live
database.sqlite,-waland-shmin the volume; the copy added from the staging folder has no./in its name and stays in. - For PostgreSQL, swap the
sqlite3andchownlines for thepg_dumpcommand (run from the Compose folder) writing to$STAGE/n8n.dump, and pack that file instead.
Copy the encrypted files off the server each night, for example with rclone.
Restore the full instance
Restore into a new volume, so the old one stays untouched until the restore checks out. On a machine with the private key:
docker volume create n8n_data_restoredgpg --decrypt n8n-2026-10-03_0240-2.41.6.tar.gz.gpg | docker run --rm -i -v n8n_data_restored:/data alpine tar -xzf - --numeric-owner -C /datadocker run --rm -v n8n_data_restored:/data alpine chown -R 1000:1000 /data-i passes the decrypted stream into the helper container; the chown makes sure the node user owns every file. Then start n8n on the restored volume, with the version in the file name and your usual options:
docker rm -f n8ndocker run -d --name n8n -p 5678:5678 -v n8n_data_restored:/home/node/.n8n n8nio/n8n:2.41.6With Compose, point the volume key your n8n service uses at the restored volume, pin N8N_VERSION in .env, and run docker compose up -d:
volumes:
n8n_data:
external: true
name: n8n_data_restoredWith PostgreSQL, load the dump before n8n starts, or n8n creates empty tables first. Restore the .n8n volume as above for the key, then start only the database, load the dump and start the rest:
docker compose up -d postgresdocker compose exec -T postgres sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB"' < n8n-2026-10-03.dumpdocker compose up -dMatch versions. A newer n8n updates the database schema when it starts, so restore onto the version that made the backup, then upgrade if you want to. Going back to an older version after a migration means running n8n db:revert on the newer one, once per migration. docker exec n8n n8n --version prints the running version.
Import workflows and credentials
To move workflows to another instance, or bring back items from a CLI export, import them. On a fresh instance, finish the owner setup in the browser first: the import commands need an owner account. Copy the files in, hand them to the node user, import, and restart:
docker cp ./n8n-export-2026-10-03 n8n:/tmp/n8n-importdocker exec -u 0 n8n chown -R 1000:1000 /tmp/n8n-importdocker exec -u node n8n n8n import:workflow --separate --input=/tmp/n8n-import/workflows/docker exec -u node n8n n8n import:credentials --separate --input=/tmp/n8n-import/credentials/docker restart n8ndocker cpcreates root-owned files; thechownletsnoderead them.- Items keep their IDs, so an item with the same ID on the target is overwritten.
- Imported workflows arrive unpublished; publish them in the editor.
--activeState=fromJsonkeeps each file's state, but only in queue or multi-main mode. - n8n's docs list a known issue: on a single instance, schedule triggers of a previously active workflow keep running after an import until n8n restarts. Hence the restart.
- Encrypted credentials import only into an instance with the same key. Otherwise import a
--decryptedexport, which n8n encrypts again with the new instance's key.
Test the restore without running your workflows
A restored copy is a working n8n with your credentials: once it starts, schedule and polling triggers run against your real systems. So restore into a test volume named n8n_restore_test with the commands above, and unpublish every workflow before the copy starts. The CLI works while n8n is stopped:
docker run --rm -v n8n_restore_test:/home/node/.n8n n8nio/n8n:2.41.6 unpublish:workflow --allOn n8n 1.x the command is update:workflow --all --active=false. Then check the key: decrypting every credential to /dev/null proves it matches without writing plain text anywhere. It ends with Successfully exported and a count, or fails with Credentials could not be decrypted:
docker run --rm -v n8n_restore_test:/home/node/.n8n n8nio/n8n:2.41.6 export:credentials --all --decrypted --output=/dev/nullNow start the copy on a port bound to 127.0.0.1, so webhooks and outside visitors can't reach it, and open it through an SSH tunnel from your computer:
docker run -d --name n8n-restore-test -p 127.0.0.1:5679:5678 -v n8n_restore_test:/home/node/.n8n n8nio/n8n:2.41.6curl -sf http://127.0.0.1:5679/healthzssh -N -L 5679:127.0.0.1:5679 root@<server-ip>Open http://localhost:5679, log in with your usual account, and check that the workflows are there, a few credentials open without errors and the execution list has its history. Then delete the copy:
docker rm -f n8n-restore-testdocker volume rm n8n_restore_testWith PostgreSQL, restore the dump into a separate database and point the test copy's DB_POSTGRESDB_* settings at it. Never start a test copy against the production database.
Testing restores covers making this a schedule; Docker volume backups covers volumes in general.
Common errors
| Error | Fix |
|---|---|
Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data. | The instance runs with another key. Restore .n8n/config from the backup, or set N8N_ENCRYPTION_KEY to the original key. |
Mismatching encryption keys | N8N_ENCRYPTION_KEY differs from the key in .n8n/config. Keep the one your credentials were encrypted with and make the other match. |
EACCES: permission denied on a file in /home/node/.n8n | Restored files belong to root. Run chown -R 1000:1000 on the volume. |
No workflows found with specified filters | There is nothing to export, or the CLI ran as a user other than node and read another user folder. Use docker exec -u node. |
Failed to find owner | The import ran before the owner account existed. Finish setup in the browser, then import. |
The "--activeState=fromJson" flag can only be used when n8n is running in queue or multi-main mode. | Drop the flag and publish the workflows in the editor. |
Frequently asked questions
- Where does n8n store its data?
- In the .n8n folder of the user running it: ~/.n8n by default, or /home/node/.n8n in the Docker image, usually on a named volume. With the default SQLite database that folder holds everything, including the encryption key. With PostgreSQL, the folder holds the key and the database holds the rest.
- Can I restore n8n credentials without the encryption key?
- No. Credentials are encrypted with the key in .n8n/config or N8N_ENCRYPTION_KEY, and n8n cannot decrypt them without it. Keep a copy of the key outside the server.
- Is n8n export:workflow --all a full backup?
- No. It exports workflows, and export:credentials exports credentials. Users, execution history, variables and settings are left out. Back up the .n8n folder and the database as well.
- Can I back up n8n while it is running?
- Yes. Copy database.sqlite with sqlite3's VACUUM INTO or .backup, which read a consistent snapshot, or run pg_dump for PostgreSQL. Only a plain file copy of the folder needs n8n stopped.
- How do I move n8n to a new server?
- Restore the .n8n folder, and the PostgreSQL dump if you use one, on the new server and start the same n8n version. Workflows, credentials, users and history come along. Then point your domain at the new server.
How this was checked
Commands, limits and prices were checked against these official pages, on October 4, 2026:
- n8n Docs: Back up and restore
- n8n Docs: Use the command line
- n8n Docs: Set a custom encryption key
- n8n Docs: Specify user folder path
- n8n Docs: Supported databases
- n8n Docs: Deployment environment variables
- n8n Docs: Executions environment variables
- n8n Docs: Encryption key rotation
- n8n Docs: Install with Docker
- n8n Docs: Install using Docker Compose
- n8n Docs: Install with npm (reverting an upgrade)
- n8n Docs: n8n 2.0 breaking changes (SQLite pooling driver uses WAL mode)
- n8n-hosting: Docker Compose withPostgres example
- n8n source 2.41.6: instance settings (config file, key mismatch check)
- n8n source 2.41.6: SQLite connection options (enableWAL)
- n8n source 2.41.6: export:credentials command
- n8n source 2.41.6: import:workflow command
- n8n source 2.41.6: credential error messages
- n8n source 2.41.6: Docker image (USER node, entrypoint)
- SQLite: VACUUM
- SQLite: Using the SQLite Online Backup API
- Docker documentation: docker volume inspect
- Docker documentation: docker container cp
- Docker documentation: docker compose exec