Introduction

Must-Have Backup is a modern backup and restore plugin for WordPress, built around three ideas that set it apart from traditional backup tools:

  • Time-machine restore — beyond full snapshots, the plugin continuously captures every file change and every database write. You can restore your site to any moment in time, not just to the last snapshot.
  • Content-addressed deduplication — files are split into chunks identified by their SHA-256 hash. Identical content is stored once, so repeat snapshots consume almost no additional space.
  • Restores that can't brick your site — every restore is staged alongside the live install first. The final swap is atomic and fully reversible until you explicitly confirm it.

Key Highlights

  • Full + incremental backups — weekly full snapshots with continuous file and database delta capture in between
  • 10+ storage destinations — Amazon S3, Backblaze B2, Wasabi, DigitalOcean Spaces, MinIO, Dropbox, Google Drive, OneDrive, WebDAV/Nextcloud, SFTP, FTP/FTPS, local folders
  • End-to-end encryption — Argon2id + XChaCha20-Poly1305; chunks are encrypted before upload
  • Disaster Recovery Kit — a standalone recovery.php that restores your site without booting WordPress at all
  • Migration bundle — move to a new host or domain with a self-deleting standalone installer
  • Self-verifying — automatic chunk integrity checks, restore drills, storage quota monitoring
  • Notifications — email, Slack and Discord alerts on success, failure, quota and integrity events
  • Full WP-CLI support — every operation scriptable from the command line

Requirements

  • WordPress 5.9 or higher
  • PHP 8.0 or higher (libsodium — bundled with PHP — is used for encryption)
  • MySQL 5.7+ or MariaDB 10.3+

Getting Started

Installation & Licensing

  1. Download the plugin ZIP from your account at musthaveplugins.com
  2. In WordPress, go to Plugins > Add New > Upload Plugin
  3. Select the ZIP, click Install Now, then Activate
  4. Navigate to the Backup menu to configure

On first visit you are redirected to musthaveplugins.com to connect your site. After completing the connect flow, you are redirected back and the plugin is activated.

Updates are delivered directly from musthaveplugins.com through the normal WordPress Plugins screen — when a new version is available, the familiar "update now" notice appears, no manual ZIP juggling needed.

Important: backups always keep running, even if your subscription lapses. An active subscription is required for restore staging and promotion, updates and support — but your data safety is never held hostage: confirming or rolling back an in-flight swap and the disaster Recovery Kit are never gated, and a licensing-server outage falls back to your last known status rather than blocking you.

Your First Snapshot

  1. Go to Backup > Overview
  2. Click Create snapshot now
  3. The plugin scans your files, dumps your database tables, and stores everything in the local chunk store under a randomized, web-inaccessible folder (wp-content/mhbackup-<random>/)

The first snapshot takes the longest since every chunk is new. Thanks to deduplication, subsequent snapshots only store what changed — a second run right after the first is nearly instant.

Once a snapshot exists, it appears on the Timeline tab, and — if you have storage destinations configured — upload jobs are queued automatically.

Settings Overview

The admin interface is organized into tabs:

  • Overview — snapshot status, create snapshot now, scan now, quick stats
  • Timeline — every snapshot with restore and time-machine actions
  • Restore — activity feed with restore points, time-machine target, staged restores, promote/confirm/rollback
  • Storages — add and manage off-site destinations, upload queue status
  • Manage — snapshot × destination matrix, retention policies, pinning
  • Schedule — snapshot cadence, file scan interval, DB write capture toggle
  • Data preferences — file/folder/extension and database-table exclusions, plus uninstall behaviour
  • Encryption — password setup, envelope mode
  • Migrate — build a migration bundle for moving the site
  • Notifications — email, Slack and Discord alerts

Deactivation & Uninstall

Deactivating the plugin removes the db.php capture dropin and unschedules all cron events — nothing else is touched.

Uninstalling keeps all backup data by default: the storage folder, the custom tables and your settings all survive, so reinstalling later loses nothing. Only when the "Delete all data on uninstall" option is explicitly ticked does uninstall wipe the storage folder, the custom tables and every plugin option.

Snapshots

How Snapshots Work

A full snapshot captures both your files and your database:

  1. File scan — walks your WordPress installation from ABSPATH, honoring all exclusions. Each file is split into chunks and stored in the chunk store.
  2. Database dump — every table (minus excluded ones) is dumped to a compressed per-table SQL file, which is itself chunked and stored.
  3. Manifest — a JSON document recording the environment, the complete file list with chunk references, and the table list. The manifest is the single source of truth for restoring the snapshot.

