Odoo backups
Odoo backups: database, filestore, and custom modules
This guide explains what a real Odoo backup should include before you trust a deployment, migration, automation, upgrade, or AI/database integration.
The backup principle
Read this before running backup commands
Planning
Design decisions before changing backup, storage, automation, or restore processes.
Safe check
Read-only commands or manual checks that should not change the server state.
Caution
Checks or plans that may involve sensitive data, secrets, or careful interpretation.
Change required
Actions that create, copy, restore, move, or automate backups. Plan storage, permissions, and impact before applying.
Backup checklist
1. Understand what a complete Odoo backup includes
PlanningAn Odoo backup is not only a PostgreSQL dump. Odoo stores binary files, attachments, custom modules, configuration, and deployment secrets outside the database.
Show command and interpretation
Command or manual check
A complete Odoo backup usually includes:
1. PostgreSQL database
2. Odoo filestore
3. Custom addons
4. Odoo configuration
5. Docker Compose or deployment files
6. Environment variables / secrets
7. Nginx configuration
8. Restore instructions
9. Off-server copyHow to interpret the result
If you only backup PostgreSQL, you may lose:
- uploaded documents
- product images
- invoice PDFs
- email attachments
- website media
- custom modules
- deployment configuration
- passwords, tokens, and integration settings stored outside the database
A backup is only trustworthy if you know how to restore it.2. Identify the Odoo deployment type
Safe checkBackup commands depend on how Odoo is installed. Before backing up anything, identify whether Odoo is managed by Docker Compose, a Linux systemd service, managed hosting, or an on-premise setup.
Show command and interpretation
Command or manual check
echo "=== Docker containers ==="
sudo docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}" 2>/dev/null || echo "Docker is not installed or you do not have permission to run Docker"
echo
echo "=== Docker Compose project files in current folder ==="
pwd
ls -la docker-compose.yml compose.yml 2>/dev/null || echo "No Docker Compose file found in the current folder"
echo
echo "=== Odoo systemd services ==="
systemctl list-units --type=service --all | grep -Ei "odoo" || echo "No Odoo systemd service found"
echo
echo "=== Common Odoo paths ==="
sudo find /etc /opt /var/lib /var/log -maxdepth 3 -iname "*odoo*" 2>/dev/null | head -100How to interpret the result
Use the output to identify the deployment type.
Docker Compose signs:
- docker ps shows Odoo and PostgreSQL containers
- a docker-compose.yml or compose.yml file exists
- common project folders look like /opt/odoo-secure, /opt/odoo, or another deployment folder
- Docker volumes may appear under /var/lib/docker/volumes
Linux package/systemd signs:
- systemctl shows an Odoo service
- common config paths may include /etc/odoo/odoo.conf
- Odoo data may be under /var/lib/odoo
Managed hosting signs:
- you may not have SSH, Docker, or systemd access
- backups may be controlled by the hosting provider
On-premise/office server signs:
- Odoo may run on a local company server
- DNS/firewall/router rules may forward public traffic to the server
- backups must consider local storage, network shares, and physical hardware risk
How to interpret common results:
- "No Odoo systemd service found" is normal for Docker Compose deployments.
- Docker volumes such as /var/lib/docker/volumes/... usually mean Docker is storing database or Odoo data.
- Nginx and Let's Encrypt paths show the public HTTPS reverse proxy setup, not the Odoo database itself.
- Do not copy backup commands blindly until you know where the database, filestore, addons, and config are stored.3. Locate the active database name
Safe checkYou need the exact database name before creating a PostgreSQL dump or testing a restore.
Show command and interpretation
Command or manual check
# Docker example:
# Replace odoo-secure-db with your PostgreSQL container name from step 2.
# Replace -U odoo with the PostgreSQL user from your odoo.conf db_user value.
sudo docker exec odoo-secure-db psql -U odoo -l
# Or inside PostgreSQL:
sudo docker exec odoo-secure-db psql -U odoo -d postgres -c "\l"How to interpret the result
Look for the active Odoo database name.
Example:
- odoo_prod
PostgreSQL often includes a default database named postgres. Do not confuse it with your Odoo database. In this example, the real Odoo business database is odoo_prod.
Backing up only postgres would not back up the Odoo customers, products, orders, invoices, users, or settings.4. Locate the Odoo filestore
Safe checkThe filestore contains uploaded files and attachments. Without it, a restored database may show missing files, images, PDFs, and documents.
Show command and interpretation
Command or manual check
echo "=== Check Odoo data_dir from config ==="
cd /opt/odoo-secure
grep -E "^data_dir" config/odoo.conf || echo "No data_dir found in config"
echo
echo "=== Check common filestore paths inside the Odoo container ==="
sudo docker exec odoo-secure-app sh -c '
echo "--- data_dir path: /var/lib/odoo ---"
ls -la /var/lib/odoo 2>/dev/null || true
echo
echo "--- common path: /var/lib/odoo/filestore ---"
ls -la /var/lib/odoo/filestore 2>/dev/null || true
echo
echo "--- database filestore: /var/lib/odoo/filestore/odoo_prod ---"
ls -la /var/lib/odoo/filestore/odoo_prod 2>/dev/null || true
echo
echo "--- alternate path: /var/lib/odoo/.local/share/Odoo/filestore ---"
ls -la /var/lib/odoo/.local/share/Odoo/filestore 2>/dev/null || true
echo
echo "--- alternate database filestore: /var/lib/odoo/.local/share/Odoo/filestore/odoo_prod ---"
ls -la /var/lib/odoo/.local/share/Odoo/filestore/odoo_prod 2>/dev/null || true
'
echo
echo "=== Find filestore paths on the host or Docker volumes ==="
sudo find /var/lib/docker /opt /var/lib -path "*filestore*" -type d 2>/dev/null | head -50How to interpret the result
The filestore path can differ depending on the Odoo version, Docker image, configuration, or volume mapping.
Good signs:
- you find a filestore folder matching the Odoo database name
- example: filestore/odoo_prod
- the folder contains many small subfolders with short names such as 0a, 1f, b5, cd, etc.
- in Docker setups, the host path may be inside /var/lib/docker/volumes/...
Common container paths:
- /var/lib/odoo/filestore/<database_name>
- /var/lib/odoo/.local/share/Odoo/filestore/<database_name>
Common host/Docker volume paths:
- /var/lib/docker/volumes/<project>_odoo-web-data/_data/filestore/<database_name>
- /var/lib/docker/volumes/<project>_odoo-data/_data/filestore/<database_name>
- a custom host folder mounted into the Odoo container
How to interpret the result:
- If /var/lib/odoo/filestore/odoo_prod exists inside the container, that is likely the active filestore.
- If the container path does not show the filestore but the host search finds /var/lib/docker/volumes/.../_data/filestore/odoo_prod, the filestore is stored in a Docker volume.
- If no filestore is found, confirm the database name, check data_dir in odoo.conf, and inspect Docker volume mounts.
- The filestore folder should match the Odoo database name. For example, database odoo_prod should usually have a filestore folder named odoo_prod.
Important:
The database dump and filestore backup should be taken together as a consistent pair. Backing up only PostgreSQL may restore the records, but uploaded files, images, PDFs, and attachments may be missing if the filestore is not included.
For a perfectly consistent pair on a busy instance, take the backup during low activity or briefly stop Odoo, not PostgreSQL, so no new attachments are written between the database dump and the filestore archive. On a quiet instance, taking them back-to-back is normally sufficient.5. Locate custom addons
Safe checkCustom modules are often outside the database. If you restore the database without the custom module code, Odoo may fail to start or important features may behave incorrectly.
Show command and interpretation
Command or manual check
# Go to the folder where your Odoo deployment is managed.
# Example for a Docker Compose install:
cd /opt/odoo-secure
echo "=== Current folder ==="
pwd
echo
echo "=== Review addons path from odoo.conf ==="
grep -E "^addons_path" config/odoo.conf || echo "No addons_path found"
echo
echo "=== Project addons folder ==="
ls -la addons 2>/dev/null || echo "No local addons folder found in this project"
echo
echo "=== Extra addons folder inside the Odoo container ==="
sudo docker exec odoo-secure-app sh -c 'ls -la /mnt/extra-addons 2>/dev/null || echo "No /mnt/extra-addons folder found"'
echo
echo "=== Search common custom addons folders ==="
sudo find /opt /mnt /var/lib -maxdepth 4 -type d \( -name "addons" -o -name "custom-addons" -o -name "extra-addons" \) 2>/dev/nullHow to interpret the result
How to interpret the result:
1. Check addons_path in odoo.conf.
This tells you where Odoo expects to load modules from.
Example:
- addons_path = /mnt/extra-addons,/usr/lib/python3/dist-packages/odoo/addons
2. Separate built-in addons from custom addons.
Built-in Odoo addons are usually part of the Odoo installation or Docker image.
Common built-in path:
- /usr/lib/python3/dist-packages/odoo/addons
You normally do not need to back up built-in Odoo core addons if you can recreate the same Odoo version from the image/package.
3. Identify custom addon folders.
These are the folders you must back up.
Common custom addon paths:
- /opt/odoo-secure/addons
- /mnt/extra-addons inside the container
- /opt/odoo/custom/addons
- /opt/odoo/extra-addons
- a custom host folder mounted into the Odoo container
4. Empty custom addon folder is okay.
If /opt/odoo-secure/addons exists but is empty, it means no custom addons are currently installed there. Still include the folder in backup scripts so future custom modules are covered.
5. Ignore unrelated addon folders.
Some paths may contain the word addons but are not Odoo custom modules.
Example:
- /var/lib/vim/addons is related to Vim, not Odoo.
If you use third-party or custom modules, also keep a list of:
- module name
- version
- source repository or vendor
- license
- Odoo version compatibility
- whether the module is required for the database to start correctly6. Locate configuration and secrets
CautionOdoo needs configuration to reconnect to the database, use the correct addons path, run behind a proxy, and protect the database manager. Some files contain secrets.
Show command and interpretation
Command or manual check
# Example Docker Compose deployment:
cd /opt/odoo-secure
echo "=== Deployment files ==="
ls -la
echo
echo "=== Config files ==="
ls -la config
echo
echo "=== Show important odoo.conf keys without secrets ==="
grep -E "^(admin_passwd|db_host|db_port|db_user|db_password|proxy_mode|list_db|dbfilter|addons_path|data_dir)" config/odoo.conf \
| sed -E 's/(admin_passwd|db_password) = .+/\1 = ***hidden***/'
echo
echo "=== Environment file exists, do not print secrets ==="
ls -la .env 2>/dev/null || echo "No .env file found"
echo
echo "=== Nginx site configs that may need backup ==="
sudo ls -la /etc/nginx/sites-available 2>/dev/null | grep -Ei "odoo|erp" || echo "No obvious Odoo Nginx config found"How to interpret the result
Important files may include:
- docker-compose.yml
- .env
- config/odoo.conf
- Nginx site config
- custom scripts
- backup scripts
- deployment notes
How to interpret the result:
1. Deployment files
The project folder should contain the files needed to recreate the deployment.
Common files and folders:
- docker-compose.yml
- .env
- config/
- addons/
2. Odoo configuration
config/odoo.conf should contain important runtime settings such as:
- db_host
- db_port
- db_user
- addons_path
- data_dir
- proxy_mode
- list_db
- dbfilter
Sensitive values such as admin_passwd and db_password should be hidden when displayed.
3. Environment file
.env may contain database passwords, Odoo master passwords, tokens, or other secrets.
It should exist if your deployment depends on it, but its content should not be printed publicly.
4. Nginx configuration
If Odoo is exposed through Nginx/HTTPS, the Nginx site config should be included in the backup plan.
Common path:
- /etc/nginx/sites-available/your-odoo-domain
5. File permissions
Secret files should have restricted permissions.
Good examples:
- .env with 600 permissions
- docker-compose.yml with restricted permissions if it contains secrets
Do not paste secrets publicly.
Backups containing .env, odoo.conf, database dumps, or configuration files should be protected with strict permissions and stored securely.7. Create a manual backup folder
Change requiredCreate a timestamped folder so database, filestore, addons, and config backups are grouped together.
Show command and interpretation
Command or manual check
BACKUP_ROOT="/opt/odoo-backups"
BACKUP_DATE="$(date +%Y%m%d-%H%M%S)"
BACKUP_DIR="$BACKUP_ROOT/$BACKUP_DATE"
sudo mkdir -p "$BACKUP_DIR"
sudo chmod 700 "$BACKUP_ROOT" "$BACKUP_DIR"
echo "$BACKUP_DIR"
echo
echo "=== Verify backup folder permissions ==="
sudo ls -ld "$BACKUP_ROOT" "$BACKUP_DIR"How to interpret the result
Expected:
- a new timestamped backup folder is created
- permissions restrict casual access
- all backup parts for this run are stored together
Important:
Copy the printed BACKUP_DIR path and reuse the same folder for the following backup steps. Do not run this folder-creation step again until the current backup run is finished, or later steps may target a new empty folder instead of the intended backup folder.
Good permission example:
- drwx------ root root /opt/odoo-backups
- drwx------ root root /opt/odoo-backups/YYYYMMDD-HHMMSS
The timestamped folder will be used for:
- PostgreSQL database dump
- Odoo filestore archive
- custom addons
- deployment configuration
- backup manifest
For production, use an off-server backup destination too. A backup stored only on the same VPS may be lost if the VPS is deleted, corrupted, encrypted by malware, or compromised.8. Back up the PostgreSQL database
Change requiredThe PostgreSQL database contains Odoo business records: users, partners, products, orders, invoices, settings, module state, and more.
Show command and interpretation
Command or manual check
BACKUP_ROOT="/opt/odoo-backups"
DB_NAME="odoo_prod"
# Prefer pinning the exact backup folder printed by step 7.
# Replace this value with the real folder created for this backup run.
BACKUP_DIR="/opt/odoo-backups/YYYYMMDD-HHMMSS"
echo "=== Using backup folder ==="
echo "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Backup folder not found. Run step 7 first, then paste the printed path into BACKUP_DIR."
exit 1
fi
echo
echo "=== Create PostgreSQL dump ==="
sudo docker exec odoo-secure-db pg_dump -U odoo -Fc "$DB_NAME" | sudo tee "$BACKUP_DIR/database.dump" >/dev/null
echo
echo "=== Verify database dump ==="
sudo ls -lh "$BACKUP_DIR/database.dump"
echo
echo "=== Test dump metadata ==="
sudo docker exec -i odoo-secure-db pg_restore -l < "$BACKUP_DIR/database.dump" | head -20How to interpret the result
The database dump should exist inside the timestamped backup folder.
Replace BACKUP_DIR with the exact folder path printed in step 7. This avoids accidentally splitting one backup across multiple timestamped folders.
Good result:
- database.dump exists
- file size is not 0
- pg_restore -l can read the dump metadata
- metadata shows the expected Odoo database name
Example:
- dbname: odoo_prod
- Format: CUSTOM
If you see "Permission denied" when finding the backup folder:
- the backup root is probably protected with 700 permissions
- use sudo when listing or finding backup folders
- do not weaken permissions just to make the command easier
If the dump is accidentally created at /database.dump:
- move it into the timestamped backup folder
- verify it with pg_restore -l
- avoid leaving backup files in /
Important:
If the server will be reset, deleted, reinstalled, or restored from scratch, copy the backup off the server before doing anything destructive. A backup stored only on the same VPS can be lost with the VPS.
For large production databases:
- schedule during lower activity
- monitor disk space
- consider compression and off-server transfer
- test restore regularly9. Back up the Odoo filestore
Change requiredThe filestore contains attachments and binary content. It should be backed up with the database.
Show command and interpretation
Command or manual check
BACKUP_ROOT="/opt/odoo-backups"
DB_NAME="odoo_prod"
# Use the exact backup folder printed by step 7.
# Replace this value with the real folder created for this backup run.
BACKUP_DIR="/opt/odoo-backups/YYYYMMDD-HHMMSS"
echo "=== Using backup folder ==="
echo "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Backup folder not found. Run step 7 first, then paste the printed path into BACKUP_DIR."
exit 1
fi
echo
echo "=== Try common filestore locations ==="
# Common Docker volume / host path example.
FILESTORE_PATH="$(sudo find /var/lib/docker /opt /var/lib -path "*/filestore/$DB_NAME" -type d 2>/dev/null | head -1)"
echo "Detected filestore path:"
echo "$FILESTORE_PATH"
if [ -z "$FILESTORE_PATH" ]; then
echo "No filestore folder found for database: $DB_NAME"
echo "Go back to the filestore location step and confirm the correct path."
exit 1
fi
echo
echo "=== Verify filestore folder ==="
sudo ls -ld "$FILESTORE_PATH"
echo
echo "=== Create filestore archive ==="
sudo tar -C "$(dirname "$FILESTORE_PATH")" -czf "$BACKUP_DIR/filestore.tar.gz" "$DB_NAME"
echo
echo "=== Verify filestore archive ==="
sudo ls -lh "$BACKUP_DIR/filestore.tar.gz"
echo
echo "=== Test archive content ==="
sudo tar -tzf "$BACKUP_DIR/filestore.tar.gz" | head -20How to interpret the result
The filestore archive should exist inside the timestamped backup folder.
Replace BACKUP_DIR with the exact folder path printed in step 7. This avoids accidentally splitting one backup across multiple timestamped folders.
Good result:
- filestore.tar.gz exists
- file size is not 0
- tar -tzf can list the archive content
- archive content includes the database filestore folder
Example:
- odoo_prod/...
- odoo_prod/b5/...
- odoo_prod/22/...
If the filestore path does not exist:
- confirm the database name
- confirm data_dir in odoo.conf
- inspect Docker volumes
- check whether filestore is mounted somewhere else
- go back to the filestore location step before continuing
If you see "Permission denied":
- the backup folder is probably protected with 700 permissions
- use sudo to read, write, list, or archive backup files
- do not weaken backup folder permissions just to make commands easier
Important:
Do not assume the filestore is inside PostgreSQL. It is usually separate. A restored database without the matching filestore may have missing images, PDFs, uploads, and attachments.
For a perfectly consistent database and filestore pair on a busy instance, take the backup during low activity or briefly stop Odoo, not PostgreSQL, so no new attachments are written between the database dump and the filestore archive. On a quiet instance, taking them back-to-back is normally sufficient.10. Back up custom addons and deployment config
Change requiredDatabase and filestore are not enough if the Odoo instance depends on custom code, Docker Compose, Nginx, or configuration files.
Show command and interpretation
Command or manual check
BACKUP_ROOT="/opt/odoo-backups"
# Use the exact backup folder printed by step 7.
# Replace this value with the real folder created for this backup run.
BACKUP_DIR="/opt/odoo-backups/YYYYMMDD-HHMMSS"
echo "=== Using backup folder ==="
echo "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Backup folder not found. Run step 7 first, then paste the printed path into BACKUP_DIR."
exit 1
fi
echo
echo "=== Back up Odoo project config and addons ==="
cd /opt/odoo-secure
sudo tar -czf "$BACKUP_DIR/odoo-project-config-and-addons.tar.gz" \
docker-compose.yml \
.env \
config \
addons
echo
echo "=== Back up Nginx Odoo site config if it exists ==="
sudo cp -a /etc/nginx/sites-available/odoo.example.com "$BACKUP_DIR/nginx-site.conf" 2>/dev/null || echo "No matching Nginx site config found"
echo
echo "=== Verify backup folder content ==="
sudo ls -lh "$BACKUP_DIR"
echo
echo "=== Test project config archive content ==="
sudo tar -tzf "$BACKUP_DIR/odoo-project-config-and-addons.tar.gz" | head -30How to interpret the result
The backup folder should include:
- database.dump
- filestore.tar.gz
- odoo-project-config-and-addons.tar.gz
- nginx-site.conf if applicable
Replace BACKUP_DIR with the exact folder path printed in step 7. This avoids accidentally splitting one backup across multiple timestamped folders.
The project config archive should include:
- docker-compose.yml
- .env
- config/
- config/odoo.conf
- addons/
How to interpret the result:
- database.dump is the PostgreSQL database backup
- filestore.tar.gz is the Odoo attachments/files backup
- odoo-project-config-and-addons.tar.gz contains deployment files, secrets, Odoo config, and custom addons
- nginx-site.conf contains the public reverse proxy / HTTPS configuration if Odoo is exposed through Nginx
Be careful:
- .env and odoo.conf may contain secrets
- database.dump may contain business and personal data
- backup folders should stay permission-protected
- do not publish or share these backup files publicly
If you see "Permission denied":
- the backup folder is probably protected with 700 permissions
- use sudo to list, read, or write backup files
- do not weaken backup folder permissions just to make commands easier11. Create a backup manifest
Safe checkA manifest helps you understand what is inside the backup later, especially during a stressful restore.
Show command and interpretation
Command or manual check
BACKUP_ROOT="/opt/odoo-backups"
# Use the exact backup folder printed by step 7.
# Replace this value with the real folder created for this backup run.
BACKUP_DIR="/opt/odoo-backups/YYYYMMDD-HHMMSS"
echo "=== Using backup folder ==="
echo "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Backup folder not found. Run step 7 first, then paste the printed path into BACKUP_DIR."
exit 1
fi
{
echo "Backup date: $(date -Is)"
echo "Hostname: $(hostname)"
echo "Odoo URL: https://odoo.example.com"
echo "Database: odoo_prod"
echo
echo "Backup folder:"
echo "$BACKUP_DIR"
echo
echo "Files:"
sudo ls -lh "$BACKUP_DIR"
echo
echo "Docker containers:"
sudo docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Ports}}" 2>/dev/null || true
echo
echo "Odoo config summary:"
cd /opt/odoo-secure
grep -E "^(db_host|db_port|db_user|proxy_mode|list_db|dbfilter|addons_path|data_dir)" config/odoo.conf 2>/dev/null || true
} | sudo tee "$BACKUP_DIR/MANIFEST.txt" >/dev/null
echo
echo "=== Manifest content ==="
sudo cat "$BACKUP_DIR/MANIFEST.txt"How to interpret the result
The manifest should describe:
- when the backup was made
- which server it came from
- which Odoo URL it belongs to
- which database was backed up
- which backup folder was used
- which files were included
- which containers/images were running
- basic Odoo configuration context without secrets
Replace BACKUP_DIR with the exact folder path printed in step 7. This avoids accidentally creating a manifest for the wrong backup folder.
Good signs:
- MANIFEST.txt exists inside the backup folder
- it lists database.dump
- it lists filestore.tar.gz
- it lists odoo-project-config-and-addons.tar.gz
- it lists nginx-site.conf if applicable
- it references the correct database name
How to interpret the result:
- the manifest is not the backup itself
- it is a readable index of what the backup contains
- it helps during restore because you do not need to guess which database, URL, server, or files the backup belongs to
Important:
Do not print secrets in the manifest. Avoid including admin_passwd, db_password, API keys, tokens, or private environment values.12. Copy the backup off the server
Change requiredA backup stored only on the same server does not protect against server deletion, disk failure, ransomware, or serious compromise.
Show command and interpretation
Command or manual check
# Step A — Run this on the server first.
# It verifies the protected backup path and creates a temporary export archive
# that your SSH user can download.
BACKUP_ROOT="/opt/odoo-backups"
# Use the exact backup folder printed by step 7.
# Replace this value with the real folder created for this backup run.
BACKUP_DIR="/opt/odoo-backups/YYYYMMDD-HHMMSS"
echo "=== Protected backup folder on the server ==="
echo "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Backup folder not found. Run step 7 first, then paste the printed path into BACKUP_DIR."
exit 1
fi
echo
echo "=== Verify backup content before export ==="
sudo ls -lh "$BACKUP_DIR"
BACKUP_NAME="$(basename "$BACKUP_DIR")"
EXPORT_DIR="$HOME/backup-export"
EXPORT_FILE="$EXPORT_DIR/odoo-backup-$BACKUP_NAME.tar.gz"
echo
echo "=== Create temporary export archive ==="
mkdir -p "$EXPORT_DIR"
chmod 700 "$EXPORT_DIR"
sudo tar -C "$(dirname "$BACKUP_DIR")" -czf "$EXPORT_FILE" "$BACKUP_NAME"
sudo chown "$USER:$USER" "$EXPORT_FILE"
chmod 600 "$EXPORT_FILE"
echo
echo "=== Export file ready for download ==="
ls -lh "$EXPORT_FILE"
echo
echo "Download this file from your local computer:"
echo "$EXPORT_FILE"
# Step B — Run this from your local computer, not from the server.
# Example from macOS/Linux terminal:
scp your-user@your-server-ip:/home/your-user/backup-export/odoo-backup-YYYYMMDD-HHMMSS.tar.gz ./
# Example from Windows PowerShell:
scp your-user@your-server-ip:/home/your-user/backup-export/odoo-backup-YYYYMMDD-HHMMSS.tar.gz C:\\Users\\YourName\\Backups\\
# Step C — After confirming the file exists on your local computer,
# run this on the server to remove the temporary export archive.
rm "$EXPORT_FILE"How to interpret the result
1. Verify the protected backup folder on the server.
Replace BACKUP_DIR with the exact folder path printed in step 7.
The server should show the selected backup folder and list files such as:
- database.dump
- filestore.tar.gz
- odoo-project-config-and-addons.tar.gz
- nginx-site.conf
- MANIFEST.txt
2. Create a temporary export archive.
The export archive is created under the SSH user's home folder so it can be downloaded with scp.
Example:
- /home/your-user/backup-export/odoo-backup-YYYYMMDD-HHMMSS.tar.gz
3. Run scp from your local computer.
Do not run the scp download command from the server. Run it from your laptop or workstation terminal.
Windows PowerShell example:
- scp your-user@your-server-ip:/home/your-user/backup-export/odoo-backup-YYYYMMDD-HHMMSS.tar.gz C:\Users\YourName\Backups\
4. Confirm the file exists on your local computer.
After download, check your local backup folder and confirm the archive is present.
5. Delete the temporary export from the server after download.
Keep the original protected backup in /opt/odoo-backups, but remove the temporary readable export archive from the user's home folder.
Why this extra export step is useful:
- /opt/odoo-backups may be protected with root-only permissions
- direct scp from /opt/odoo-backups can fail
- the temporary archive lets you download the backup without weakening backup folder permissions
Important:
Keep at least one protected copy on the server for quick rollback and at least one copy off the server. For sensitive production systems, use encrypted storage. Do not store unencrypted production backups in public buckets, shared folders, or insecure drives. Backups can contain customer data, employee data, invoices, passwords, API keys, and private business information.13. Verify backup integrity
Safe checkA backup file that exists is not automatically usable. Basic checks help catch empty files, missing parts, or corrupt archives.
Show command and interpretation
Command or manual check
BACKUP_ROOT="/opt/odoo-backups"
# Use the exact backup folder printed by step 7.
# Replace this value with the real folder created for this backup run.
BACKUP_DIR="/opt/odoo-backups/YYYYMMDD-HHMMSS"
echo "=== Using backup folder ==="
echo "$BACKUP_DIR"
if [ ! -d "$BACKUP_DIR" ]; then
echo "Backup folder not found. Run step 7 first, then paste the printed path into BACKUP_DIR."
exit 1
fi
echo
echo "=== Backup files ==="
sudo ls -lh "$BACKUP_DIR"
echo
echo "=== Check required backup files exist ==="
for file in database.dump filestore.tar.gz odoo-project-config-and-addons.tar.gz MANIFEST.txt; do
if sudo test -s "$BACKUP_DIR/$file"; then
echo "OK: $file exists and is not empty"
else
echo "MISSING OR EMPTY: $file"
fi
done
echo
echo "=== Test filestore archive ==="
sudo tar -tzf "$BACKUP_DIR/filestore.tar.gz" | head -20
echo
echo "=== Test config/addons archive ==="
sudo tar -tzf "$BACKUP_DIR/odoo-project-config-and-addons.tar.gz" | head -30
echo
echo "=== Test PostgreSQL dump metadata ==="
sudo cat "$BACKUP_DIR/database.dump" | sudo docker exec -i odoo-secure-db pg_restore -l | head -20
echo
echo "=== Show manifest ==="
sudo cat "$BACKUP_DIR/MANIFEST.txt"How to interpret the result
Good signs:
- required files exist and are not empty
- filestore.tar.gz can be listed with tar -tzf
- odoo-project-config-and-addons.tar.gz can be listed with tar -tzf
- pg_restore -l can read the database dump metadata
- metadata shows the expected Odoo database name
- MANIFEST.txt exists and describes the backup
Replace BACKUP_DIR with the exact folder path printed in step 7. This avoids verifying the wrong backup folder.
Expected required files:
- database.dump
- filestore.tar.gz
- odoo-project-config-and-addons.tar.gz
- MANIFEST.txt
Optional but recommended:
- nginx-site.conf if Odoo is exposed through Nginx/HTTPS
How to interpret common results:
1. "Permission denied"
The backup folder is probably protected with root-only permissions.
Use sudo to list, read, and test backup files.
Do not weaken backup folder permissions just to make commands easier.
2. tar archive cannot be listed
The archive may be missing, incomplete, or corrupted.
Recreate that part of the backup before trusting it.
3. pg_restore cannot read database.dump
The database dump may be invalid or incomplete.
Recreate the PostgreSQL dump and test again.
4. File exists but size is 0
The backup command likely failed.
Do not trust an empty backup file.
Important:
These checks prove that the backup files can be read at a basic level. This still does not replace a real restore test in a separate sandbox.14. Test restore on a separate sandbox
Change requiredThe only reliable proof of a backup is a successful restore test. Never test restore first on production.
Show command and interpretation
Command or manual check
This step should be done in a separate sandbox, not on production.
High-level restore test:
1. Prepare a separate sandbox server or separate Docker project.
2. Restore PostgreSQL dump into a test database.
3. Restore the filestore under the matching database name.
4. Restore custom addons and config.
5. Start Odoo against the restored database.
6. Log in and verify key records, attachments, images, invoices, and custom modules.
7. Document the restore steps and issues found.
Related guide:
- /guides/odoo-sandbox-environmentHow to interpret the result
A restore test should confirm:
- Odoo starts
- users can log in
- key records exist
- attachments and images load
- custom modules are available
- reports and important workflows work
- integrations are disabled or safely sandboxed
During sandbox restore, PostgreSQL ownership or ACL differences may require restore options such as --no-owner or --no-acl, especially if the sandbox uses a different database role than production. Document any restore options needed so the process is repeatable.
If you do not have a sandbox yet:
- stop here
- keep the backup safely stored
- create a sandbox first
- then come back and perform the restore test
A backup that has never been restored is only an assumption.
Current status after file-level verification:
- backup files exist
- archives can be read
- database dump metadata can be read
- off-server copy can be created
- full restore is still not proven until tested in a sandbox15. Automate backups carefully
CautionAutomated backups are useful, but they can create a false sense of safety if they are not monitored, rotated, and tested.
Show command and interpretation
Command or manual check
A practical automation plan:
- run backup daily or according to business risk
- store backups in timestamped folders
- copy backups off-server
- keep a retention policy
- send success/failure alerts
- monitor disk usage
- periodically restore-test a backup
- protect backups containing secrets or personal dataHow to interpret the result
Avoid:
- silent backup failures
- backups filling the server disk
- backups stored only on the same VPS
- backups containing secrets without protection
- no restore testing
- no notification when backup fails
For production, automation should include alerting and a documented restore process.Final verification
- ✓The PostgreSQL database dump exists and can be read by pg_restore.
- ✓The Odoo filestore is included and matches the database name.
- ✓Custom addons are included or documented with their source and version.
- ✓docker-compose.yml, odoo.conf, .env, and Nginx config are backed up securely.
- ✓The backup has a manifest explaining what it contains.
- ✓At least one copy exists off the server.
- ✓Backup permissions protect secrets and customer data.
- ✓A restore test has been performed on a separate sandbox.
- ✓Backup automation includes monitoring, retention, and failure alerts.
Need a tested Odoo backup setup?
If your current Odoo backup is only a database dump, or if you are not sure whether your filestore, custom addons, configuration files, and secrets are included, I can help review or improve the backup process.
A reliable setup should include the PostgreSQL database, filestore, custom addons, deployment configuration, off-server copies, a manifest, and a restore test plan.
Contact me for Odoo backup setup →Related guides
Backups are part of a wider safe Odoo deployment process. Harden the server first, then expose Odoo through HTTPS carefully.