Remotely Back Up Your WordPress Database and Media With WP Migrate CLI

#
By Mike Davey, Senior Editor

Keeping regular backups of your WordPress installs is one of the most important things you should do after you launch a site. If you search for the term “backup” in the WordPress plugin repository, you’ll find over 700 that claim to be up to the task. In fact, we reviewed some of the best backup plugins earlier this year.

In this article, we’re using a bit of a different approach to set up a highly reliable remote backup solution using a plugin that isn’t on that list: WP Migrate. We’ll also use the command line.

Why?

Some readers may be asking themselves, “Why would I use WP Migrate for this when I’m already on the command line? Couldn’t I just use rsync and grab the remote database over SSH?” Sure you could! There are a lot of ways that you could accomplish remote backups and some may be better options for you, but there are a few reasons to do it this way:

  • Low config: Once you’ve got this set up, the amount of configuration you need to do is pretty small. You don’t need to create SSH keys or store remote login and mysql credentials in cleartext on your backup server. All you have to do is copy your connection information into a new profile and add that profile to your bash script.

  • Portability: If the site will run WordPress, you can back it up, even on restrictive hosts that don’t give you SSH access or allow remote mysql login.

  • The “Everest” Reason: Why would you want to climb Everest? Because it’s there. As a backup solution, this isn’t right for every situation. However, it’s a good way to see what can be done with a bit of config and some CLI know-how.

Overview

The setup here is pretty simple, but there are a few things you’ll need:

  • A fresh install of WordPress you only use to back up other sites.
  • An existing WordPress site that you want to back up.
  • WP Migrate installed on both sites.
  • Your favorite code editor.
  • mysql-client installed on your machine.

That’s about it in terms of initial setup. You can even use a local development environment, so long as you have access to your system’s CRON.

From that starting point we’re going to be writing a bash script that will:

  1. Create a dump of the current database.
  2. Run a profile migration via the WP Migrate CLI that pulls the database and possibly media files from a remote site.
  3. Package up the pulled database and media files and move to a backups folder.
  4. Restore the original database.
  5. Repeat for each profile.

The Build

Set Up Remote Site files

The first step is to get the connection information for the site you’d like to start backing up. The connection info is needed to create a new profile in WP Migrate on your backup site. Feel free to skip to the section on configuring the backup script if you’re already familiar with this process.

You can find the connection information you need under “Settings” in WP Migrate on the site you want to back up. While you’re there, remember to click the Pull toggle on or this won’t work.

A screenshot of WP Migrate showing connection info. The connection information has been blurred out for security reasons.

The next step is to create a profile on the site where we’ll store the backup. You can create new profiles under the “Profiles” tab in WP Migrate. Click on Get Started or New Migration to begin creating the profile.

The action needed here is Pull. Normally this would replace the site’s database and files with the backup, but we’ll also uncheck the prefilled find and replace rows. This means we’ll just back up the remote site as-is, not actually migrate the data to the new server. Before we get into that, we first paste in the connection info from the site we want to back up.

Next, open Advanced Options and uncheck Replace GUIDs. There are situations where you’ll want to replace GUIDs, but this isn’t one of those times.

Open Post Types as well, select Migrate only selected post types below, and uncheck revision.

Creating a profile in WP Migrate, with post revisions unchecked, and pages and posts checked.

The next step is to uncheck the prefilled find and replace rows. This pops up a message warning us not to do this unless we’re sure we know what we’re doing. We’re going to ignore this and steam ahead at full speed.

WP Migrate 's Find and Replace Settings. We need to uncheck both boxes, as we don't want to replace a site with the backup.

Check the Media Uploads box and select Pull New and Updated Media Uploads. We’re going to leave “Themes” and “Plugins” unchecked in this example, but you can include these if you have custom themes or plugins you want to back up. Note that if you’re using version control, you may not want to include custom themes and plugins as part of your backup. It just depends on your preference.

Selecting media uploads in WP Migrate.

Next, click on Save file and give it a name. Don’t click “Pull”, as this will automatically start the process. We don’t want to do that yet, we’re just getting the profile information to use in our script.

Repeat these steps for as many sites as you want to back up, then click on the Saved Profiles link at the top of the page so you can see the list of profiles you’ve created.

Configure the Backup Script

We can use the script below as a starting point. The original script was created by J.R. Gould. The version below was forked by Carrie Dils of WP Engine and updates legacy syntax found in the original gist.


#!/bin/bash
#
# Declare SITES as an associative array
declare -A SITES
# Format: SITES[WPMDB profile number]=backup_filename_base
SITES[1]=remote_backup_com
#SITES[2]=another_site_to_backup
#SITES[3]=my_other_blog

## These variables will be specific to the WordPress
## install that you'll be using to backup your remote sites
# The absolute path to the root of your site
SITE_PATH=/var/www/html/
# The folder to store backup .tar.gz files in
BACKUP_DIR=${SITE_PATH}/_backups

# MySQL connection information, should be the same as what's in your wp_config.
MYSQL_USER=
MYSQL_PASS=
MYSQL_DB=

# These should be the same on most servers
PATH_TO_WP_CMD=/usr/local/bin/wp
UPLOADS_FOLDER_CHMOD=775
APACHE_USER=www-data

###################################### STOP EDITING #####################################

# Suppress warnings from WP CLI about not running in a terminal
export TERM=xterm-color

for i in "${!SITES[@]}"
do

  PROFILE_INDEX=$i
  CURRENT_SITE=${SITES[$i]}
  DATE=$(date +"%Y%m%d%H%M")

  # Print the date/time on a newline
  printf "\n----------------- %s | $CURRENT_SITE | Migrate DB Profile: $PROFILE_INDEX -------------------\n\n" "$(date)"

  # Create backup of pristine db
  echo "Backing up db..."
  mysqldump -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${MYSQL_DB}" > wpmdb_backup_dump.sql

  # The migration command
  echo "Performing migration via WPMDBPro CLI"
  echo "-------------------------------------------"
  ${PATH_TO_WP_CMD} migratedb profile "${PROFILE_INDEX}" --path="${SITE_PATH}" --allow-root
  echo "-------------------------------------------"

  BACKUP_NAME=$CURRENT_SITE-$DATE
  WORKING_DIR=$BACKUP_DIR/$BACKUP_NAME

  mkdir -p "${WORKING_DIR}"

  echo "Getting tables to save for backup of ${CURRENT_SITE}"
  mysql -u"${MYSQL_USER}" -p"${MYSQL_PASS}" -N information_schema -e "SELECT table_name FROM tables WHERE table_schema = '${MYSQL_DB}';" > tables-to-export.txt

  # Dump tables that aren't for the backup install
  echo "Backing up tables: $(tr '\n' ' ' < tables-to-export.txt)"

  mysqldump -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${MYSQL_DB}" "$(cat tables-to-export.txt)" > "${WORKING_DIR}"/database.sql

  echo "Moving Uploads Folder"
  rm -rf "$SITE_PATH"/wp-content/uploads/wp-migrate-db/
  mv "$SITE_PATH"/wp-content/uploads/ "$WORKING_DIR"/

  echo "TARing Backup"
  tar -zcf "${BACKUP_DIR}"/"${BACKUP_NAME}".tar.gz -C "${WORKING_DIR}"/ .
  rm -r "${WORKING_DIR}"

  # Drop all tables in db

  echo "Cleaning up..."
  mysqldump -u"${MYSQL_USER}" -p"${MYSQL_PASS}" --add-drop-table --no-data "${MYSQL_DB}" | grep ^DROP | mysql -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${MYSQL_DB}"

  # Restore original DB
  mysql -u"${MYSQL_USER}" -p"${MYSQL_PASS}" "${MYSQL_DB}" < wpmdb_backup_dump.sql
  rm wpmdb_backup_dump.sql
  rm tables-to-export.txt

  #Restore uploads folder
  mkdir -m ${UPLOADS_FOLDER_CHMOD} "${SITE_PATH}"/wp-content/uploads/
  chown ${APACHE_USER} "${SITE_PATH}"/wp-content/uploads/