Snapshots run synchronously when you click Create snapshot now, and in the background via WP-Cron on the schedule you configure (default: weekly). Only one snapshot can run at a time — a concurrent trigger (manual click during a scheduled run, overlapping cron) is turned into a friendly "already running" notice instead of two builds trampling each other.

Chunk Store & Deduplication

All backup content lives in a content-addressed chunk store under wp-content/mhbackup-<random>/chunks/. Every chunk is named by its SHA-256 hash and stored in a fanout directory structure. The storage folder carries a random 128-bit suffix so its URL is unguessable on any web server, and deny rules for Apache and IIS (plus silent index.php files) are written and self-healed automatically; the plugin even probes its own folder over HTTP and warns you (with a ready-made nginx snippet) if it is still reachable.

This design has two important consequences:

  • Deduplication is automatic. If two snapshots reference the same file content, the chunk exists once on disk. Repeat snapshots of a mostly-unchanged site are nearly free.
  • Integrity is verifiable. A chunk's name is its hash — re-hashing a chunk immediately reveals any corruption.

When encryption is enabled, chunks are encrypted with authenticated encryption (AEAD) before being written.

Exclusions

Three exclusion layers are applied on top of each other:

  1. Hard excludes (always on) — the plugin's own storage root (wp-content/mhbackup-*), WP core update staging (wp-content/upgrade), and legacy backup folders
  2. Hard basename excludes (always on) — .git, .svn, .hg, node_modules
  3. User excludes — configured on the Excludes tab

User exclusion entries are interpreted by a simple rule — whether the entry contains a slash:

EntryMeaningExample matches
wp-content/uploads/privatePath prefix from ABSPATHwp-content/uploads/private/foo.jpg
wp-content/plugins/*/cachePath glob (* and ?)wp-content/plugins/foo/cache/baz.txt
cacheBasename anywhere in the treewp-content/cache/, any folder named cache
error_*Basename glob anywhereerror_log, error_2026.log

Default excludes: the cache basename (cache folders anywhere) and the extensions log, tmp, bak, old, swp.

Database tables can be excluded from the continuous write capture on the same tab (snapshots still dump every table). Entries work with or without the WP prefix and match exactly — actionscheduler_logs and wp_actionscheduler_logs both skip that one table and nothing else; use an explicit wildcard for families (actionscheduler_*). Every skip is a hole in time-machine replay for that table, so add only truly uninteresting churn.

Incremental Backups

File Change Scanner

Between full snapshots, an incremental scanner keeps track of file changes. It walks the installation using the same exclusion rules as the snapshot scanner and diffs the result against the last known state — recording created, modified and deleted files, along with their content chunks.

Scans are triggered automatically after relevant WordPress events — plugin and theme installs/updates, theme switches, media uploads — and run periodically via cron. You can also click Scan now on the Overview tab to run one immediately.

Database Write Capture

The most distinctive feature of Must-Have Backup: a lightweight database dropin that captures every write operation — INSERT, UPDATE, DELETE, REPLACE, TRUNCATE, ALTER, DROP, CREATE, RENAME — as it happens.

How it works:

  • Enabling DB write capture on the Schedule tab installs a db.php dropin into wp-content/
  • Captured operations are buffered in memory during the request and flushed once at shutdown as a single query — the overhead per page load is negligible
  • Timestamps are recorded with microsecond precision, and non-deterministic SQL functions (NOW(), RAND(), UUID()) are resolved before capture so replay is deterministic
  • Transaction-aware — writes inside a transaction are only logged on COMMIT; a ROLLBACK discards them, so a rolled-back WooCommerce checkout can never be replayed as a ghost order
  • The dropin recognizes foreign db.php files (HyperDB, LudicrousDB) and never overwrites them

The Schedule tab shows the capture status: whether the dropin is installed, whether it loaded in the current request, and the total number of operations captured.

Off-Site Delta Shipping

Incremental captures don't wait for the next full snapshot to leave the server. Every 15 minutes (configurable on the Schedule tab) the plugin ships the between-snapshot deltas to every configured destination:

  • New content chunks referenced by recent file changes — deduplicated against everything the destination already holds
  • The file and database change logs themselves, as compressed log archives

This means a time-machine restore point is off-site within minutes of the change happening — not days later at the next snapshot. On a wiped server, wp mh-backup delta import pulls the logs back so time-machine restore works again from the remote copy alone.

Restore

Restore Points & Activity Feed

The Restore tab leads with an activity feed, because you usually know what happened ("the update this morning broke checkout") — not the exact minute. The feed turns the captured change logs into human-readable restore points:

  • File and database events are merged into one time-ordered stream, curated (background noise like scheduler runs, session tokens and transients is hidden) and labeled by what they touched — orders, posts & pages, products, users, comments, media, plugin & theme files, settings
  • Events are clustered into short bursts; each burst is a card ("32 posts · 8 orders"), and full snapshots appear inline as green "Full backup" anchors
  • Picking a card sets the restore target and shows a plain-language "Undoes since then:" summary of everything the restore would roll back
  • Preview changes opens the full breakdown — per-category database and file change counts and sample file paths — before you commit to anything
  • A day-jump toolbar (Latest / 7 days ago / 30 days ago / date picker) makes a multi-month history navigable

Power users can still enter an exact date and minute under the Precise time disclosure — it sets the same target the feed does.

Staged Restore

Restores never touch your live site directly. Clicking Restore into staging on the Timeline tab rebuilds the chosen snapshot in an isolated area:

  • Files are reassembled under wp-content/mhbackup/work/restore-*/ with their original permissions and timestamps
  • Database tables are recreated under a random staging prefix — completely separate from your live tables
  • If any chunks are missing locally, they are fetched automatically from your configured storage destinations — there is no "restore from destination X" picker to get wrong; restore is local-first and transparently sources any gaps from whichever destination has them

Your live site keeps running throughout. You can inspect the staged files and tables before deciding to go further — or discard the staging with one click.

Staging runs as a resumable, time-budgeted state machine: the admin UI polls it step by step with a progress bar, each request stays short so it never trips a host's request timeout, and a dropped request simply resumes from where it left off — multi-gigabyte sites stage reliably even on constrained shared hosting.

Promote, Confirm & Rollback

When you're satisfied with the staged restore, Promote (swap) makes it live:

  1. Pre-swap health check — before anything live is touched, the staged database must contain the WordPress core tables and a well-formed site URL; a truncated or half-imported staging set is refused here and can never be promoted
  2. Maintenance mode is enabled
  3. Files are renamed into place (renames are atomic and fast — the swap takes seconds, not minutes)
  4. A single RENAME TABLE statement swaps every table simultaneously
  5. Maintenance mode is lifted

If anything fails mid-swap — including the post-swap sanity check — the swap reverses itself automatically: the database rename is undone and the original files are put back, so a failed promotion always lands you on the original site, files and database together.

The previous live version isn't deleted — it's held in a temporary area. After verifying your site works:

  • Confirm — drops the temporary copies, the restore is final
  • Rollback — reverses the entire swap, putting the previous version back exactly as it was

The plugin's own backup data (mhbackup/ folder and tables) is preserved through the swap, so your backup history survives every restore.

Time-Machine Restore

With incremental capture enabled, you aren't limited to snapshot times — any moment in your captured history is a valid restore target. Whether the target comes from an activity-feed card, the Precise time field, or a snapshot's Restore to time… action, the mechanics are the same:

  1. The plugin picks the newest snapshot at or before your target time as the baseline
  2. Every recorded file change up to the target moment is replayed on top
  3. Every captured database operation up to the target moment is replayed in order — with auto-increment IDs pinned to their original values, so replayed rows get the exact primary keys they had live

The result is staged exactly like a normal restore — with the same promote/confirm/rollback safety net.

Per-Table & Per-Path Exceptions

Sometimes you want different parts of your site restored to different times. The classic case: a content problem happened this morning, but you've received orders all day. Under the Advanced block on the Restore tab you can add exceptions on top of the base moment:

  • Table exception — pin one database table to its own moment (e.g. keep wc_orders at "now" while everything else goes back to 9:00 AM)
  • Path exception — pin one folder subtree to its own moment (e.g. keep wp-content/uploads current)

The whole combined state is staged in a single pass — one base moment plus any number of exceptions, each with its own target time. If the chosen moments could leave related data inconsistent, the UI warns you but leaves the decision to you.

Large Sites & Shared Hosting

Restore and migration are built to survive constrained shared hosting on multi-gigabyte sites:

  • Files stream at any size — restore reassembles content chunk by chunk straight to disk, so memory stays flat whether a file is 4 KB or 4 GB
  • Database imports stream statement by statement — a multi-GB table never has to fit in PHP's memory limit
  • Replay is batched — the change logs are walked in bounded batches with a persisted cursor
  • Staging is resumable — short polled requests with a progress bar; a dropped request resumes from its cursor

Practical prerequisites: a full restore needs roughly 2× the site size in free disk (staging holds a full copy, and the swap briefly parks the previous live copy alongside it). For the very largest sites, run the restore from WP-CLI — the command line has no web-request timeout at all.

Storage Destinations

Overview

Snapshots are produced locally, then shipped off-site by the storage subsystem. Add any number of destinations on the Storages tab — each snapshot is uploaded to every active destination via a background queue.

Supported destinations:

  • Local folder — another disk or mounted volume on the same server
  • SFTP / FTP / FTPS — classic server-to-server transfer (SFTP client bundled, no PHP extension needed)
  • S3-compatible — Amazon S3, Backblaze B2, Wasabi, DigitalOcean Spaces, MinIO
  • Dropbox — one-click connect
  • Google Drive — one-click connect
  • OneDrive
  • WebDAV — Nextcloud, ownCloud, Synology, Apache mod_dav

The manifest is always uploaded last, so an interrupted upload never leaves a remote copy that looks complete but isn't.

Several sites can share one destination. Every site writes under its own must-have-backup/<hostname>/ namespace inside the destination, so multiple WordPress installs can back up into the same bucket, folder or server without colliding — while each site's own snapshots still deduplicate against each other.

S3-Compatible

Native AWS Signature V4 implementation — no SDK bloat. Works with Amazon S3 and any S3-compatible service:

  • Amazon S3 — enter access key, secret, region and bucket
  • Backblaze B2 — use the S3-compatible endpoint from your B2 dashboard
  • Wasabi / DigitalOcean Spaces / MinIO — enter the custom endpoint URL (usually with path-style addressing enabled)

Create a dedicated, least-privilege IAM user scoped to the one bucket (list, put, get, delete) — never use your root keys. Add destination immediately round-trips a small canary object, so a wrong permission surfaces on the spot.

Dropbox

Two auth modes on the Add-destination form:

  • Connect with Dropbox (default) — one click, sign in, approve. Nothing to configure in the Dropbox developer console; backups land in the app's own scoped folder, and the plugin can't see anything else in your Dropbox.
  • Advanced: access token — create your own app in the Dropbox App Console and paste a generated access token, if you prefer not to use the one-click flow.

Google Drive

Two auth modes on the Add-destination form:

  • Connect with Google Drive (default) — one click, Google sign-in popup, approve. Uses the drive.file scope, so the plugin only ever sees files it created itself. Backups go to an auto-created "Must-Have Backup — <host>" folder. Nothing to configure in Google Cloud.
  • Advanced: service account — create a service account in Google Cloud Console, download its JSON key, share a Drive folder with the service account's email address, and paste the JSON + folder ID. No third-party relay involved.

OneDrive

Connects through the Microsoft Graph API. Register an Azure AD app (Web), add the redirect URI shown on the form, grant Files.ReadWrite + offline_access, create a client secret, then use the form's built-in authorize → code-exchange helper to obtain the refresh token.

WebDAV / Nextcloud

Connects to any WebDAV server with Basic authentication — Nextcloud, ownCloud, Synology NAS, or a plain Apache mod_dav share. Enter the base URL, username and password (for Nextcloud, use an app password).

SFTP / FTP / FTPS

A pure-PHP SFTP client is bundled, so SFTP works without any server-side PHP extensions. Plain FTP and explicit FTPS are also supported for legacy hosts.

SFTP is its own entry in the destination dropdown, with a Password / SSH key toggle — paste a private key (with an optional passphrase) instead of a password if your server is key-only. Note that the standalone Recovery Kit uses password auth for SFTP (it carries no SSH library); if your server is key-only, create a temporary password user for the recovery and remove it afterwards.

Upload Queue & Bundles

Uploads never block your site. Every snapshot enqueues one upload job per destination; a WP-Cron worker processes the queue every minute. The Storages tab shows live queue status with pending, running and failed counts.

For network destinations (SFTP, FTP, and cloud drivers), chunks are packed into ~64 MB bundle archives before transfer. Instead of uploading tens of thousands of small chunk files — slow on any protocol — a snapshot ships as a handful of large, fast transfers. Local destinations keep the chunk-per-file layout, where renames are cheap.

Bundles are built and shipped one part at a time — pack, upload, delete, repeat — so the temporary disk a snapshot upload needs stays bounded to one bundle (~64 MB), not the whole snapshot. This is what lets even a multi-gigabyte backup ship from a shared account with a tight disk quota.

