VPS Snaps

How to back up and restore Odoo

An Odoo backup has two halves that must come from the same moment: the PostgreSQL database and the filestore, the folder <data_dir>/filestore/<database> that holds every attachment, image and stored PDF. Keep odoo.conf and your custom modules with them. The database manager's zip bundles both halves; for scheduled backups, use pg_dump and tar, and neutralize any copy you restore for testing.

10 min readUpdated Checked against official documentation

What an Odoo backup includes

Odoo Community is open source under the LGPL v3. Odoo Enterprise is Community plus proprietary modules sold by subscription, installed from Odoo's Enterprise package or as an extra addons folder. Both store data the same way. This guide covers self-hosted Odoo 17 to 20 from Odoo's Debian/Ubuntu package, with notes for the official Docker image. The database is called mydb.

PartWhereNotes
DatabasePostgreSQLRecords, settings, users and the list of installed modules.
Filestore<data_dir>/filestore/mydbAttachments, images, stored PDFs and asset bundles, as files named after their SHA-1 hash.
odoo.conf/etc/odoo/odoo.confDatabase connection, paths and the master password. Keep it, encrypted.
Custom and third-party modulesFolders in addons_pathIn git, or archived if you cannot download them again.
Enterprise modulesEnterprise package or enterprise folderDownload again for the same version.
Sessions<data_dir>/sessionsSkip. Users sign in again.

Where the filestore lives depends on the install:

