
The short version: A backup running perfectly every night is only half a safety net. We watched a restore delete a live website database and then fail to put anything back โ and the backups themselves were fine the whole time.
Read the full technical breakdown โ
What happened
A website on one of our shared servers started misbehaving one morning. Nothing dramatic โ the sort of thing where you look at it and think, I'll just roll it back to yesterday and get on with my day.
So the person looking after it did the sensible thing. cPanel has Acronis backup built in, with a Restore button, and last night's backup sitting right there. They clicked it.
The files came back. The database didn't.
They tried again. Same result. And again. Over the course of about ninety minutes the restore ran eight times, and every single time it reported the files restored and the database not.
Here's the part that matters. That restore wasn't failing harmlessly and leaving things as they were. Each attempt was deleting the live database first and then discovering it couldn't put the replacement back. The site went from mildly broken to completely gone, and the tool that was supposed to be rescuing it was the thing taking it apart.
Why the backup wasn't the problem
This is the bit worth sitting with, because it runs against the way most of us think about backups.
The backups were fine. Every night, on schedule, complete. If you had asked me that morning whether that site was protected, I would have said yes, and I would have been right. The data existed โ it still does.
What didn't work was the path back. Restoring isn't simply the backup played in reverse. It's a separate piece of machinery with its own steps, and any one of them can fail. In this case the restore had to remove the old database before writing the new one, and it turned out it could do the removing but not the writing.
A backup you have never restored is a promise nobody has checked.
What we did
The site also runs Akeeba Backup, on its own nightly schedule, entirely independent of Acronis. That's what saved it. Akeeba had a complete copy from 1:00 that morning, and none of what went wrong had anything to do with it.
We pulled the database out of that Akeeba archive and rebuilt it. Everything came back: every article, every menu, every user account. The most recent change to the site had been made weeks earlier, so nothing of substance was lost.
One thing had to change permanently, and we'll come to why in the technical section. The database now carries a slightly different name than it did before, and the site's configuration was updated to match.
Then we went looking to see how many other sites on that server could hit the same wall. The answer was twenty-two databases across seventeen accounts.
What this means for you
Four things, and only the last one is technical.
Having a backup is not the same as being able to restore it. If nobody has ever actually performed a restore from your backups, then what you have is an untested assumption. It's worth asking whoever looks after your site when a restore was last proven to work.
More than one backup, from more than one system, is not paranoia. This site was saved by the fact that Akeeba was running alongside Acronis. If it had only had the one, this would have been a very different article.
If a restore fails, stop and ask before trying again. The instinct to click it once more is completely natural, and here it did real damage. Eight attempts didn't get any closer to working โ they just kept demolishing something that was already gone.
There is a safe way to get a database out of Acronis, and it isn't the Restore button. Acronis will hand you the raw SQL file instead and let you do the import yourself, which keeps the destructive step under your control. Step-by-step instructions are in the technical section below โ they're more approachable than the heading suggests, and worth a look if you ever manage your own cPanel.
If you'd like us to check whether your own site's backups can actually be restored, get in touch. It's a short job and the answer is worth having before you need it.
The technical breakdown
The rest of this post is for fellow admins who want the receipts. If you came for the summary, you're done โ thanks for reading.
The symptom
Joomla site returning HTTP 500 on every request. The error log had the tell:
PHP Warning: session_start(): Failed to read session data: user
(path: /var/cpanel/php/sessions/ea-php81) in
/libraries/joomla/session/handler/native.php on line 260
The site stores sessions in the database, so a database that isn't there surfaces as a session failure rather than anything that says "database" in it. Checking the account's databases found the one named in configuration.php simply absent โ and in its place, an empty database with a strange doubled name.
The mechanism
The Acronis cPanel plugin documents its own restore behaviour plainly, under Recovering databases to the original location:
"As a result, the selected databases are recovered to the original location. The existing databases are overwritten. If a database no longer exists, it is recreated automatically."
Overwritten means dropped and rebuilt. And that rebuild goes through cPanel's own API โ which enforces the account's current database naming rules.
That's where it came apart. cPanel used to truncate database name prefixes to the first eight characters of the account username. Modern cPanel uses the full username. An account called northwoods created years ago would have databases named northwoo_sitedb; the same account today gets northwoods_sitedb.
Existing databases are never renamed when that rule changes, and they keep working indefinitely. MySQL doesn't care what cPanel's naming convention is, and neither does the application connecting to it. The mismatch is completely inert.
It is inert right up until something tries to create the database again. Then:
The name "northwoo_sitedb" does not begin with the required prefix "northwoods_".
So the sequence on an affected account runs:
- Drop the existing database โ succeeds, destructively
- Recreate it under the name recorded in the backup โ refused
- Load the data โ never reached
What's left is no original, plus an empty database under a doubled name: the current prefix bolted onto the front of the old full name, northwoods_northwoo_sitedb. That doubled name looks exactly like a human typing the wrong thing into a create-database box, which is precisely how I misread it for the first half hour.
Files restore cleanly throughout, because file recovery is a straight overwrite with no create step. That's why the reported symptom is always "the files work, the database doesn't" โ and why repeated attempts feel like they might eventually catch, when in fact the data went on the first one.
To be fair to Acronis: the destructive-overwrite behaviour is documented, and on any normally-named account the recreate step succeeds and you never see this. What isn't documented anywhere I can find is what happens when that recreate is refused. There's no pre-flight check that the recorded name is still a name cPanel will accept, and no rollback when it isn't.
Establishing that it really was the restore
MySQL keeps no record of this by default. Binary logging and the general query log are both off on a typical shared host, so nothing anywhere states that a particular database was dropped at a particular moment. The case has to be built circumstantially:
- The Acronis task log at
/opt/acronis/var/log/task-manager/โ JSON lines recording eachmount,restoreandunmounttask with timestamps. Eightrestoretasks across the morning. - Filesystem mtimes under the MySQL data directory โ the empty replacement database's directory creation time matched the final restore task to the second.
- The cPanel access log โ no phpMyAdmin and no MySQL-databases page anywhere in the session. Nothing else was touching that account.
That third check is the one worth remembering. Ruling out the human is what turns "something deleted the database" into "the backup tool deleted the database."
โ The safe way: download the dump, import it yourself
Acronis will hand you the raw SQL instead of trying to install it. This is the path to use on any account with a legacy-named database, and honestly it's a reasonable default even where the Restore button would work โ it keeps the destructive step in your hands.
Step 1 โ get the dump out of Acronis. In cPanel:
- Open Acronis cPanel & WHM
- Go to the Backups tab
- Select the recovery point you want
- Click Databases
- Tick the database(s)
- Click Download โ not Recover
Acronis prepares a .zip of SQL dumps and drops it in your home folder. It never touches the live database.
Step 2 โ unpack it. cPanel File Manager, navigate to your home directory, find the zip, Extract. You'll get a .sql file per database.
Step 3 โ prepare the target. Two situations:
- The database still exists and you're rolling it back โ take your own export first (phpMyAdmin โ Export) so you have a way back, then drop the existing tables. In phpMyAdmin: select the database, Check All, and choose Drop from the dropdown. The dump's
CREATE TABLEstatements will otherwise collide with what's already there. - The database is gone โ cPanel โ MySQL Databases โ create one. Remember it must start with your current account prefix. Then add your existing database user to it with ALL PRIVILEGES.
Step 4 โ import. For most sites, phpMyAdmin is the easiest route: cPanel โ phpMyAdmin, select the target database in the left sidebar, open the Import tab, choose the .sql file, click Go.
Watch the upload limit โ phpMyAdmin usually caps around 50 MB, and a busy site's dump can exceed that. If it does, and you have SSH access, the command line has no such limit:
mysql -u northwoods_dbuser -p northwoods_sitedb < dump.sql
Step 5 โ point the site at it. If the database name changed, update your application's config:
// Joomla โ configuration.php
public $db = 'northwoods_sitedb';
// WordPress โ wp-config.php
define('DB_NAME', 'northwoods_sitedb');
One thing that trips people up here. The database name and the table prefix are separate things, and only the first one is changing. The table names inside the dump โ abc123_content, wp_posts, whatever yours are โ are written into the file as-is and don't care which database they land in. So importing a dump taken from northwoo_sitedb into a brand-new northwoods_sitedb works perfectly. The only thing you change is the one line of config naming the database.
Getting the data back the hard way
In our case the Acronis dump wasn't the route we took, because by the time we understood what had happened we already had a known-good Akeeba archive from 1:00 that morning and no appetite for asking Acronis to do anything else with that account.
Akeeba's .jpa is its own archive format and no off-the-shelf tool unpacks it, so the dump had to be extracted by parsing the archive directly. It's a sequential format with a fixed header per entry, which makes it straightforward to stream through and pull out only what's wanted. (Akeeba's own Kickstart tool will of course restore the whole site, but we wanted the database alone, without touching a single file on the server.)
Two things about that dump are worth knowing if you ever have to do it by hand.
It's split. Not one file but thirty-six parts, which have to be concatenated in the right order before anything else.
The table prefix is abstracted. Table names are written with a #__ placeholder rather than the real prefix, so a restore can target any prefix. The obvious move is to replace every #__ with the real one. That is wrong, and it corrupts data.
In this dump there were 1,198 occurrences. Only 1,082 were table identifiers โ those are the ones wrapped in backticks. The remaining 116 were genuine row content: column comments, the JSON in Joomla's #__content_types table which stores table references with the placeholder intact, and an article whose body happened to document some SQL. Replace those and you have silently rewritten live content.
The safe substitution is backtick-anchored only:
# correct โ only touches real identifiers
body = raw.replace(b'`#__', b'`' + PREFIX)
# wrong โ also rewrites row data, column comments and article text
body = raw.replace(b'#__', PREFIX)
Worth verifying before you commit to it, too: check that no unquoted #__ is sitting in a genuine identifier position, and that every statement terminates cleanly. In this dump both checks passed, and the reassembled file imported 190 tables in under two seconds.
Finding every other account at risk
Any database whose prefix predates the naming change is exposed. cPanel keeps a per-account map of which databases belong to whom, which makes them easy to enumerate โ flag any database whose name doesn't begin with its owning account's username:
for f in /var/cpanel/databases/*.json; do
python3 -c "
import json
d = json.load(open('$f')).get('MYSQL', {})
owner = d.get('owner')
for db in (d.get('dbs') or {}):
if owner and not db.startswith(owner + '_'):
print('%-20s %s' % (owner, db))
"
done
On the server in question that returned twenty-two databases across seventeen accounts โ every one of them carrying the same landmine.
The fix, and the better fix
The immediate fix is to restore under a name cPanel will accept and update the application config to match. That gets the site back, and as a side effect permanently removes the problem for that account: the new name conforms to current rules, so future Acronis restores work normally.
The better fix for everyone else is to do that rename before it's needed rather than during an outage. Create a correctly-prefixed database, move the data across, update the config, drop the old one. Perhaps ten minutes on a live site, and it converts a recovery-time failure into a scheduled maintenance task.
Lessons filed away
A backup and a restore are two different products. One of them you test every night by running it. The other you find out about at the worst possible moment.
Destructive-first restores deserve more suspicion than they get. A restore that removes before it verifies it can replace has no safe failure mode. When the create step is the risky one, doing it first and swapping only on success costs nothing and removes the entire class of problem.
A dormant mismatch is not a latent bug โ but check which ones only bite during recovery. That eight-character prefix sat harmlessly for nine years and would have sat there forever, because the rule it violates only governs creation. It cost nothing until the one moment it cost everything. The category worth auditing isn't "things that look inconsistent"; it's "things that only matter when you're already having a bad day."
Repeating a failed restore is not free. Eight attempts did no more damage than one, as it happens โ the data was gone on the first. But that was luck, not design. The first failure is the moment to stop and find out what's actually happening.
โ Back to the plain-English summary
Add comment