WordPress sites
A WordPress site is backed up by the SafeGrd Backup plugin, from wp-admin. Nothing is installed on the server beside it, so it works on shared and managed hosting. Each backup is a complete snapshot of the database and the site's files, encrypted on your server before it leaves, and stored in SafeGrd's hosted storage under a lock nobody can delete early. A backup uploads only what changed since the last one. The free plan covers one site; Pricing lists what each plan includes.
Install and connect
- Download
safegrd-backup.zipfrom the plugin's releases. - In wp-admin, open Plugins, Add New, Upload Plugin, choose the zip, then Activate.
- Open Tools, SafeGrd, choose who keeps the key, and press Connect. Sign in or create an account in the tab that opens, and check the code matches the one in wp-admin.
The first backup starts right after connecting. After that it runs once a day through WP-Cron, which runs on visits to the site. Back up now on the same page runs one at once.
Who keeps the key
SafeGrd-managed key (the default): SafeGrd keeps your key sealed and releases it only to your enrolled hosts, so you can restore even after losing this site. It is also what lets SafeGrd test-restore your backups for you.
Customer-managed key: only you can decrypt these backups. The plugin shows the key once, when you connect. Keep a copy somewhere safe, such as a password manager. The site keeps only the public half, so it can write backups and cannot read them.
What is backed up
| Part | What is in the snapshot |
|---|---|
| Database | Every table that starts with the site's $table_prefix, read in one consistent snapshot, in the form mysqldump writes |
| Files | wp-config.php, .htaccess and wp-content: uploads, themes and plugins |
| Left out | WordPress core (reinstall the version the backup names), caches, upgrade, debug.log, and other backup plugins' archives. Symlinks and unreadable files are listed after each run |
wp-config.php holds the database password and the site's salts. It is in the
snapshot because a restore needs it, and it is encrypted with everything else.
Multisite networks are refused, and so is a site whose media a plugin keeps in object storage (WP Offload Media, WP-Stateless, Media Cloud): the files are not on the server for the plugin to read.
What the test restore checks
SafeGrd test-restores the newest backup on its own machines, in memory, on every plan. The drill fails if any of these fails:
- every pack the snapshot names is in storage and opens with the key, and the snapshot's content root matches the one recorded when it was taken;
- the database dump is complete, and every table has the rows the backup counted;
- every file matches the size and SHA-256 listed beside it, and no file is unlisted;
- every attachment the database names (
_wp_attached_file) is a file in the snapshot. The list is read out of the database half, so a missing upload fails the drill by name.
Restore
From wp-admin, on any host: install WordPress, install the plugin, and connect it to the same account. Tools, SafeGrd, Restore lists the backups of every WordPress site in the account. Press Restore on one and confirm. The same page restores a site over itself.
- The site keeps running on its own database and files until everything is loaded and checked: every table's rows against the backup, every file against its SHA-256. Then one step swaps the restored ones in.
- The tables and files it replaced are kept aside until you press Delete the copy.
- The site keeps its own
wp-config.phpand address. When the address differs, the old one is replaced in the database, serialized values included. A different table prefix is handled too. - Afterwards the site's users are the backup's: sign in with an administrator account of the restored site. The site stays connected to SafeGrd.
- A restore runs in slices like a backup, so a host that stops long requests does not stop
it. From the command line:
wp safegrd snapshots, thenwp safegrd restore <snapshot>.
The plugin restores backups taken with a SafeGrd-managed key. For one taken with a
customer-managed key, or onto a server without WordPress, use the
safegrd CLI on any machine enrolled in the same organization. It
needs an empty MySQL or MariaDB database, an empty directory, and the mysql
client.
The restore prints the snapshot's WordPress version and site URL. Put the site back together
in the directory the web server serves, here /var/www/html:
Skip step 5 when the site comes back at the same address. Run it with
--dry-run first to see how many values it changes. A plain text replace in the
SQL breaks serialized options, such as widget settings, because their recorded lengths no
longer match.
The restored site is not connected to SafeGrd: the plugin's own settings are left out of every backup. Connect it again from Tools, SafeGrd.
What each backup uploads
Files are cut into 4 MiB chunks, encrypted and gathered into packs. A backup uploads only the chunks this month's repository does not hold yet, so an unchanged image, theme or table costs nothing; each table is a part of the dump of its own. The first backup of each month uploads everything once, so every month stands on its own. Every snapshot is complete: a restore never needs an earlier one.
Large sites on strict hosts
A backup runs in slices of a few seconds, each its own request: a third of the host's
max_execution_time, at most 25 seconds. Each slice uploads what it read, saves
where it got to, and starts the next. A host that stops a request stops one slice, and the
next resumes from the last saved point.
The database dump runs in one slice, so that it is one consistent snapshot, and so does the
largest single file. When a host stops either, the run is recorded as failed with the reason,
in wp-admin and the console. Then run backups from the server's cron, where PHP has no time
limit, and turn off WP-Cron's own trigger with define('DISABLE_WP_CRON', true);:
The plugin needs PHP 7.4 or newer with the sodium, zlib and mysqli extensions, which PHP
includes by default. A self-hosted SafeGrd server is set with
define('SAFEGRD_SERVER_URL', 'https://...');, and a private CA with
define('SAFEGRD_CA_FILE', '/path/to/ca.pem');.