InstallFilestore of mydb
Debian/Ubuntu package (no data_dir set; the odoo user's home is /var/lib/odoo)/var/lib/odoo/.local/share/Odoo/filestore/mydb
Official Docker image (data_dir = /var/lib/odoo, a volume)/var/lib/odoo/filestore/mydb in the container
Source install~/.local/share/Odoo/filestore/mydb of the user running odoo-bin
data_dir set in odoo.conf<data_dir>/filestore/mydb
Terminal
sudo ls /var/lib/odoo/.local/share/Odoo/filestore

Odoo looks for a database's files only in the folder with exactly its name. A database restored without its filestore opens, but images, attachments and stored PDFs are gone. Back up both halves every time.

The odoo.conf settings that matter

/etc/odoo/odoo.conf
[options]
admin_passwd = $pbkdf2-sha512$...
db_host = False
db_port = False
db_user = odoo
db_password = False
data_dir = /var/lib/odoo/.local/share/Odoo
addons_path = /usr/lib/python3/dist-packages/odoo/addons,/opt/odoo/custom-addons
list_db = False
dbfilter = ^mydb$
  • admin_passwd is the master password for the database manager. Odoo stores it hashed once it is set from the manager.
  • db_host = False and db_user = odoo connect over the local socket as the odoo role the package creates, which is why the commands below run as the odoo user.
  • data_dir holds filestore/ and sessions/. Setting it to the package default makes the path obvious to whoever restores it.
  • addons_path lists module folders. Enterprise modules must come before the Community ones.
  • list_db and dbfilter lock the database manager, below.

The database manager backup, and why to lock it down

The database manager at /web/database/manager downloads backups. Backup asks for the master password and a format: zip holds dump.sql (plain pg_dump output), manifest.json (Odoo and PostgreSQL versions, installed modules) and, with Include filestore ticked, filestore/. "pg_dump custom format (without filestore)" is the database alone. Odoo 17 and later do the same from the command line, even with the manager disabled, into a folder the odoo user can write to:

Terminal
install -d -m 700 -o odoo -g odoo /var/backups/odoo
Terminal
sudo -u odoo odoo db -c /etc/odoo/odoo.conf dump mydb /var/backups/odoo/mydb-$(date +%F).zip

Both copy the whole filestore into a temporary folder before zipping, so they need that much free space, and a zip download is built completely before it starts. Odoo's deployment guide says the manager "is not designed to handle large databases, and may trigger memory limits". With workers enabled, a request that runs past limit_time_real (120 seconds by default) is killed.

The same guide says: "It is strongly recommended to disable the Database Manager for any internet-facing system!" The manager also duplicates, restores and deletes databases. While admin_passwd is still the default admin, Odoo saves the password typed into the next manager form as the new master password, so whoever reaches the page first sets it. Once db_name or dbfilter picks the database for every request, set list_db = False (the --no-database-list option), which blocks the database selection and management screens, then restart:

Terminal
systemctl restart odoo

Scriptable backup: pg_dump plus a filestore archive

Terminal
sudo -u odoo pg_dump --no-owner mydb | gzip > /var/backups/odoo/mydb-db-$(date +%F).sql.gz

--no-owner leaves out ownership commands, as Odoo's own dump does, so the role that loads the file owns the tables. The output is plain SQL, like the dump.sql in Odoo's zip; the pg_dump guide covers other formats. Then the filestore:

Terminal
tar -czf /var/backups/odoo/mydb-filestore-$(date +%F).tar.gz -C /var/lib/odoo/.local/share/Odoo/filestore mydb

Dump the database first. A stored file is named after the hash of its content and never changes, and Odoo deletes files no record uses only in its daily auto-vacuum job, so a filestore copied after the dump has every file the dump refers to, plus a few newer ones that do no harm. Odoo's own zip does it the other way round.

With the official Docker image and Compose services named web and db, as in Docker's example, run the same steps through the containers from the project folder (Compose database backups has more):

Terminal
docker compose exec -T db pg_dump -U odoo --no-owner mydb | gzip > /var/backups/odoo/mydb-db-$(date +%F).sql.gz
Terminal
docker compose exec -T web tar -czf - -C /var/lib/odoo/filestore mydb > /var/backups/odoo/mydb-filestore-$(date +%F).tar.gz

Keep custom modules in git and note the commit each server runs, so a restore uses the code that matches the database. Archive modules you bought, and keep odoo.conf in its own encrypted archive: db_password is stored there in plain text.

Automate it with cron

The script writes under temporary names, checks that pg_dump wrote its closing line, and only then renames. tar exits with 1 when something changed while it read, so 1 counts as success.

/usr/local/bin/odoo-backup.sh
#!/bin/bash
set -euo pipefail

DB="mydb"
FILESTORE="/var/lib/odoo/.local/share/Odoo/filestore"
BACKUP_DIR="/var/backups/odoo"
KEEP_DAYS=14
STAMP="$(date +%Y-%m-%d_%H%M)"
SQL="$BACKUP_DIR/$DB-db-$STAMP.sql.gz"
FILES="$BACKUP_DIR/$DB-filestore-$STAMP.tar.gz"

trap 'rm -f "$SQL.partial" "$FILES.partial"' EXIT

sudo -u odoo pg_dump --no-owner "$DB" | gzip > "$SQL.partial"
gunzip -c "$SQL.partial" | tail -n 6 | grep -q "PostgreSQL database dump complete"
mv "$SQL.partial" "$SQL"

tar -czf "$FILES.partial" -C "$FILESTORE" "$DB" || [ $? -eq 1 ]
mv "$FILES.partial" "$FILES"

find "$BACKUP_DIR" -type f -name "$DB-*" -mtime +"$KEEP_DAYS" -delete
Terminal
chmod 755 /usr/local/bin/odoo-backup.sh
/etc/cron.d/odoo-backup
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

15 2 * * * root flock -n /run/lock/odoo-backup.lock /usr/local/bin/odoo-backup.sh >> /var/log/odoo-backup.log 2>&1

Copy the files off the server each night, for example with rclone; Odoo's guide asks for a remote server "that is not accessible from the server itself". The cron guide covers alerts when a run fails.

Restore onto a new server

Install the same major version and edition of Odoo, your custom modules at the matching commit, and /etc/odoo/odoo.conf, adjusted for any new paths or database host. Odoo 20 warns below PostgreSQL 16, Odoo 19 below 13. Stop Odoo, then create the database as Odoo does, in UTF-8 with the C collation from template0, owned by the odoo role:

Terminal
systemctl stop odoo
Terminal
sudo -u postgres createdb -O odoo -E UTF8 -T template0 --lc-collate=C mydb
Terminal
gunzip -c /var/backups/odoo/mydb-db-2026-10-04.sql.gz | sudo -u odoo psql -v ON_ERROR_STOP=1 --quiet -d mydb

ON_ERROR_STOP=1 stops at the first error instead of carrying on. Restoring a PostgreSQL dump covers other formats. Put the filestore back under the database's exact name, then start Odoo:

Terminal
sudo -u odoo mkdir -p /var/lib/odoo/.local/share/Odoo/filestore
Terminal
tar -xzf /var/backups/odoo/mydb-filestore-2026-10-04.tar.gz -C /var/lib/odoo/.local/share/Odoo/filestore
Terminal
chown -R odoo:odoo /var/lib/odoo/.local/share/Odoo/filestore/mydb
Terminal
systemctl start odoo

A zip from the manager or odoo db dump restores with odoo db load, which accepts nothing else. --move keeps the database UUID, which an Enterprise subscription is tied to; use it when production moves. Without it Odoo treats the restore as a copy and creates a new UUID, like the manager's "This database is a copy" answer.

Terminal
sudo -u odoo odoo db -c /etc/odoo/odoo.conf load --move mydb /var/backups/odoo/mydb-2026-10-04.zip

If the address changed, check web.base.url under Settings > Technical > System Parameters (developer mode). Odoo resets it to the address an administrator signs in from, unless web.base.url.freeze is set. See also moving a server to a new provider.

Neutralize test copies

A restored copy still has your mail servers, scheduled actions, payment providers and webhooks, so a test restore can email customers. Restore it under a test name (renaming the extracted filestore folder to match), then neutralize it before Odoo serves it:

Terminal
sudo -u odoo odoo neutralize -c /etc/odoo/odoo.conf -d mydb_test

This runs each installed module's neutralization script. The base one deactivates every mail server and clears its credentials, adds a dummy server so nothing falls back to one set on the command line, deactivates every scheduled action except the auto-vacuum, and disables webhooks. Odoo's documentation adds payment providers, delivery methods, bank synchronization and search-engine indexing, and a red banner marks the database. --stdout prints the SQL instead; odoo db load -n and the manager's Neutralize box do the same during a restore.

An Enterprise copy restored from SQL also keeps production's UUID, and Odoo's documentation says no two databases should share one. This makes the same call Odoo's restore makes for a copy, and also resets database.secret and web.base.url:

Terminal
echo "env['ir.config_parameter'].init(force=True); env.cr.commit()" | sudo -u odoo odoo shell -c /etc/odoo/odoo.conf -d mydb_test

Upgrades are not restores

Odoo's documentation separates updating, a newer build of the same version that leaves the database alone, from upgrading to a new major version, which it calls irreversible. A backup from Odoo 18 restores only into Odoo 18; Odoo 19 does not upgrade it, and its database list marks it "This database may not be compatible".

  • Back up database and filestore before every update or upgrade, and keep the old version's code.
  • Odoo's upgrade service is included with Enterprise; python <(curl -s https://upgrade.odoo.com/upgrade) test -d <your db name> -t <target version> requests an upgraded test copy. Community databases are usually upgraded with OpenUpgrade, the Odoo Community Association's open-source project.
  • The upgraded database comes back without your filestore. Odoo's guide says to merge its filestore folder into production's.
  • Upgrading PostgreSQL is a separate job: see PostgreSQL major upgrades.

Verify the restore

List every file the database expects that the filestore lacks. No output means nothing is missing:

Terminal
sudo -u odoo psql -d mydb -Atc "SELECT DISTINCT store_fname FROM ir_attachment WHERE store_fname IS NOT NULL" | while read -r f; do [ -e "/var/lib/odoo/.local/share/Odoo/filestore/mydb/$f" ] || echo "missing: $f"; done
Terminal
grep -E '_read_file reading|_file_read reading|Some modules are not loaded' /var/log/odoo/odoo-server.log
Terminal
curl -sS -o /dev/null -w '%{http_code}\n' https://erp.example.com/web/login

Expect 200. Sign in, open a record with attachments, and print a PDF. Testing restores makes this routine.

Common errors

ErrorFix
Database backup error: Access Denied (17 to 19) or Database management function blocked, bad admin password. (20)Wrong master password. It is admin_passwd in odoo.conf; Odoo's deployment guide explains how to reset it.
The database manager has been disabled by the administratorlist_db = False. Use odoo db dump or pg_dump instead.
WorkerHTTP followed by a process ID and timeout after 120s in the logThe manager backup ran past limit_time_real. Back up from the command line.
Not a zipped dump file, use followed by a pointer to pg_restore and psqlodoo db load takes only zips. Load SQL dumps with psql.
Database mydb not initialized, you can force it with -i baseThe database is empty; the dump went elsewhere or failed. Load it again. -i base would start a blank database.
_read_file reading (17 to 19) or _file_read reading (20) followed by a pathThe filestore is missing, under another database name, outside data_dir, or not owned by odoo.
Some modules are not loaded, some dependencies or manifest may be missing:addons_path lacks custom or Enterprise modules the database uses.
invalid command \restrictpsql from before the August 2025 minor releases (17.6, 16.10, 15.14, 14.19, 13.22) is loading a dump from a newer pg_dump. Update the client.
psql reports that a role does not existThe dump was made without --no-owner. Dump again with it, or create the role.

Frequently asked questions

Where does Odoo store attachments?
In the filestore: a folder named after the database inside data_dir/filestore. For the Debian/Ubuntu package that is /var/lib/odoo/.local/share/Odoo/filestore; for the official Docker image, /var/lib/odoo/filestore inside the container.
Does an Odoo database backup include the filestore?
The database manager's zip format includes it when Include filestore is ticked. The pg_dump custom format, and any pg_dump you run yourself, contains the database only, so archive the filestore separately.
Can I restore an Odoo 18 backup into Odoo 19?
No. A backup restores only into the major version it came from. Restore it into Odoo 18, then upgrade it with Odoo's upgrade service (Enterprise) or OpenUpgrade (Community).
What is the Odoo master password?
The admin_passwd setting in odoo.conf. It protects the database manager's create, duplicate, backup, restore and delete actions. Set a random one, or disable the manager with list_db = False.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: