The False Security of Untested Backups
Every Odoo project I've worked on since roughly 2015 has a backup schedule written into the go-live checklist. A nightly `pg_dump`, a cron job, a folder on a NAS or an S3 bucket somewhere. Someone ticks the box. Everyone moves on to the next fire.
Nobody checks whether the backup actually restores.
That's not laziness — it's a reasonable-sounding assumption that turns out to be wrong more often than it should. A backup file that exists is not the same thing as a backup that works. I've seen dump files that were 40MB when the production database was 6GB, because the export job silently failed halfway through and nobody looked at the exit code. I've seen filestore directories backed up on a different schedule than the database itself, so the restored system had order records pointing at attachments and product images that no longer existed. Odoo stores your documents, invoices, and product images as files on disk, separate from the PostgreSQL database that holds the transactional data. Back up one without the other and you get something that opens but is quietly broken — which is worse than something that doesn't open at all, because you don't find out until three weeks later when a customer asks where their signed contract went.
CISA's #StopRansomware Guide (cisa.gov) puts this plainly: backups need to be tested regularly, not just taken regularly, and organizations should validate through periodic recovery exercises rather than assuming the last successful backup job means the data is recoverable. That guidance exists because insurers, incident responders, and IT teams kept discovering the same failure mode after the ransomware note had already appeared on screens.
How Ransomware Exploits Backup Failures During Recovery
Modern ransomware doesn't just encrypt your live database. It goes hunting for backups first, because a company with working backups doesn't pay. Attackers who get a foothold on a server hosting an on-premise Odoo instance will typically sit quietly for days or weeks, mapping out where backups live — the NAS share, the cron-scheduled dump folder, the cloud sync target — before triggering encryption on all of it simultaneously.
This is where the gap between "we have backups" and "we have tested backups" turns into the difference between a bad Tuesday and a bad quarter. A Rayong plastics manufacturer I worked with in 2022 had backups running nightly to a network share mounted on the same Windows server that got hit. The ransomware encrypted the share along with the production files. Their actual recovery point ended up being a manual export someone had emailed themselves eleven days earlier, because that was the only copy that existed outside the blast radius.
The technical fix here isn't exotic — it's the 3-2-1 rule CISA and most backup vendors have pushed for years: three copies of data, on two different media types, with one copy off-site and, ideally, offline or immutable so ransomware running on your network literally cannot reach it and rewrite it. Odoo.sh, Odoo's own PaaS hosting, handles this automatically for databases hosted there — backups are stored off the production infrastructure and retained on a rolling basis across daily, weekly, and monthly points, per Odoo's documentation on Odoo.sh backups. If you're self-hosting or running through a third-party host, that separation is entirely on you to build, and it's the first thing I check in a security review.
Setting Up Automated Backup Validation in Odoo
A validation routine needs to answer one question automatically, every day: could this backup actually be restored right now, not just did the export job complete without an error?
For self-hosted or on-premise Odoo, that means going past `pg_dump` succeeding and into an actual scripted restore cycle:
- Pull the latest database dump and filestore archive to a separate, isolated machine or container — never the production host.
- Run a scripted restore into a throwaway PostgreSQL instance and confirm the Odoo service boots against it without errors in the log.
- Check row counts on a handful of high-value tables (`sale_order`, `account_move`, `stock_move`) against expected ranges. A dump that restores cleanly but has half the expected invoice records is still a failure.
- Confirm the filestore attachment count roughly matches `ir_attachment` records in the database. A mismatch here is the single most common silent failure I've encountered.
- Log the result somewhere that isn't itself dependent on the system being backed up — a separate monitoring tool, a Slack webhook, an email to an address checked by a human.
If you're on Odoo.sh, the platform gives you a meaningful shortcut: you can restore any listed backup directly into a new build or duplicate the database into a Staging branch. That's not automated validation on its own, but it removes the infrastructure work of standing up a scratch server, and there's no excuse not to run it monthly at minimum.
One feature worth building into this routine: database neutralization, which has been available since at least Odoo 16.0 — documented back through the SaaS point releases (16.1, 16.2, 16.4) that preceded it, per Odoo's own documentation on neutralized databases. It isn't new to 17 or 18. When you duplicate or restore a database for testing, Odoo's neutralized database mode disables outgoing mail servers, scheduled actions, payment providers, and other systems that could otherwise fire real emails or real transactions from what's supposed to be a dead copy. Skipping this step is how test restores turn into duplicate invoices landing in customers' inboxes at 2am. I've had to send that apology email exactly once, and I built neutralization checks into every restore script since.
Restore Testing: Your Real Ransomware Insurance Policy
A backup schedule is a promise. A tested restore is proof. The distinction matters most in the exact moment you need it — during an actual incident, under pressure, with management asking for an ETA you don't have.
Quarterly is the minimum cadence I'd defend to a client, and monthly is what I actually recommend for anyone running Odoo as their operational core — inventory, invoicing, manufacturing. Scale this to the size of the business, though. A five-person shop running Odoo for invoicing and nothing else doesn't need a scripted monthly restore pipeline against a scratch server; a manual quarterly check, done by someone who writes down what they found, is proportionate. The full rigor described here is for anyone whose warehouse, POS terminals, or production line stops the moment Odoo does.
The test itself needs a stopwatch on it. Time how long it takes from "start restore" to "Odoo is serving pages and a test user can log in and pull up a sales order." That number is your Recovery Time Objective in practice, not in theory, and it's almost always longer than people guess. A 15GB production database with a 40GB filestore of product images and scanned delivery notes does not restore in the ten minutes someone assumed during the original infrastructure planning meeting.
Test the edge cases too, not just the happy path:
- Restore from a backup that's a week old, not just last night's — because in a real ransomware event, last night's backup may already be compromised.
- Restore onto hardware or a cloud instance different from production, since the incident that takes down your database may also take down the server it lives on.
- Verify that scheduled actions, automated emails, and integrations (payment gateways, shipping APIs, EDI connections) come back in a controlled, deliberate state rather than firing automatically the second the restored instance goes live.
Building a Ransomware Recovery Timeline with Verified Backups
Once you know your restore actually works and roughly how long it takes, you can build a timeline that isn't guesswork. This is the document that turns a ransomware event from a panic into a checklist — and the difference between those two states, in my experience, is usually eight to twelve hours of downtime, minimum.
A realistic timeline looks something like this:
- Hour 0–1: Isolate affected systems. Confirm which backup generation is clean — this requires knowing roughly when the compromise began, which is why file-integrity monitoring and login audit logs matter as much as the backups themselves.
- Hour 1–2: Stand up clean infrastructure — new server or new Odoo.sh branch, not the compromised host, ever.
- Hour 2–4 (adjust to your tested restore time): Restore database and filestore from the last verified-clean backup. Run in neutralized mode first.
- Hour 4–5: Validate against your known-good checklist — record counts, attachment integrity, a handful of manual spot checks against last known transactions.
- Hour 5–6: Re-enable outgoing mail, scheduled actions, and integrations one at a time, watching logs for anything unexpected.
- Hour 6+: Reconcile the gap. Whatever transactions happened between your last clean backup and the incident need to be manually re-entered or recovered from secondary sources — POS terminals, paper delivery notes, email confirmations.
That gap-reconciliation step is the part nobody wants to plan for, but it's where most of the real pain lives. A daily backup schedule means your worst-case data loss is roughly 24 hours of transactions. If that number is unacceptable for your business — and for anyone running high-volume sales or manufacturing, it probably is — the fix is more frequent backups or write-ahead log (WAL) archiving for point-in-time recovery, not just hoping the ransomware hits at a convenient hour.
Common Backup Testing Mistakes That Leave You Vulnerable
- Testing the backup file, not the restore. Confirming a `.zip` or `.dump` file exists and has a non-zero size tells you almost nothing about whether Odoo will actually boot from it.
- Backing up the database and filestore on different schedules. They need to be point-in-time consistent with each other or you get orphaned records and broken attachments.
- Storing backups with the same credentials as production. If your admin login is compromised, and your backup storage uses the same login, the backups are compromised too. This defeats the entire purpose.
- Never testing a restore onto different infrastructure. A restore that only works on the exact server it came from isn't a disaster recovery plan.
- Skipping database neutralization during test restores. Live payment providers and mail servers firing from a test environment create a second incident on top of the first.
- Assuming Odoo.sh or any managed host removes the need to think about this at all. Managed backups solve infrastructure risk, not process risk — someone on your team still needs to know how to trigger a restore and how long it takes, before the day they need to do it for real.
- Treating backup testing as an IT-only exercise. The people who'll notice a bad restore fastest are the ones who use the sales order screen or the inventory count every day. Include them in at least one test cycle a year.
None of this is complicated. It's tedious, and it competes for calendar time against things that feel more urgent right up until the moment they don't. The Rayong client mentioned earlier now runs a scripted restore against a duplicate Odoo.sh branch every month, without being told to. It's the one client on that list who's had a real ransomware incident and hasn't needed me to explain, a second time, why last night's backup wasn't enough.


