Automate Database and Filestore Backups
An Odoo backup is only useful when it contains both parts of the installation: the PostgreSQL database and the filestore. A database dump without its matching filestore can leave documents, product images, and other attachments missing after a restore.
Odoo 19 includes a command-line database tool that creates a ZIP archive containing the database dump, filestore, and backup metadata. This guide explains how to use that tool in a shell script, schedule daily backups with cron, remove expired archives, and test the restore process.
You need shell access to a self-hosted Odoo server and an account with sudo privileges. The automated method in this guide is intended for an Odoo 19 source installation.
Install unzip, which the backup script uses to check each archive:
sudo apt update sudo apt install unzip
Find the database name if you do not already know it:
Terminal
sudo -u postgres psql -l
The database name appears in the first column. In the examples below, it is odoo19.
Create the Backup Directory
Store local Odoo backups in a directory that is not accessible to regular users:
Terminal
sudo install -d -m 750 -o odoo19 -g odoo19 /var/backups/odoo
The Odoo service account owns this directory because the backup command runs as that user. The 750 mode allows access only to the owner and group.
Local backups are the first layer of protection, not the final destination. Later in the guide, we will cover why the archives must also be copied off the Odoo server.
Test an Odoo Backup
Before creating the automation script, run one backup manually. The following command uses Odoo’s db dump tool to create a ZIP archive with the database and filestore:
Terminal
sudo -u odoo19 /opt/odoo19/odoo-venv/bin/python3 \
/opt/odoo19/odoo/odoo-bin db \
-c /etc/odoo19.conf \
dump odoo19 /var/backups/odoo/odoo19-test.zip
The important arguments are:
- db - Opens Odoo’s command-line database manager.
- -c /etc/odoo19.conf - Loads the database connection and data directory settings from the Odoo configuration file.
- dump odoo19 - Creates a backup of the odoo19 database.
- /var/backups/odoo/odoo19-test.zip - Sets the output archive path.
Check that Odoo created the file:
Terminal
sudo ls -lh /var/backups/odoo/odoo19-test.zip
Test the ZIP structure before relying on it:
Terminal
sudo unzip -t /var/backups/odoo/odoo19-test.zip
A valid Odoo ZIP backup contains dump.sql, manifest.json, and a filestore/ directory when the database has a filestore. The final output line should report that no errors were detected.
Remove the test archive after verification:
Terminal
sudo rm /var/backups/odoo/odoo19-test.zip
Create the Automatic Odoo Backup Script
Create the backup script in /usr/local/sbin:
Terminal
sudo nano /usr/local/sbin/backup-odoo
Add the following script:
/usr/local/sbin/backup-odoosh
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
BACKUP_DIR="/var/backups/odoo"
DATABASE="odoo19"
ODOO_PYTHON="/opt/odoo19/odoo-venv/bin/python3"
ODOO_BIN="/opt/odoo19/odoo/odoo-bin"
ODOO_CONFIG="/etc/odoo19.conf"
RETENTION_DAYS=7
TIMESTAMP=$(date -u +%Y-%m-%d_%H-%M-%S)
BACKUP_FILE="${BACKUP_DIR}/${DATABASE}_${TIMESTAMP}.zip"
TEMP_FILE="${BACKUP_FILE}.tmp"
cleanup() {
rm -f "$TEMP_FILE"
}
trap cleanup EXIT
"$ODOO_PYTHON" "$ODOO_BIN" db \
-c "$ODOO_CONFIG" \
dump "$DATABASE" "$TEMP_FILE"
test -s "$TEMP_FILE"
unzip -tq "$TEMP_FILE"
mv "$TEMP_FILE" "$BACKUP_FILE"
find "$BACKUP_DIR" \
-type f \
-name "${DATABASE}_*.zip" \
-mtime "+${RETENTION_DAYS}" \
-delete
printf 'Odoo backup created: %s\n' "$BACKUP_FILE"Change DATABASE and the Odoo paths to match your installation. RETENTION_DAYS=7 keeps one week of local daily backups.
The script writes the backup to a temporary filename first. It checks that the file is not empty and uses unzip -tq to test the archive. Only after both checks pass does it move the file to its final name and delete expired backups. If the Odoo command fails, the script exits without rotating valid archives.
Set the script owner and permissions:
Terminal
sudo chown root:root /usr/local/sbin/backup-odoo sudo chmod 755 /usr/local/sbin/backup-odoo
The script does not contain the Odoo master password. It reads the database connection settings from /etc/odoo19.conf and runs as the Odoo service account.
Test the complete script:
Terminal
sudo -u odoo19 /usr/local/sbin/backup-odoo
List the resulting archive:
Terminal
sudo ls -lh /var/backups/odoo
Do not schedule the script until this manual run completes successfully.
Schedule the Backup with Cron
Open the root crontab:
Terminal
sudo crontab -e
Add this entry to run the backup every day at 1:30 AM:
cron
30 1 * * * /usr/bin/flock -n /run/lock/odoo-backup.lock /usr/sbin/runuser -u odoo19 -- /usr/local/sbin/backup-odoo >> /var/log/odoo-backup.log 2>&1
The cron job uses flock to prevent two backup processes from running at the same time. This matters when a large database takes longer than expected to archive. Standard output and errors are written to /var/log/odoo-backup.log.
After the first scheduled run, inspect the log and backup directory:
Terminal
sudo tail -n 20 /var/log/odoo-backup.log sudo ls -lh /var/backups/odoo
Copy Backups Off the Odoo Server
A local archive does not protect you if the server disk fails, the host is deleted, or an attacker removes both the application and its backups. Copy every successful backup to separate storage.
Good destinations include:
- A backup server that pulls files from the Odoo host
- Object storage with versioning or retention lock enabled
- A separate server account restricted to creating new backup files
A pull-based design is preferable because the Odoo server does not hold credentials that can delete the remote copies. If you push backups with rsync , restrict the remote SSH key and account so they cannot remove or overwrite older archives.
Keep more than one retention window. For example, you can keep seven daily backups locally while the remote system retains weekly and monthly copies.
Restore and Test an Odoo Backup
Backups should be restored regularly on a test system. A successful archive check confirms that the ZIP file is readable, but only a restore confirms that Odoo can load the database and filestore.
Copy the selected archive to the test server, then stop the test Odoo service:
Terminal
sudo systemctl stop odoo19
Restore the archive under a new database name:
Terminal
sudo -u odoo19 /opt/odoo19/odoo-venv/bin/python3 \
/opt/odoo19/odoo/odoo-bin db \
-c /etc/odoo19.conf \
load --neutralize odoo19_restore \
/var/backups/odoo/odoo19_2026-06-15_01-30-00.zipReplace the archive name with the backup you are testing. The target database must not already exist. The --neutralize option disables scheduled actions and outgoing integrations that should not run from a copied production database.
Start Odoo and open the restored database:
Terminal
sudo systemctl start odoo19
Check several records with attachments or product images. This verifies that both the PostgreSQL data and filestore were restored.
Warning
Test restores on an isolated server or database. Do not use the --force option against a production database because it allows Odoo to delete an existing target before loading the backup.
Manual Backup from the Database Manager
Odoo also provides a web database manager at:
txt
https://odoo.example.com/web/database/manager
Select the database, choose Backup, enter the master password, and keep the ZIP format so the archive includes the filestore.
This interface is suitable for an occasional manual backup on a private or development installation. Odoo recommends disabling database listing and management pages on internet-facing production systems by setting list_db = False. The command-line backup method still works with database management disabled.
Never send the master password over plain HTTP. Restrict access to the database manager at the reverse proxy or firewall when the page must remain enabled.
Troubleshooting
The backup command reports a permission error
Confirm that the command runs as the same operating-system user as Odoo and that this user can write to /var/backups/odoo. Check the directory with sudo ls -ld /var/backups/odoo.
Odoo cannot find the database
Verify the DATABASE value and the db_host, db_port, db_user, and db_password settings in /etc/odoo19.conf. List available databases with sudo -u postgres psql -l.
The ZIP archive does not contain a filestore directory
Confirm that Odoo is using the same data directory when the backup script runs. If your service uses a custom data_dir, define it in the configuration file loaded with -c.
The cron job works manually but not on schedule
Use absolute paths in the script and cron entry, then inspect /var/log/odoo-backup.log. Also confirm that the root crontab contains the entry with sudo crontab -l.
Old backups are not removed
find -mtime +7 removes files after they have passed seven complete 24-hour periods. Run the find command without -delete first if you change the filename pattern or retention period.
Design features
Our design features offer a range of tools to create visually stunning websites. Utilize WYSIWYG editors, drag-and-drop building blocks, and Bootstrap-based templates for effortless customization. With professional themes and an intuitive system, you can design with ease and precision, ensuring a polished, responsive result.
Building blocks system
Create pages from scratch by dragging and dropping customizable building blocks. This system simplifies web design, making it accessible to all skill levels. Combine headers, images, and text sections to build cohesive layouts quickly and efficiently.
Bootstrap-Based Templates
Design Odoo templates easily with clean HTML and Bootstrap CSS. These templates offer a responsive, mobile-first design, making them simple to customize and perfect for any web project, from corporate sites to personal blogs.
Conclusion
The Odoo command-line database tool creates a single archive containing the database and filestore, while the script adds validation, retention, logging, and overlap protection. Keep copies away from the Odoo server and perform scheduled restore tests, because the real measure of a backup is whether you can restore it.
Start writing here...