How to restore WordPress from a backup

Restore a WordPress site from a backup the right way: matching files and database, the official restore order, three working methods (hosting panel, UpdraftPlus, WP-CLI/SSH), and the WooCommerce HPOS rules that catch most operators by surprise.

The site is broken and you have a backup. The job from here is to restore the right things in the right order, prove the restore worked, and not lose new data along the way. There are three working paths to do this: through your hosting control panel, through UpdraftPlus inside the dashboard, or by hand over SSH with WP-CLI. This article walks through all three, plus the WooCommerce HPOS rules that catch most store owners by surprise, and the post-restore checks that separate "the page loads" from "the site actually works".

If you arrived here because you need to recover from a hack rather than a broken update, restore is only part of the answer. The full hack-recovery procedure lives in WordPress hacked: detecting and cleaning a malware redirect. Read that article first, then come back here for the restore mechanics. A database-only restore after a compromise is not a recovery; the file-resident backdoors survive and reinfect the site within hours.

Before you restore: confirm what kind of backup you have

A WordPress backup is two things: a SQL dump of the database, and an archive of the files. The official WordPress backup guide is explicit that backing up the filesystem does not include the database, and vice versa. Open the backup you have and confirm that both are present before you do anything else.

You are looking for two artifacts:

  • A SQL dump, usually named something like db-2026-04-07.sql, backup_2026-04-07_sitename_hash-db.gz, or a .sql.bz2 file. Anything between a few megabytes and a few hundred is normal.
  • A files archive, usually a .zip or .tar.gz. This must contain at minimum wp-content/ (themes, plugins, uploads, mu-plugins). If it also contains WordPress core directories and wp-config.php, even better.

Two checks before you continue:

  1. Files and database must come from the same point in time. Restoring last night's database into yesterday morning's files (or vice versa) gives you a site where plugin tables reference plugins that no longer exist, or where order rows reference orders that haven't been placed yet. Always restore from a matched backup set.
  2. The WordPress version on disk must match what the database expects. If the backup you have predates a major core upgrade, you need the matching WordPress core for it. Restoring a 6.7 database into a 6.5 install (or the other way around) leaves you with a "WordPress database update required" loop you then have to debug separately.

If something is missing from the backup set, stop and find a complete one before going further. A half-restore creates problems that take longer to clean up than the original incident.

Restoring via your hosting control panel

This is the fastest path when the host took the backup and you want to roll the entire site back to a recent snapshot. The control panel restore is atomic in most cases: files and database go back together, to the same point in time, in one click.

The exact path depends on your host:

  • cPanel with JetBackup: Files > JetBackup > Full Account Backups, pick a snapshot, click Restore. For a partial restore, the same JetBackup interface has Files, Databases, and Home Directory restore tabs that you can run independently.
  • Plesk: Websites & Domains > Backup & Restore, select the backup, choose "All objects (entire system)" or "Selected objects" if you only want the WordPress site, and run.
  • WP Toolkit on Plesk: open the WordPress install in WP Toolkit, click Backup/Restore, pick a snapshot, click Restore. The Plesk WP Toolkit documentation covers the exact flow including what is and is not included in a WP Toolkit backup.
  • Managed WordPress hosts (Kinsta, WP Engine, SiteGround): a Backups tab in the site dashboard, a list of snapshots, a Restore button. Some hosts gate restore behind a support ticket on cheaper plans.

The honest limitation of this path: you are restoring to the host's snapshot, on the host's schedule, with the host's retention. If the bad change happened 40 days ago and your host keeps 30 days, the panel cannot help you. That is the gap that your own off-host backup is supposed to cover. If you are unsure when the issue actually started, restore to a staging environment first, verify the snapshot is clean, and only then push to production.

After the panel says "restore complete", skip to Post-restore checks. Do not assume the page loading means the site works.

Restoring via UpdraftPlus (files first, database last)

UpdraftPlus is the most-installed backup plugin in the WordPress directory and the realistic choice for most operators who want a click-through restore. The plugin handles the order automatically and the TeamUpdraft restore documentation walks through the UI in detail. One thing to be precise about: UpdraftPlus's internal sequence is files first, database last, not the other way around. The TeamUpdraft team explains the rationale plainly: "By migrating files first and the database last, UpdraftPlus minimises the risk of a failed restore or migration." If the restore fails halfway through, having old files with a partial database is more recoverable than the reverse.

Prerequisites:

  • WordPress is reachable through the dashboard. If it is not, jump to the manual restore section below.
  • UpdraftPlus is installed and active (the same version, or newer, as the version that took the backup).
  • The backup files are either already on the remote storage you configured (Google Drive, Dropbox, S3, etc.) or you have them on your laptop and can upload them.
  • PHP memory_limit of at least 512MB and max_execution_time of at least 900 seconds. The TeamUpdraft team documents these as the recommended minimums for restore stability, and lower values are the most common cause of restores that die mid-way.
  • An hour you can spend without other people touching the site.

Steps:

  1. Take the site offline first, or at least put it in maintenance mode. A restore in flight on a live site is an open window for new orders, comments, or registrations that the restore will silently overwrite.
  2. Go to Settings > UpdraftPlus Backups > Existing Backups. If your backup is on remote storage, click Rescan remote storage so UpdraftPlus picks it up. If you have the files on your laptop, click Upload backup files and drop the five UpdraftPlus archives (-db.gz, -plugins.zip, -themes.zip, -uploads.zip, -others.zip) into the upload area.
  3. Find the backup row for the snapshot you want and click Restore.
  4. In the dialog, tick the components you want to restore. For a full restore: Plugins, Themes, Uploads, Others, and Database. For a "this morning's database is corrupted but the files are fine" scenario: Database only. For a "I deleted a plugin and want it back" scenario: Plugins only.
  5. Click Next. UpdraftPlus downloads files from remote storage if they are not local yet, then unpacks and validates them. This step can take several minutes for a multi-gigabyte uploads archive.
  6. Read the warnings on the next screen, then click Restore again to confirm. The restore log streams in the browser. Do not close the tab.
  7. Watch the log. UpdraftPlus processes each component in sequence: plugins, themes, uploads, others, then database. A successful restore ends with a green Restore successful! message.

Expected output: The log shows each component being processed with progress lines. A typical line looks like Files have been successfully copied into the original location followed by Restoring with backup set timestamp 1712534400. The final line is Restore successful! Actions: Return to UpdraftPlus configuration.

If the restore dies mid-way: This is a documented failure mode and the cause is almost always PHP max_execution_time being too low, the server being killed by an out-of-memory event, or a Cloudflare/web server proxy timeout (HTTP 524) cutting the connection while the restore is still running on the server. The TeamUpdraft team's restore-not-completing troubleshooting page lists the mitigations. The most reliable fix when you cannot raise PHP limits: restart the restore but pick only one component at a time. Restore Plugins on its own. Then Themes on its own. Then Uploads. Then Others. Then Database. Each request stays well under the time limit and the restore completes piecewise.

Verify the final result: When the log shows Restore successful!, return to the WordPress dashboard, hard-refresh, and skip to Post-restore checks. One important caveat about UpdraftPlus that the TeamUpdraft docs spell out: "UpdraftPlus only backs up what is specific to your site: database, media, plugins and themes. It does not back up [WordPress core] files." That means an UpdraftPlus restore is not a WordPress core reinstall. If your WordPress core directories (wp-admin/, wp-includes/) are corrupted or missing, you also need to drop a fresh copy of WordPress core onto the site, which UpdraftPlus does not do for you.

Manual restore: importing the database via WP-CLI or mysql

This is the most reliable path for developers with SSH access, and the only viable path when the WordPress dashboard is unreachable. There are no PHP time limits to fight; MySQL handles the import directly.

The official restore order from the WordPress Advanced Administration backup guide is files first, then database. That order matters because the database refers to plugins, themes, and uploads that need to exist on disk before WordPress tries to load them.

Prerequisites:

  • SSH access to the server, with shell write permission in the WordPress root.
  • WP-CLI 2.10 or newer installed on the server (wp --info to confirm). If you do not have WP-CLI, the same steps work with the mysql client directly; both are shown below.
  • MySQL or MariaDB credentials matching what wp-config.php expects. If wp-config.php is part of the backup, this is automatic. If not, you need to know DB_NAME, DB_USER, and DB_PASSWORD.
  • The backup files transferred to the server. scp, rsync, or pulling from S3 with aws s3 cp are all fine.

Step 1: Import the database with WP-CLI

The WP-CLI wp db import command reads credentials from wp-config.php, drops the dump into the database, and is the easiest path for most cases. Run from the WordPress root:

# Import an uncompressed SQL dump using credentials from wp-config.php.
wp db import /home/deploy/restores/db-2026-04-07.sql

For a gzipped dump, pipe it through gunzip and pass - to read from STDIN:

# Decompress on the fly and import via STDIN.
gunzip < /home/deploy/restores/db-2026-04-07.sql.gz | wp db import -

A few facts to be precise about. The command does not create the database; it expects the database named in wp-config.php to already exist. By default, WP-CLI applies optimizations (disabling auto-commit and key checks) that significantly speed up large imports. The --skip-optimization flag turns those off and is rarely needed. If you hit a specific compatibility problem during a large restore, try it then.

Expected output: A short status line per file: Success: Imported from '/home/deploy/restores/db-2026-04-07.sql'. That is the only success indicator. If WP-CLI prints anything else, treat it as a failure and read the error before continuing.

Step 2 (large databases): mysql directly with max_allowed_packet

For very large dumps where WP-CLI hits a max_allowed_packet ceiling or you want a progress indicator, drop down to the mysql client. The MySQL packet-too-large reference explains the parameter; the practical command is:

# Restore a large dump with an explicit packet size override.
mysql -u DB_USER -p DB_NAME --max_allowed_packet=256M < /home/deploy/restores/db-2026-04-07.sql

Or with a progress bar via pv (pipe viewer):

# Show MB/s and percentage while restoring a compressed dump.
pv /home/deploy/restores/db-2026-04-07.sql.gz | gunzip | mysql -u DB_USER -p DB_NAME

The default max_allowed_packet in MySQL 8.4 is 64 MB. If a row in your dump (a large wp_posts.post_content, a serialized option blob, a single big WooCommerce order with a long item list) exceeds it, the restore aborts with ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes. Bumping it to 256 MB or higher on the client clears the error in almost every case. If the server side also has the limit set low, you may need to set max_allowed_packet=256M in the [mysqld] section of my.cnf and restart MySQL.

For dumps split across many per-table SQL files (the export style that some mysqldump configurations produce), loop through them in shell:

# Import a directory of per-table SQL files in alphabetical order.
for DUMP in /home/deploy/restores/sql/*.sql; do
  wp db import "${DUMP}"
done

If SSH is unavailable and the dashboard is down, BigDump is the classic browser-based chunked importer for moving very large dumps when nothing else fits. Treat it as a last resort, not a default.

Manual restore: replacing wp-content files via SSH/rsync/FTP

The file restore is straightforward but the order matters. Restore files before the database, so that when WordPress next loads, every plugin and theme the database references actually exists on disk.

Steps:

  1. Take the site offline. The simplest way over SSH is to put a maintenance.html file in the web root and add an .htaccess rewrite, or set the site to maintenance mode through the host panel.

  2. cd to the WordPress root (the directory containing wp-config.php).

  3. Move the existing wp-content/ out of the way rather than overwriting it. You may need it for forensics later, especially after a hack:

    # Preserve the current wp-content for reference before replacing.
    mv wp-content wp-content.broken-2026-04-07
  4. Extract the file backup. For a tar.gz archive:

    # Unpack the backup archive into the WordPress root.
    tar -xzf /home/deploy/restores/files-2026-04-07.tar.gz

    For a zip archive (UpdraftPlus and many host backups):

    # Unpack each UpdraftPlus zip into the right destination.
    unzip /home/deploy/restores/backup_2026-04-07-plugins.zip -d wp-content/
    unzip /home/deploy/restores/backup_2026-04-07-themes.zip -d wp-content/
    unzip /home/deploy/restores/backup_2026-04-07-uploads.zip -d wp-content/
    unzip /home/deploy/restores/backup_2026-04-07-others.zip -d wp-content/
  5. Fix file ownership and permissions. The web server user (www-data, nginx, apache, or whatever your host uses) must own the files and the directories must be 755, files 644:

    # Restore standard WordPress ownership and permissions.
    chown -R www-data:www-data wp-content
    find wp-content -type d -exec chmod 755 {} \;
    find wp-content -type f -exec chmod 644 {} \;
  6. If the backup contains wp-config.php and the database credentials on this host are different from the source host, edit wp-config.php to match the new credentials before importing the database. The official WordPress backup guide calls this out specifically: when database credentials change, wp-config.php must be updated after the file restore and before the database import.

  7. Now run the database import from the WP-CLI section above.

Expected output: After the file restore, ls -la wp-content/ shows the expected themes/, plugins/, uploads/, and mu-plugins/ directories with current timestamps. After the database import, wp option get siteurl returns the URL the backup was taken from, and wp post list --format=count returns the post count from the backup.

Verify the final result: Skip to Post-restore checks. Do not bring the site back online until those checks pass.

Restoring a WooCommerce site with HPOS (the four-tables rule)

Skip this section if you do not run WooCommerce. If you do, this section is the difference between a clean restore and a store with corrupted orders that you find out about three days later.

WooCommerce's High-Performance Order Storage (HPOS) has been stable since WooCommerce 8.2 in October 2023 and is the default for new installs on recent WooCommerce versions. HPOS stores orders in four custom tables instead of wp_posts/wp_postmeta:

  • wp_wc_orders (main order data and frequently used columns)
  • wp_wc_order_addresses (billing and shipping)
  • wp_wc_order_operational_data (operational values like cart hash and download permissions)
  • wp_wc_order_meta (extension and custom meta)

The authoritative source for orders is determined by the woocommerce_custom_orders_table_enabled option in wp_options. If it is true, the four wc_order* tables are authoritative and wp_posts/wp_postmeta are sync targets. If it is false or absent, the legacy tables are still authoritative.

There are three restore scenarios for a WooCommerce store with HPOS:

Scenario 1: Full database restore. The entire database goes back, both the legacy tables and the four HPOS tables, to the same point in time. No special action is needed. Run the restore as described above and move on to verification.

Scenario 2: Selective restore of orders only. This is the dangerous one. If HPOS is active and you restore only the legacy wp_posts/wp_postmeta rows for orders, the HPOS tables are now out of sync with the legacy tables, and the store reads from HPOS, so your restored orders are invisible. You must restore all four wc_order* tables alongside any legacy order data, then re-sync:

# Re-sync HPOS tables with legacy tables after a selective order restore.
wp wc hpos sync
# Then verify both sides agree on every order.
wp wc hpos verify_data --verbose

The WooCommerce HPOS CLI tools documentation covers what sync and verify_data actually do. Treat the verify step as mandatory, not optional.

Scenario 3: Restoring a backup taken before HPOS was enabled. If your backup predates HPOS migration, the four wc_order* tables will not exist (or will be empty) in the dump, and the woocommerce_custom_orders_table_enabled option in the restored wp_options will be false or absent. WooCommerce falls back to legacy mode automatically. You then have a choice: stay on legacy and re-migrate to HPOS later when you are ready, or re-enable HPOS through WooCommerce > Settings > Advanced > Features and let WooCommerce re-migrate. Re-enabling HPOS triggers a fresh sync from the legacy tables; expect it to take time on a large catalog.

Why the verify_data step is non-optional: WooCommerce has had documented HPOS sync bugs in recent issue tracker history, including issue #53307 on metadata loss when HPOS is authoritative and sync is enabled. Whether those are fully resolved on your specific WooCommerce version is something wp wc hpos verify_data can confirm in a few seconds and you cannot confirm by eye. Run it.

Post-restore checks

The page loading is not the same as the site working. Run all of the following before you bring the site back online to traffic.

  1. Front page and a deep page: load the home page, then load a blog post or product page that you know was on the live site before the incident. If both render and contain content, the database is talking to the files correctly.

  2. Image rendering: open the front page in a browser with DevTools, look at the Network tab, and confirm that no image requests return 404. A common failure mode after a restore is "the database came from a snapshot where the uploads directory had an extra month of files that the restored uploads zip does not have".

  3. wp-admin login: log in. If you cannot log in, the most likely cause is a wp_options.siteurl mismatch between the restored database and the URL the site is now hosted at. Open phpMyAdmin from your hosting panel, select the WordPress database, open the wp_options table, and find the rows where option_name is siteurl and home. If the option_value does not match the URL where the site currently lives, edit both rows to the correct URL and save.

    If you have SSH access: WP-CLI can do the same thing and also handles serialized data in plugin options, which phpMyAdmin cannot:

    # Confirm the URL the database expects.
    wp option get siteurl
    wp option get home
    # If they are wrong, update them. Use search-replace, not raw SQL,
    # because plugin options often contain serialized PHP arrays.
    wp search-replace 'https://old-url.example' 'https://new-url.example' --skip-columns=guid
  4. Plugin and theme load: in wp-admin > Plugins, confirm every plugin you expect is listed and active. In wp-admin > Appearance > Themes, confirm the active theme is the right one. If a plugin is missing, the file restore was incomplete.

  5. WooCommerce orders: open WooCommerce > Orders and confirm the most recent order is the one you expect from the backup timestamp. If you ran HPOS, also run wp wc hpos verify_data one more time and read the output. Anything other than "all checks passed" is a problem.

  6. Permalinks: visit Settings > Permalinks and click Save Changes once without changing anything. This rewrites the .htaccess (Apache) or refreshes the rewrite rules in the database (nginx) and clears a class of "the home page works but no other URL does" failures.

  7. Send a test email: trigger a password reset email or a WooCommerce test transactional email. Mail delivery breaks easily after a server change, especially if wp-config.php was restored from a different host. If mail does not arrive, that is a separate problem; see WordPress not sending email (covered in the WordPress email subcategory).

  8. Check the error log: browse the site for a minute, then open the error log viewer in your hosting panel (cPanel: Metrics > Errors, Plesk: Websites & Domains > Logs). Anything that was not there before the restore is something you need to chase down before going live. If you have SSH access: tail -100 /path/to/php-error.log gives you the same view faster.

When all eight checks pass, take the site out of maintenance mode. Watch the error log and the server load for the first hour after going live; if something is going to break, that is when it usually surfaces.

When the restore fails mid-way

If you are reading this section, the restore has hung, returned an error, or completed with warnings. The three most common causes:

PHP max_execution_time exhausted. This is by far the most common UpdraftPlus failure mode. The fix is the TeamUpdraft component-by-component approach: instead of one restore that tries everything, run five smaller restores in sequence (Plugins, Themes, Uploads, Others, Database). If you can edit php.ini or .htaccess directly, raise max_execution_time to 900 and memory_limit to 512M before the next attempt. If your host caps these and will not raise them, drop down to SSH and use the manual path; PHP limits do not apply when MySQL handles the import directly.

Cloudflare or proxy 524 timeout. A 524 means the server is still working but the proxy in front of it gave up waiting. The restore may actually still be running on the server. Wait it out, then check the UpdraftPlus log and the database for new tables before restarting from scratch. If the restore is genuinely stuck, the same component-by-component fix above usually solves it because each request finishes inside the proxy timeout window.

max_allowed_packet too small for the dump. This shows up during the database import phase as ERROR 1153 (08S01): Got a packet bigger than 'max_allowed_packet' bytes. Restart the import with --max_allowed_packet=256M on the mysql client (and bump it on the server side too if needed). The MySQL packet reference has the full explanation.

The restore "succeeded" but the site is broken. This is the worst category because the failure is silent. Walk through the eight post-restore checks above, in order. The first one that fails tells you where to dig. The most common silent failures are: incomplete file extraction (some plugins missing because the unzip ran out of disk), URL mismatch in wp_options, file permissions wrong so the web server cannot read uploads, and HPOS being out of sync after a partial database restore.

If you are stuck, the data you want before asking for help is: the exact restore method (host panel, UpdraftPlus, manual SSH), the backup age and source, the WordPress and PHP versions, the WooCommerce version if applicable, the last few hundred lines of the PHP error log, and which post-restore checks have failed and how. With that in hand, the diagnosis is usually quick. Without it, it is guesswork.

If you would rather not be the one debugging this at 02:00, my WordPress backup strategy article covers how to set up a backup approach that makes restores routine instead of stressful, and the WordPress hacked: detecting and cleaning a malware redirect article covers the case where the restore is part of a security cleanup rather than an update rollback.

Want this to stop being your problem?

If outages or errors keep repeating, the fix is often consistency: updates, backups and monitoring that don't get skipped.

See WordPress maintenance

Search this site

Start typing to search, or browse the knowledge base and blog.