Docs / Surfaces / WordPress

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

  1. Download safegrd-backup.zip from the plugin's releases.
  2. In wp-admin, open Plugins, Add New, Upload Plugin, choose the zip, then Activate.
  3. 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.

# the same from WP-CLI, with a token from Tokens in the console
wp plugin install safegrd-backup.zip --activate
wp safegrd connect --token=env:SAFEGRD_TOKEN
wp safegrd backup

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

PartWhat is in the snapshot
DatabaseEvery table that starts with the site's $table_prefix, read in one consistent snapshot, in the form mysqldump writes
Fileswp-config.php, .htaccess and wp-content: uploads, themes and plugins
Left outWordPress 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:

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 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.

# find the snapshot
safegrd list
# database into an empty database, files into an empty directory
safegrd restore --snapshot snap-20261006-120000-a1b2c3 \
--target mysql://wp:...@db.example.com:3306/wordpress --target-dir ./restored

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:

# 1. WordPress core, at the version the restore printed
wp core download --version=6.8.3 --path=/var/www/html
# 2. the restored files over it
cp -a ./restored/. /var/www/html/
# 3. owned by the web server's user (www-data on Debian and Ubuntu)
chown -R www-data:www-data /var/www/html
# 4. wp-config.php pointed at the restored database
wp config set DB_NAME wordpress --path=/var/www/html
wp config set DB_USER wp --path=/var/www/html
wp config set DB_PASSWORD '...' --path=/var/www/html
wp config set DB_HOST db.example.com:3306 --path=/var/www/html
# 5. only on another domain: the old URL, serialized values included
wp search-replace 'https://old.example' 'https://new.example' --all-tables --skip-columns=guid --path=/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);:

17 3 * * * cd /var/www/html && wp safegrd backup --quiet

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');.