Each destination keeps a small index of what it holds. If it ever drifts from reality (files deleted by hand on the remote, a restored server), the Sync button — or wp mh-backup storage reconcile — rebuilds the index from the remote itself. Sync also ships any pending between-snapshot delta, so a destination reads "Up to date" only when the full snapshots and the change-log have both reached it — until then it shows "Syncing changes" rather than a misleading green.

Removing a destination takes it off this site instantly; the backups already on its cloud storage are kept by default. Ticking the optional wipe checkbox also deletes the remote copy — and if that wipe can't complete, the destination is kept and the reason shown, so cloud data is never silently orphaned.

Encryption

Password Mode

The default encryption mode is designed to be a one-field setup:

  1. Go to the Encryption tab
  2. Choose a password — a live strength meter nudges you toward a strong one, and an eye-toggle lets you verify what you typed (no confirm-twice)
  3. Click Enable

From that point on, every new snapshot is encrypted with XChaCha20-Poly1305 authenticated encryption. The password is verified with Argon2id — the same primitives used by modern password managers.

Backups run unattended (cron never prompts for a password); the password gates restore operations. Disabling encryption requires the current password. Existing encrypted snapshots stay encrypted.

Enabling encryption does not retroactively encrypt previously-taken snapshots — for a clean encrypted-only history, set up encryption before taking your first snapshot. Mixed histories still restore fine: every stored chunk carries a format marker, so plaintext and encrypted content can coexist and mode switches never strand old backups.

The threat model: encryption protects your backups from the storage provider — or a leaked storage API key. Chunks are encrypted before upload, so Amazon, Dropbox or your FTP host only ever see random bytes.

Envelope Mode

Envelope mode uses an RSA-4096 keypair: the public key stays on the server and wraps each chunk's own random encryption key — so deduplication keeps working across snapshots. The private key is shown once at setup for you to download and keep safe. Where it lives after that is your choice:

  • Stored on the server (default) — the private key is also kept in the WordPress database, so restores stay fully automatic. Same threat model as password mode: your backups are protected from the storage provider or a leaked storage API key.
  • Offline only (opt-in) — untick the store-key option at setup and the private key exists only in your offline copy. Then even a fully compromised server cannot decrypt previously created backups. During restore the UI asks for the key, uses it for that one request, and never persists it.

Key Handling

  • The master key is stored in the WordPress options table — not as a file under the web root, so a misconfigured server can never serve it over HTTP
  • An Argon2id verifier of your password gates the restore UI
  • Disabling encryption keeps the master key — it is random, not derived from your password, and deleting it would permanently destroy every already-taken encrypted backup. Re-enabling reuses it.
  • Losing the password/key means losing access to encrypted backups. There is no recovery path — this is by design. Store your Recovery Kit and password in a safe place.

Retention & Management

The Manage Tab

The Manage tab shows a matrix: snapshots as rows, destinations as columns. Each cell shows whether that snapshot exists at that destination, with per-cell actions:

  • Pin / Unpin — protect a snapshot from retention sweeps at that destination
  • Delete — remove the snapshot from that one destination
  • Drop everywhere — remove the snapshot from local storage and all destinations

Retention Rules

Retention is per-destination with a global fallback — so your local disk can keep only the last week while an archive FTP server holds a year of history.

  • Keep last N — each destination keeps the N newest snapshots; older ones are swept
  • 0 / blank — keep everything at that destination

The sweep runs automatically once a day, or on demand via Run sweep now. Deletes are refcounted: a chunk is only removed when no retained snapshot — or a shipped incremental delta — still references it, so retention can never corrupt a kept snapshot or hole a time-machine replay.

The incremental change logs have their own days-based retention, applied by the same daily sweep. Local change-log rows are additionally only pruned once every destination has received them — a lagging destination pauses the prune rather than losing its disaster-recovery replay.

Pinning Snapshots

Pinned snapshots are never touched by retention sweeps, regardless of the keep-last-N setting. Pin the snapshot you made right before a major upgrade, or the last known-good state of a client site — it stays until you unpin it.

Schedule & Data Preferences

Schedule Tab

  • Full snapshot cadence — days between automatic full snapshots (default 7 = weekly; 0 = manual-only, relying on incrementals)
  • File scan interval — how often the incremental file scanner runs
  • Incremental push interval — how often captured deltas are shipped to destinations
  • DB write capture — toggle for the database dropin, with live status display

A live "next auto-snapshot" line under the cadence input shows exactly when the next automatic snapshot will run.

Cron fallback: on hosts where DISABLE_WP_CRON is set but no system cron was configured to replace it, scheduled tasks would normally stall silently — exactly what a backup plugin must never do. The plugin detects this and kicks the due events itself on admin page loads (non-blocking, throttled to once a minute). With a working WP-Cron or system cron this is a no-op.

