How to Recover a WordPress Site With No Backup
No backup? Check host snapshots, CDN caches, the Wayback Machine and old copies first, then rebuild from feeds and uploads, or rescue a database MySQL will not start.
Pick the right restore point, restore from a host snapshot, a backup plugin or raw files plus a SQL dump, and keep the orders and comments that arrived since.
An update broke the site, a hack defaced pages, or someone deleted the wrong thing, and you want to put it back from a backup. Restores cause a lot of extra damage: people pick the wrong restore point, overwrite the only copy of new orders, or restore a backup that already contains the problem.
A WordPress site is two things: files (core, themes, plugins, uploads) and a database (posts, settings, users, orders, comments, form entries). A restore puts one or both back to an earlier state. This guide shows site owners and designers how to do that safely, whether the backup lives at the host, in a plugin or as a zip and a .sql file.
A backup is a snapshot of files, database or both at one moment. Restoring a part rolls it back to that moment, and anything created later in that part disappears: posts, users, comments, orders, settings and uploads. Choosing what to restore matters as much as choosing when.
| Backup type | What it usually contains | Strength | Watch out for |
|---|---|---|---|
| Host snapshot (control panel backup) | Whole account or website: files, databases, sometimes email | Fast, works when WordPress cannot load at all | Restoring the whole account can also roll back email or other sites |
| Backup plugin archive (UpdraftPlus, BlogVault and similar) | Separate parts: database, plugins, themes, uploads, other files | Lets you restore one part at a time | Some plugins need WordPress working to restore; archives stored on the same server can be lost with it |
| Manual files plus SQL dump | A zip or tar of the site folder and a .sql or .sql.gz file |
Works anywhere, full control | Easy to forget hidden files like .htaccess, or to import into the wrong database |
If you are restoring because of a hack, read the first-hour hacked guide (FG-05) first. A restore removes the visible damage but does not close the hole the attacker used.
wp, mysql, tar or gunzip run in an SSH terminal on the server. Change into the site folder first, the one that contains wp-config.php, usually something like public_html. On some hosts WP-CLI is called wp-cli instead of wp. Commands starting with curl run on your own computer: macOS and Linux include it, and on Windows use curl.exe in PowerShell or a WSL terminal.Here is the safety backup to take before anything else, run over SSH in the site folder:
wp db export ../pre-restore-YYYY-MM-DD.sql --single-transaction
tar -czf ../pre-restore-files-YYYY-MM-DD.tar.gz .
YYYY-MM-DD is today’s date, so you can tell the files apart later. The ../ puts both files one level above the web root, where nobody can download them through the browser. wp db export reads the database name, user and password from wp-config.php and runs mysqldump for you; extra flags such as --single-transaction are passed straight through to mysqldump, which takes a consistent copy of InnoDB tables without locking the site. In tar -czf, c creates an archive, z compresses it with gzip and f names the output file; the final . means the current folder, hidden files included. Good output from WP-CLI is Success: Exported to '../pre-restore-YYYY-MM-DD.sql'. and tar prints nothing. Error establishing a database connection means the credentials are wrong (see FG-01).
Pick the newest backup taken before the change. If you know the update ran at 14:05 UTC on a given day, a backup from 03:00 UTC that morning is ideal. Check which time zone the backup list uses.
Here the rule is different: pick a backup from before the infection, not before you noticed it. Attackers often plant a backdoor days or weeks before they use it. Find the earliest sign of compromise first. Over SSH in the site folder:
find . -name "*.php" -newermt "YYYY-MM-DD" -not -path "./wp-content/cache/*" | head -50
wp user list --role=administrator --fields=ID,user_login,user_registered
Replace YYYY-MM-DD with a date a few weeks before you noticed the problem. -newermt lists files modified after that date, -not -path skips the cache folder, which changes constantly, and head -50 stops a huge list from flooding the screen. Good output is a short list that matches updates you remember. Bad output is PHP files in wp-content/uploads, oddly named files in core folders, or files dated before any update you ran. The second command lists administrators with their registration date: an admin you do not recognize, created on a specific day, is a strong clue to when the attack started. Choose a backup from before the oldest suspicious date. Modification times can be faked, so treat this as a guide, not proof.
Check that the backup set includes the parts you need and that sizes look normal; a database archive of a few kilobytes is a failed backup. For a manual dump, check the top and the bottom:
zcat BACKUP_FILE.sql.gz | head -20
zcat BACKUP_FILE.sql.gz | tail -5
BACKUP_FILE.sql.gz is the name of your compressed dump (use head and tail directly, without zcat, on an uncompressed .sql file). The top should show a -- MySQL dump or -- MariaDB dump header and the database name. The last lines should include -- Dump completed on followed by a date. If that line is missing, the dump was cut off and will import only part of your tables.
Most control panels keep automatic backups, the quickest route when WordPress will not load at all. Open the website in your control panel, find Backups, pick a date, and choose what to restore.
When the restore finishes, clear every cache layer (Step 6) before you judge the result.
Plugins such as UpdraftPlus, BlogVault and BackWPup store separate archives for the database and for groups of files, and can put any of them back.
A plugin that runs inside WordPress cannot restore a site whose WordPress is dead. You have three ways round this:
wp-content and import the database as in Step 4.You often do not need to restore everything. Matching the restore to the damage keeps more of your recent work.
| What broke | What to restore |
|---|---|
| A plugin or theme update, white screen, PHP fatal errors | Files only (often only that plugin or theme folder) |
| Content deleted, settings wiped, a bad search-replace | Database only, or a single table |
| Hack with injected files and spam posts | Both, from before the infection |
| Missing images | The wp-content/uploads folder only |
To replace a single plugin from a backup archive, rename the current folder first so you can switch back, then extract the old one. Over SSH in the site folder:
mv wp-content/plugins/PLUGIN_SLUG wp-content/plugins/PLUGIN_SLUG.broken
tar -xzf ../BACKUP_FILES.tar.gz -C . ./wp-content/plugins/PLUGIN_SLUG
PLUGIN_SLUG is the plugin’s folder name, which you can see in wp-content/plugins or with wp plugin list. BACKUP_FILES.tar.gz is your file archive. In the tar command, x extracts, -C . extracts into the current folder, and the path at the end tells tar to pull out only that folder. The path must match how it is stored in the archive; run tar -tzf ../BACKUP_FILES.tar.gz | grep PLUGIN_SLUG | head first to see the exact path. No output from the extract means success. Not found in archive means the path did not match.
Import the dump over the current database. Over SSH in the site folder:
wp db import BACKUP_FILE.sql
wp option get siteurl
BACKUP_FILE.sql is the uncompressed dump; unzip a .sql.gz first with gunzip -k BACKUP_FILE.sql.gz (the -k keeps the original). wp db import uses the credentials in wp-config.php and switches off unique and foreign key checks during the import to speed it up. Good output is Success: Imported from 'BACKUP_FILE.sql'., and the second command should print your live address. If it prints a staging or old address, the dump came from another copy of the site; the migration guide (FG-58) explains how to fix URLs safely. If the import fails with packet, collation or row size errors, see the large database import guide (FG-59).
Tables in the dump are dropped and replaced. Tables that exist now but are not in the dump are left alone, which is usually harmless.
If the backup is from 03:00 and you restore at 16:00, every order, customer account, comment and form entry from those 13 hours lives only in the database you are about to overwrite.
Before restoring, count what changed since the backup time. Over SSH in the site folder, on the current (broken) database:
wp db query "SELECT COUNT(*) FROM wp_comments WHERE comment_date_gmt > 'YYYY-MM-DD HH:MM:SS'"
wp db query "SELECT COUNT(*) FROM wp_users WHERE user_registered > 'YYYY-MM-DD HH:MM:SS'"
wp wc shop_order list --user=ADMIN_USER --after=YYYY-MM-DDTHH:MM:SS --fields=id,status,total --format=table
YYYY-MM-DD HH:MM:SS is the backup time in UTC; comment and user dates in these columns are stored in UTC. wp_ is the default table prefix: check yours with wp config get table_prefix and swap it in. The third line uses WooCommerce’s WP-CLI commands, which go through its REST API, so ADMIN_USER must be an administrator login; --after filters orders created after that date. A result of zero means you can restore the database freely. Any other number is data you must save first.
wp_wc_orders and related wp_wc_order_* tables. Older stores keep them as shop_order posts in wp_posts and wp_postmeta. Line items are in wp_woocommerce_order_items and wp_woocommerce_order_itemmeta in both modes. Check yours under WooCommerce, Settings, Advanced, Features.wp_actionscheduler_*), so a rollback can make one run twice or not at all. Check WooCommerce, Status, Scheduled Actions.Do not trust the green check mark. Check the restore from the outside and the inside.
wp core verify-checksums
wp plugin verify-checksums --all
wp db check
These need no placeholders. wp core verify-checksums compares every core file against the official checksums for your WordPress version: good output ends with Success: WordPress installation verifies against checksums., while lines like File doesn't verify against checksum: wp-includes/FILE.php or File should not exist point to modified or added files. wp plugin verify-checksums --all does the same for plugins from wordpress.org; premium plugins will report that checksums are not available, which is normal. wp db check runs a table check and should show OK for every table.
DB_PASSWORD in wp-config.php.wp config shuffle-salts over SSH, which logs everyone out.
A full mysqldump file is plain text with each table in its own block that starts with a -- Table structure for table comment. You can extract one table and import only that. Over SSH, in a folder outside the web root:
sed -n '/^-- Table structure for table `TABLE_NAME`/,/^-- Table structure for table `/p' BACKUP_FILE.sql > one-table.sql
grep -c "INSERT INTO" one-table.sql
TABLE_NAME is the full table name with prefix, such as wp_options. sed -n prints nothing by default, and the /start/,/end/p range prints from the line that starts this table to the line that starts the next one. The last line of the output belongs to the next table and is only a comment, so it is harmless. The grep -c counts insert statements: zero means the pattern did not match (check the exact table name with grep "Table structure" BACKUP_FILE.sql). If the table is the last one in the file, the range runs to the end, which is fine.
Import it with wp db import one-table.sql. Because the block includes DROP TABLE IF EXISTS, only that table is replaced.
To compare a backup with the current site without touching live data, restore the backup into a second, empty database (create it in your control panel) and compare row counts with SELECT COUNT(*) on the tables that matter, such as wp_posts, wp_users and wp_comments.
Open a ticket when the backups you expected are missing, a panel restore fails or hangs, or you need a restore from a snapshot only the host can reach. Send:
Use our diagnostic tools to check DNS, SSL and HTTP status, or the Emergency Room for other symptoms.
Did this fix it?
Still stuck? Start again from what you can see in the Emergency Room.
Still stuck?
Print a free one-page checklist to keep, or open the Emergency Room if the symptom does not match.
Get the free checklistsKeep reading
Search the field manual
Or jump straight to a symptom
Press / to search from any page, Esc to close