done

Save it to a safe place on your backup server as wpmdbpro-cli-remote-backup-script.sh and set the following variables based on your site's settings:

SITE_PATH      #The absolute path to your WordPress install
BACKUP_DIR     #The absolute path to a folder to keep the backups in
MYSQL_USER     #MySQL username from wp-config.php
MYSQL_PASS     #MySQL password from wp-config.php
MYSQL_DB       #MySQL database name from wp-config.php

Next you'll want to go to the list of profiles that you've already set up in WP Migrate and add them to the SITES array of profiles to run through. If the list of profiles in your WP admin looks like this:

1 - Backup foo.com
2 - Backup dev.anothersite.com
3 - Backup danger.zone

Then you'd set the SITES array up like this:

SITES[1]=foo_com
SITES[2]=dev_anothersite_com
SITES[3]=danger_zone

The numbers in the brackets should be the profile ID. The string on the right will be the filename base for your backup files and therefore shouldn't contain any spaces or special characters, with the possible exception of underscores or hyphens.

You may also need to alter the variables PATH_TO_WP_CMD, UPLOADS_FOLDER, and APACHE_USER if these settings won't work for your server.

Go ahead and save the changes to the shell script and make sure that it's executable by running:

$ sudo chmod +x wpmdbpro-cli-remote-backup-script.sh

Now you can test the script out to make sure that everything's working well. Here, I'm assuming that you've saved the script to your home folder:

$ ~/wpmdbpro-cli-remote-backup-script.sh

You should see output that looks something like this and repeats for each profile you've set up:

----------------- Tue May 19 15:35:05 PDT 2015 | foo_com | Migrate DB file: 1 -------------------

Backing up db...
Performing migration via WPMDB CLI
-------------------------------------------
Verifying connection...
Initiating migration...
Migrating tables      100%[========================================] 0:01 / 0:01
Initiating media migration...
Removing all local files before download of remote media...
Determining media to migrate - 39 of 39 attachments (100%)
Downloading files  100%[===========================================] 0:24 / 1:33
Cleaning up...
Success: Migration successful.
-------------------------------------------
Getting tables to save for backup of foo_com
Backing up tables: wp_commentmeta wp_comments wp_links wp_options wp_postmeta wp_posts wp_term_relationships wp_term_taxonomy wp_terms wp_usermeta wp_users 
Moving Uploads Folder
TARing Backup
Cleaning up...

If you have errors in your output or something looks like it didn't work, make sure to double check that you've correctly filled out all of the variables and that you've got sufficient permissions for all of the folders where you’re manipulating files. If everything looks good, check out the folder you specified to store your backups. You should find a .tar.gz file for each of the profiles that you set up with a timestamped filename like foo_com-201505191535.tar.gz.

3. Set Up a CRON Job

Now that the script is set up, let's set up a cron job. We’re going to set it to run this script once a week so we're creating full backups of all our remote sites every week.

First, open your crontab for editing.

$ crontab -e

I'll set up a job that will run this script every Sunday morning at 2 a.m. and append the output to a logfile.

0 2 * * 0 /home/my_user/wpmdbpro-cli-remote-backup-script.sh >> /home/my_user/backups.log 2>&1

Now you can save the crontab file. Remember to check the log next Monday to make sure everything went according to plan.

That's It!

This should be enough to get you going with backing up your remote sites, but there's definitely room for improvement. For example, you could modify the script to send you an email if something went wrong, you could grab the profiles directly from the WordPress database so that you don't have to hardcode them into the bash script, and you could delete old backups to make room for new ones.

What’s your go-to backup solution for remote sites? Do you use a script, depend on plugins, or use something else entirely? Let us know in the comments.

About the Author

Mike Davey Senior Editor

Mike is an editor and writer based in Hamilton, Ontario, with an extensive background in business-to-business communications and marketing. His hobbies include reading, writing, and wrangling his four children.