Data Preferences Tab

All the "what data, and what happens to it" settings in one place — file/folder patterns, extensions, and the database tables captured by the continuous write log. Changes auto-save and are picked up immediately by the running scanners. See Exclusions for the pattern syntax.

This tab also holds the uninstall behaviour toggle (Delete all data on uninstall) — see Deactivation & Uninstall.

Disaster Recovery

The Recovery Kit

The Recovery Kit answers the worst-case question: what if WordPress won't load at all? A fatal error from a bad update, a broken database, a corrupted db.php — cases where no plugin-based restore can help, because the plugin itself can't run.

The kit is a single self-contained PHP file that never boots WordPress:

  • Generated in your account at musthaveplugins.com — you choose a password, which is baked into the file as a one-way Argon2id hash
  • Not stored on your site, no standing endpoint — it only exists while you're actively recovering
  • Reads database credentials directly from wp-config.php and connects with mysqli — a broken WordPress is irrelevant. Even a dropped database is fine: the backup's substance lives on disk, and the database is just the restore target
  • Restores from the local backup data on disk first, falling back to your remote destinations if the disk is gone — S3/S3-compatible, FTP/FTPS, SFTP (password auth), WebDAV, Dropbox, Google Drive and OneDrive are all supported directly in the tool
  • Handles every encryption mode: plaintext, password mode, and envelope mode — stored keys are read from the surviving database/disk automatically; an offline-only envelope key is pasted in
  • Never overwrites wp-config.php during the file restore — your live database credentials stay intact
  • Self-deletes after a successful recovery

Generate your Recovery Kit right after setting up backups, and store the file somewhere off the server — a password manager attachment, an encrypted USB stick, your laptop.

Generating Your Kit

The kit is generated from your account on musthaveplugins.com — it is available with every subscription that includes Must-Have Backup:

  1. Log in at musthaveplugins.com and go to My Account > Subscriptions
  2. On your Backup subscription, click the three-dot (⋯) menu in the top right corner
  3. Choose Site recovery kit from the menu
  4. Enter a recovery password — this is the password you will type into the tool when restoring, so pick one you can retrieve in a disaster (password manager, printed note in a safe)
  5. Click Generate & download recovery.php and store the file somewhere off the server

The password is embedded in the downloaded file as a one-way Argon2id hash — it cannot be recovered from the file, and we never see or store it. Forgot it? Just generate a new kit with a new password; the old file simply stops being useful.

Using recovery.php

  1. Upload recovery.php next to your site's wp-config.php (site root)
  2. Open it in a browser: https://yoursite.com/recovery.php
  3. Enter your Recovery Kit password
  4. Pick a snapshot from the list — or use Connect remote storage if the local disk is gone — and start the restore (offline-only envelope key: paste it in first)
  5. When finished, the tool deletes itself

Authentication is the password you chose when generating the kit, embedded as a one-way Argon2id hash, with per-IP rate limiting (6 attempts per hour) against brute force. If a plugin caused the breakage, deactivate it after the restore, before it runs again.

It never reports a restore it didn't achieve. If a file or table can't be reassembled because a chunk is missing from the source you're restoring from, the tool marks the restore incomplete, names exactly which file and which chunk is missing, and does not self-delete — so you can resolve the gap (try the other source: the snapshot list merges disk and remote) and re-run, resuming where it stopped. Because an incomplete restore keeps its staging for resume, the tool shows a Delete leftover data button to reclaim that space straight from recovery.php (WordPress may be down, so the plugin can't clean it for you).

Migration

Migration Bundle

The Migrate tab packages your site into exactly two files — a standalone installer.php and an archive.zip — so a move is two uploads, not thousands of loose files over FTP. Use it to move to a new host, a new domain, or both.

You can bundle the site as it was at any moment in time, exactly like a restore: pick a minute with the date picker, and the bundle is built from the baseline snapshot with all captured file and database changes replayed forward to that moment.

The build survives closing the browser. Bundling runs server-side, so you can close the tab and come back later — reopen the Migrate tab and any finished or still-building bundle resurfaces with its download buttons (or a live progress bar it resumes to completion). A ready bundle stays available for 24 hours and then auto-deletes so it can't quietly eat disk; a discreet Delete bundle link removes it immediately.

