What a Restorable WordPress Backup Actually Contains
A tar of wp-content and a database dump can still restore into a broken site. The parts a complete backup needs, and the ones people leave out.
Martin Kiel · @mkiel
WordPress internals
- Topic
- WP
- Section
- WordPress Internals
- Published
- 20 OCT 2026
- Reading time
- 7 MIN
- Words
- 1751W
The restore looked fine. The homepage rendered, the admin loaded, the posts were there. Then a customer tried to buy something and the checkout returned a fatal error, because the orders lived in a custom table that the export had skipped. The images were broken too: the file archive had been built from wp-content/uploads, so the plugins and the theme were the ones already on the new server, at whatever version they happened to be. The site was up. It was not restored.
This is the ordinary shape of an incomplete backup. Nobody forgets the database or the uploads folder, because every guide names them. What gets left out is everything that does not look like content: the configuration file, the drop-ins, the custom tables, the rules in .htaccess, the DNS records, the cron jobs, the licences. Each omission is small on its own. Together they turn a restore into a rebuild with a deadline.
A backup is not a copy of the site. It is a copy of everything needed to make the site behave the way it did at a chosen moment. The archive is only the part of that which happens to be a file.
Disclosure: some links on this page are affiliate links, and WPSifero may earn a commission if you buy through them. That does not change the price you pay, and it does not change the checklist below: the omissions named here are omissions in any tool, including the ones this site is paid for.
The database is the smaller half, and it is more than wp_posts
The core tables are the ones everybody knows: wp_posts, wp_postmeta, wp_options, wp_users, wp_usermeta, the term tables and the comment tables. A dump that contains only those restores a blog. It does not restore a store, a membership site or anything with a plugin that keeps its own schema.
WooCommerce is the clearest example. With HPOS enabled, orders live in wp_wc_orders and their addresses in wp_wc_order_addresses, not in wp_posts. Action Scheduler keeps queued and past jobs in its own tables. A membership plugin, a form plugin, a page builder with a custom table and a booking system all add more. If the dump was produced with a command that lists core tables explicitly, those tables are missing and the site will look almost right until somebody opens the screen that reads them.
Size the database before deciding how to dump it. The query below lists the fifteen largest tables, which is usually enough to see whether the site is mostly content or mostly logs:
SELECT table_name,
ROUND((data_length + index_length) / 1024 / 1024, 1) AS mb
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY (data_length + index_length) DESC
LIMIT 15;
Two tables usually dominate on a busy store: the action scheduler log and a plugin's own log or session table. Excluding them makes the dump smaller and the restore faster, as long as the exclusion is written down. The danger is excluding them by accident because the export tool only knew about core tables. Excluding a log is a decision. Skipping a table is a bug.
wp-content is not another word for uploads
An archive of wp-content/uploads restores the media library and nothing else. The same directory holds the plugins, the themes, the must-use plugins, the language files and the drop-ins that change how WordPress boots. A restore that brings back uploads but takes plugins from the new server's existing install is a restore of two different sites glued together.
Include the whole of wp-content, then exclude from it. The cache directory, the local backup directory, and any node_modules or build cache a developer left behind are noise. Everything else belongs in the archive, including the plugins that were deactivated at the time. The inactive plugin is part of the state: the fact that it was deactivated is why the site worked.
Version drift is the quiet failure here. A plugin updates on Tuesday, the site breaks, and the operator restores Saturday's database onto a server with Wednesday's plugin files. The database expects a schema the code no longer creates, or the code expects an option the database no longer holds. Restoring files and database from one timestamp avoids the argument entirely, which is why the pair matters more than either half.
The files outside wp-content that restores forget
wp-config.php is the most important file not in wp-content, and the one most often skipped by archives that start at the document root and exclude PHP configuration. It holds the database credentials, the table prefix, the authentication keys and salts, and often a handful of constants that change behaviour: WP_DEBUG, DISALLOW_FILE_EDIT, WP_HOME, a Redis configuration block. Restoring without it means reconstructing it from memory at the worst possible time.
The salts deserve their own sentence. They are the keys that sign login cookies. Restoring an old wp-config.php with old salts logs every user out, which is fine during a disaster and confusing when it happens during a routine staging refresh. Restoring a new wp-config.php beside an old database is worse: the users table may predate the salts, and every session is invalid until someone logs in again.
Salts are state, not noise
Treat the salts as part of the backup. If you rotate them after a compromise, do it deliberately with wp config shuffle-salts, then take a fresh backup so the archive matches reality.
Then the rest of the document root: .htaccess with its redirects and rewrite rules, index.php, robots.txt, the favicon, the verification files that services ask you to place there, and any custom error page. None of these is large. All of them are the kind of thing that is easy to rebuild badly.
Inside wp-content there are also drop-ins that do not look like plugins: advanced-cache.php, object-cache.php, db.php, maintenance.php and sunrise.php. A file-level backup catches them. A selective backup of plugins and themes does not, and a site restored without its object-cache drop-in can behave very differently under load, which is the kind of problem that appears a week later.
What never lives in the archive and still has to be recovered
Some of the site is not on the server at all. The domain's DNS zone is the clearest case: A and AAAA records, the CNAME for www, MX records, SPF, DKIM and DMARC. A restore onto a new host without the mail records brings the website back and silently kills email, which is often the more expensive outage.
Then the server-side pieces: cron entries for the backup itself and for anything else the host runs, the PHP version and its extensions, the web server configuration, TLS certificates, firewall rules and the CDN settings that sit in front of the site. None of this is in a WordPress backup, and all of it is part of the environment that made the site work.
Finally, the commercial state. Plugin and theme licences tied to a domain or a key, API credentials for payment and shipping providers, webhook endpoints registered with third parties, the analytics property, the Search Console verification. A restored staging copy that uses the production payment key is not a staging copy. A restored production site that has lost its licence key loses updates at the moment it most needs them.
A manifest turns a restore into a checklist
The fix is one page, kept outside the site, listing every component, where it comes from, how often it is captured, and how to check it. The table below is a starting point for a small store. It is deliberately boring, because the point is that a tired person can follow it at eleven at night.
| Component | Captured by | Check after restore |
|---|---|---|
| Database, all tables | wp db export or the plugin | Row count in orders and posts |
wp-content, minus cache | File archive | Plugin list matches the old site |
wp-config.php | File archive | The site connects without edits |
.htaccess, drop-ins | File archive | Permalinks and cache behave |
| DNS zone | Exported text file | Mail sends, domain resolves |
| Cron and PHP config | Screenshot and notes | Scheduled jobs run |
| Licences and API keys | Password manager | Updates and payments work |
The commands are not exotic. A database export, a tar of the document root with the cache excluded, and a copy of the zone file will cover most of the table. What makes them a backup is that they run on a schedule, land somewhere off the host, and carry the same timestamp.
wp db export /tmp/site-db.sql --add-drop-table
tar -czf /tmp/site-files.tar.gz \
--exclude='wp-content/cache' \
--exclude='wp-content/backups' \
-C /var/www/site .
tar -tzf /tmp/site-files.tar.gz | head -40
That last line is the habit worth keeping. Listing the archive before storing it takes ten seconds and shows whether wp-config.php is inside, or whether the exclude pattern quietly removed it along with the cache.
Open the archive before you need it
A complete backup is a claim until it has been restored. The cheapest verification is to import the dump into a scratch database and count a few tables, then extract the file archive into an empty directory and compare the plugin directory names with the live site. That catches truncation, a missing table prefix and an archive that excludes more than intended.
For a fuller check, restore into a staging copy and walk the site. Log in, open the settings screen that reads a custom table, publish a draft, upload an image, place a test order if the site sells something, and send a password reset to confirm mail. The failures that matter are rarely visible on the homepage. They show up in the one screen nobody opened during the restore, which is why the checklist names screens rather than feelings.
The free path covers all of this. WP-CLI for the dump, tar for the files, and a bucket in a second account for storage. Paid services such as Jetpack's backup service capture the database and files together and keep the pair restorable from outside WordPress, which removes the most common way the pair drifts apart. A host like SiteGround adds panel snapshots and a staging copy, useful for the fast rollback, though the snapshot still sits in the same account as the site.
Where the paid route is the wrong purchase: a site whose host already takes off-provider snapshots, whose content changes monthly and whose owner will never open a manifest. There, the money is better spent on a second payment method and a calendar reminder. The product is not the plan. The plan is knowing what a complete copy contains, and having opened one.
Martin Kiel · @mkiel
WordPress internals
Writes about the load-bearing parts of WordPress: the options table, hook ordering, WP_Query behaviour and the database work that decides whether a plugin scales or falls over.
Continue