A working website backup has three parts: every file under the web root, a database dump taken as one consistent snapshot, and the configuration that ties the two together. Keep at least one copy somewhere that is not the hosting account, on a schedule matched to how often the site changes, and restore one somewhere harmless before you ever need to.
The usual failure is not a missing backup. It is a backup that exists, ran last night, and does not restore. Half a database. An archive sitting on the disk that just failed. A scheduled job that has been reporting success for fourteen months while writing a zero byte file, which nobody looked at, because it said success.
What Is Actually in a Complete Backup
Three parts. The third is the one people lose, and losing it turns a restore into a day of guesswork.
Files means everything under the web root, and the part that matters is the uploads directory. Themes and plugins can be downloaded again from where they came from. Four thousand product photographs cannot, and neither can the PDF price list somebody attached to a page in 2019 and has never had a second copy of. On a WordPress site, wp-content/uploads is usually most of the disk footprint and nearly all of the material that exists in exactly one place.
The database is the site itself for anything built on a CMS. Posts, pages, products, orders, customer accounts, plugin settings, and menu structure all live there. A folder of PHP files with no database restores to an installation screen.
Then the configuration that connects the two, which is what goes missing. wp-config.php carries the database name, user, password and the authentication salts, and it usually sits outside whatever your backup plugin considers content. .htaccess carries rewrite rules, redirects accumulated over years, and sometimes an access restriction that is the only thing keeping a directory private. Cron entries, the DNS zone, and any certificate private key you paid for belong in the same bucket.
Here is a habit worth breaking: do not let a backup plugin write its archives into a folder under wp-content and call the job finished. That copy shares a fate with the thing it is protecting. Every deletion, every ransomware event, every billing lapse that closes the account takes the site and the archive in the same motion, and the archive is also web-readable by default on a lot of setups, which means a stranger can download your database.
Take the Database Dump as One Consistent Snapshot
A dump of a live database reads one table at 02:00 and another at 02:04. If a customer checks out in between, you get the order header without its line items, or an inventory count that no longer matches anything. The restore succeeds. The data is wrong, quietly, and you find out during a reconciliation six weeks later.
For InnoDB tables, mysqldump --single-transaction opens one consistent snapshot and reads the whole database inside it, without locking the site:
mysqldump --single-transaction --quick --default-character-set=utf8mb4 \
-u dbuser -p dbname > dbname-$(date +%F).sql
--quick streams rows rather than buffering a table in memory, which is what keeps a large table from killing the job on a modest account. The character set flag has to appear on both the dump and the import, or apostrophes and accented names come back as mojibake and you get to correct them one at a time.
The flag does nothing for MyISAM, so check what you actually have before trusting it:
sql
SELECT table_name, engine FROM information_schema.tables
WHERE table_schema = 'yourdb';
Mixed engines are worse than all MyISAM, because the snapshot covers part of your data and the job still exits zero. If you find MyISAM tables in a modern CMS install, converting them to InnoDB is usually the right fix and is worth doing on its own merits.
On WordPress, wp db export reads the credentials out of wp-config.php for you and passes your flags through to mysqldump, which removes the most common cause of a broken cron entry: a password that changed and a script nobody updated.
One thing that is never a backup. Copying the MySQL data directory over FTP while the server is running gives you files mid-write, and restoring them produces a database that either refuses to start or comes up subtly corrupt. Dump through the database, always. If you are doing this as part of a one-time move rather than as a routine, the checklist for switching hosts covers the freeze-and-cut sequence around the dump.
If the Copy Lives on the Server, It Is Not a Backup
The old rule is three copies of anything you would miss, with one of them held somewhere that has no connection to the other two. That second clause is the entire point, and it is the clause people quietly drop.
Ask what single event you are protecting against. A failed disk, a mistaken rm -rf, a compromised administrator account, an expired card, a support ticket that goes badly. Every one of those reaches everything inside the hosting account at once. A copy stored inside that account survives none of them, which is why it does not count as one of your three.
Your host's own backup is a convenience and should be treated as one. It is genuinely useful for the common case, where you broke something at four in the afternoon and want yesterday afternoon back. What it is not is a copy you control. You did not set the retention, you cannot verify it from outside the account, and it goes away when the account does. Accounts at BestPaidHosting are administered through the Hepsia control panel, which is where the account-level tools sit, and the same sentence would be true of any host with any panel: what lives in there is a floor, not the whole arrangement.
Whether a host retains anything for you at all is one of the practical differences between free hosting and a paid plan, and the guide to free and paid hosting works through where else that line falls.
For the copy you do control, pick a destination that has nothing to do with your web host. Object storage at a different provider, a NAS in your office, an external drive that gets swapped, a second account somewhere cheap. restic and rclone both handle scheduled encrypted uploads to most storage backends and are free.
Encrypt before it leaves. A database dump contains user records, order history, hashed passwords, and often API keys pasted into a settings table, so an unencrypted archive in a public storage bucket is a data breach waiting for somebody to run a scanner. The same logic runs through the rest of the website security basics worth having in place.
How Often, and How Long to Keep It
Frequency is set by one question: how much work are you willing to redo? The honest unit is hours of orders, comments, edits and uploads that somebody would have to reconstruct from memory. Daily is a habit rather than an answer.
| How the site changes | Database | Files | Keep |
|---|---|---|---|
| Brochure site, edited a few times a year | Weekly | Monthly, or after any change | 3 months |
| Blog or news site posting weekly | Daily | Weekly | 3 to 6 months |
| Store taking orders every day | Hourly, or continuous | Daily | 6 months, plus a monthly archive |
| Membership or forum with user content | Daily at minimum | Daily | 6 months |
Files and the database do not need the same schedule, and treating them as one job is what makes people copy tens of gigabytes of unchanged images every night until the account runs out of room. Uploads are append-mostly. An incremental file backup plus a frequent database dump is cheaper and faster than a full archive of everything, and it is what every tool in the previous section is built to do.
Retention is the setting that gets left at the default. Seven days is fine for a mistake you noticed the same morning and useless for a database that has been quietly accumulating injected spam links for two months, because by the time you look, every copy you hold already contains the problem. Keep one monthly copy for at least half a year. Storage is the cheapest thing in this entire article.
Have You Ever Restored One?
An untested backup is a hypothesis. You believe the archive is complete and that the dump will import into an empty database. Neither is knowledge until somebody tries it on a machine that is not the production server.
Run a drill. It takes an hour and you only need to do it properly once, then repeat it after any significant change to the site or the backup job:
- Create a scratch database and a subdomain you do not care about, or do the whole thing on your own machine.
- Import the most recent dump into the scratch database. Watch for errors rather than scrolling past them.
- Extract the file archive into the subdomain's directory and point a copy of
wp-config.phpat the scratch database. - Fix the site URL, since a WordPress database carries absolute URLs and the copy will otherwise redirect to production.
wp search-replace 'https://example.com' 'https://restore.example.com' --skip-columns=guidhandles it. - Load the site. Log in. Open the newest post, the newest order, and a page with images on it.
Then write down two things: how long it took, and what you had to look up. That elapsed time is your real recovery window, and it is almost always several times longer than the number in your head. The lookups are what you should turn into a one-page document, because the day you need this you will be doing it upset, possibly on a phone, possibly while somebody asks how long.
Delete the scratch copy afterward. A forgotten restore test on a live subdomain is an unpatched second installation of your site, and it will be found by a scanner long before it is found by you.
The Files Nobody Remembers Until the Restore
Small files, every one of them.
Four things live outside both the web root and the database, and each costs an afternoon when it turns out to be missing:
- Export the DNS zone, or at minimum photograph every record in it. Rebuilding mail authentication records from memory while mail is down is a bad hour.
- Certificate private keys. Free automated certificates get reissued and need nothing from you. A purchased one without its key is worthless.
- Print the crontab and keep the printout somewhere other than the server. Every path in it will be wrong after a restore onto different hardware, and one of those entries is doing something load-bearing that nobody documented.
- The list of who you pay: registrar, host, mail provider, payment gateway, and the addresses their invoices arrive at. An account that lapses because the renewal notice went to a mailbox on the dead server is a recoverable situation made permanent by paperwork.
What a Working Setup Looks Like
| Element | Get this right | What skipping it costs |
|---|---|---|
| Database dump | Single consistent transaction, correct character set | A restore that imports cleanly and holds wrong data |
| Files | Incremental, including uploads and dotfiles | Missing images and a lost .htaccess |
| Configuration | Credentials, cron, DNS, keys captured separately | A working site you cannot reconnect |
| Location | At least one copy outside the hosting account | Everything gone in one event |
| Encryption | Applied before the copy leaves the server | A dump of your users in somebody else's hands |
| Schedule | Set by acceptable data loss, not by habit | Retyping a day of orders |
| Retention | One monthly copy kept for months, not days | Every copy contains the same silent damage |
| Restore test | Done once for real, then after changes | Discovering the gap during the outage |
Start With the Restore, Not the Backup
If you already have backups running, do not add another tool this week. Take yesterday's copy and restore it into a scratch subdomain instead. One hour of that will tell you which rows in the table above your setup already satisfies, and reading about it substitutes for none of them.
If you have nothing running, start with the database. It is small, it changes constantly, it is the part that cannot be reconstructed from anywhere else, and a nightly dump pushed to storage outside your host is maybe twenty minutes of setup. Files can follow next week. A partial backup that runs beats a complete one you are still planning.