Running the Installer

  1. Upload both files — installer.php and archive.zip — into an empty directory on the target server (no need to extract anything)
  2. Open installer.php in a browser
  3. Enter the new database credentials and the new site URL, hit Install
  4. The installer extracts the files straight into place, imports the database streaming statement by statement (multi-GB tables are fine), and rewrites URLs across every text column

The search-replace is serialization-aware: PHP serialized values get correct length prefixes after replacement, and JSON-encoded and escaped URL variants are handled too — so theme options, widgets and page builder content survive the URL change intact. The installer self-deletes (and removes the archive) when done.

Plugin Rollback

A bad plugin update is the most common reason anyone needs a backup — so the plugin also attacks the problem directly. Every WordPress.org-hosted plugin on the Plugins screen gets a Rollback action link:

  1. Click Rollback in the plugin's row on the Plugins screen
  2. Pick the version you want from the list of previous releases
  3. The chosen version is installed over the current copy through the standard WordPress upgrader — if the plugin was active, it is reactivated

WordPress.org keeps every release's package available, so no backup restore is needed just to undo one plugin's update. Premium or custom plugins that aren't hosted on WordPress.org don't get the link — for those, use a regular restore or the time-machine. Rolling back doesn't undo database changes a plugin's update routine may have made; when in doubt, take a snapshot first.

Monitoring

Notifications

Configure alerts on the Notifications tab. Each channel is independent — leave a field blank to skip it:

  • Email — subject prefixed with [MH Backup / hostname] and a short human-readable summary
  • Slack — incoming webhook URL
  • Discord — webhook URL

Events: snapshot completed, snapshot failed, upload job failed, storage quota warning, integrity check failure, restore drill failure. Success and failure notifications can be toggled separately.

Integrity Checks

A scheduled job samples stored chunks, re-hashes them (and decrypt-verifies when encryption is on) and compares against their content address. Any mismatch fires an integrity alert — you learn about silent corruption before you need the backup, not after.

Restore Drills

Opt-in: the plugin periodically stages your newest snapshot into a throwaway area, sanity-checks the result, and cleans up. This is the strongest guarantee a backup system can offer — regular proof that your backups actually restore.

Storage Quota Warnings

Every destination's free space is checked during the daily sweep. When usage crosses your configured threshold, you get a quota warning notification — before the destination fills up and uploads start failing.

WP-CLI

Overview

Everything the UI can do is also available under wp mh-backup. This makes system-cron scheduling, CI pipelines and server automation straightforward. The CLI is also the escape hatch for very large sites — it has no web-request timeout. As in the UI, restore commands require an active subscription; backup commands never do.

Snapshots & Restore

# Force a full snapshot right now
wp mh-backup snapshot --reason=deploy

# List snapshots (newest first)
wp mh-backup list --limit=20

# Stage a restore of a snapshot (does not swap)
wp mh-backup restore <snapshot_id>

# Time-machine stage: baseline + replay up to the given moment
wp mh-backup restore --time=<unix_timestamp>

# Verify integrity: sample chunks, re-hash, decrypt-verify
wp mh-backup verify

Storage & Queue

# Destinations
wp mh-backup storage list
wp mh-backup storage test <id>
wp mh-backup storage sync <id>     # enqueue all snapshots for upload
wp mh-backup storage reconcile <id> # rebuild the destination index from the remote

# Upload queue
wp mh-backup queue status
wp mh-backup queue run              # process up to 50 pending jobs
wp mh-backup queue reset            # reset stuck running jobs

# Incremental deltas
wp mh-backup delta ship             # push pending deltas to destinations
wp mh-backup delta import <id>   # disaster recovery: pull delta logs back

Automation Cookbook

Weekly full snapshot from system cron (bypassing WP-Cron):

0 3 * * 0 cd /var/www/site && wp mh-backup snapshot --reason=weekly-cron --allow-root

Nightly integrity verification:

0 4 * * * cd /var/www/site && wp mh-backup verify --allow-root

Snapshot before every deployment:

wp mh-backup snapshot --reason=pre-deploy && ./deploy.sh

Developer Reference

Everything the plugin exposes for programmatic integration lives under the mhbackup/ hook namespace. Options are read and written through global helpers, and every read/write passes through a filter/action pair, so any setting can be overridden or reacted to from code:

mhbackup_get_option($key, $default)    // read (filtered)
mhbackup_set_option($key, $value)      // in-memory only
mhbackup_update_option($key, $value)   // persists to DB (fires update actions)

Actions

Fired by the plugin — hook these to react to backup lifecycle events:

ActionArgumentsWhen
mhbackup/snapshot_created$snapshot_id, $manifestA full snapshot finished successfully
mhbackup/snapshot_failed$reason, $errorA snapshot attempt failed
mhbackup/queue_job_failed$job_id, $messageAn upload/queue job exhausted its retries
mhbackup/quota_warning$destination_id, $percentA destination crossed its quota warning threshold
mhbackup/integrity_failed$messageIntegrity check found corrupt chunks, or a restore drill failed
mhbackup/register_storage_driversStorage registry boot — register custom drivers here
mhbackup/option/{key}/updated$valueA specific option was persisted
mhbackup/option/updated$key, $valueAny option was persisted

Filters

FilterArgumentsPurpose
mhbackup/option/{key}$valueFilter every read of a specific option
mhbackup/option/not_set/{key}$defaultFilter the default returned when an option has never been set
mhbackup/update_option/{key}$valueFilter a value just before it is persisted
mhbackup/check_option$result, $key, $value, $compareOverride option comparison results
mhbackup/ajax_update/{key}_option$response, $valueFilter the JSON response of a settings-UI option save
mhbackup/has_active_subscription$active, $statusOverride the subscription check — add_filter('mhbackup/has_active_subscription', '__return_true') unlocks restore on dev/staging clones
mhbackup/api_url$urlOverride the licensing/relay API base URL at runtime
mhbackup/get_template$html, $templateFilter rendered template output
mhbackup/verbose_logfalseReturn true to get plugin log lines in debug.log even without WP_DEBUG
mhbackup/dev_skip_ssl_verifyfalseDev/test only: skip SSL verification on storage driver HTTP calls (self-signed local endpoints)

Cron Events

All recurring work runs on named WP-Cron events — you can trigger any of them manually (wp cron event run <hook>) or reschedule them:

HookDefault scheduleDoes
mhbackup/queue_workerevery minuteProcesses the upload/job queue
mhbackup/full_snapshotdaily (gated by the cadence setting)Automatic full snapshot
mhbackup/file_scanhourly (gated by the scan interval)Incremental file change scan
mhbackup/incremental_uploadevery 15 minutesShips file/DB deltas to destinations
mhbackup/retention_sweepdailyApplies retention policies + quota checks
mhbackup/integrity_checkdaily (gated)Samples and verifies stored chunks
mhbackup/restore_drilldaily (gated, opt-in)Test-restores the newest snapshot

Constants

Define these in wp-config.php before the plugin loads to override defaults:

ConstantDefaultPurpose
MH_BACKUP_API_URLhttps://api.musthaveplugins.com/backup/Licensing / OAuth-relay API base — point it at a staging API for development

Defined by the plugin (read-only, useful in integrations):

ConstantValue
MH_BACKUP_CAPmhbackup_admin — the capability gating the admin UI and AJAX; granted to administrators on activation. Grant it to other roles to give them access.
MH_BACKUP_STORAGE_DIRAbsolute path of the randomized storage root (wp-content/mhbackup-<hex>/)
MH_BACKUP_PLUGIN_DIR / MH_BACKUP_PLUGIN_URL / MH_BACKUP_PLUGIN_FILEStandard plugin path/URL helpers

Template Overrides

Every admin template is rendered from the plugin's templates/ directory and can be overridden by the active theme: copy the file to {your-theme}/must-have-backup/{template}.tpl.php and it takes precedence. The rendered output also passes through the mhbackup/get_template filter.

Custom Storage Drivers

Third-party destinations plug into the same registry as the built-in drivers. Extend the abstract driver, then register on the boot action:

class My_Backup_Driver extends MH_Backup_Storage_Driver {
    const SLUG  = 'mydriver';
    const LABEL = 'My Storage';

    // implement: describe(), put(), get(), delete(),
    //            ls(), head(), free_space(), test()
}

add_action('mhbackup/register_storage_drivers', function () {
    MH_Backup_Storage::register_driver(My_Backup_Driver::SLUG, 'My_Backup_Driver');
});

Driver contract invariants:

  • put() must be idempotent — if the remote already holds a byte-identical object at the path, return without re-uploading (chunks are content-addressed, so a size match is sufficient evidence)
  • delete() must tolerate missing objects — the retention sweep calls it speculatively
  • test() must round-trip a canary write, not just connect — credentials might lack write permission
  • free_space() may return null when the backend has no quota API; retention treats it as unlimited

Changelog

0.2.3 – 2026.07.23
[BETA] First public beta release

This website uses cookies to enhance your browsing experience and ensure the site functions properly. By continuing to use this site, you acknowledge and accept our use of cookies.

Accept All Accept Required Only