Having a reliable WordPress system backup is important, having it outside the application is even more so. Combine that with full control and we have something pretty awesome going on.

The file structure I like to have is:

www/
    backups/      <-- Backup location
    html/         <-- WordPress location 
    scripts/ 
        backup.sh <-- This file

And we will create the backup.sh script. This assumes that you have the WP CLI and tar utilities installed.

#!/bin/bash

#
# This script backs up the entire WordPress Database and filesystem, using
# the file naming format will determine how often the backup will take place
#
# Author: Eliott Robson (https://eliottrobson.me)
#

WP_ROOT=/var/www/html
BAK_ROOT=/var/www/backups

BAK_FOLDER=${BAK_ROOT}/$(date +%Y/%m)
BAK_DB=${BAK_FOLDER}/$(date +%Y%m%d)_database.sql
BAK_FILES=${BAK_FOLDER}/$(date +%Y%m%d)_files.tar.gz

# Check interactive shell
ECHO_CLEAR=""
ECHO_RED=""
ECHO_YELLOW=""
ECHO_GREEN=""
ECHO_BLUE=""

if [ -t 1 ]; then
    ECHO_CLEAR="\e[0m"
    ECHO_RED="\e[1;31m"
    ECHO_YELLOW="\e[1;33m"
    ECHO_GREEN="\e[1;32m"
    ECHO_BLUE="\e[1;36m"
fi

# Indicate start of backup, useful for cron logs
echo -e "${ECHO_YELLOW}Beginning backup sequence.${ECHO_CLEAR}"
echo -e "${ECHO_YELLOW}$(date "+%Y-%m-%d %H:%M:%S")${ECHO_CLEAR}"

echo ""

mkdir -p ${BAK_FOLDER}

cd ${WP_ROOT}

# Backup database
echo -e "${ECHO_YELLOW}Attempting database backup.${ECHO_CLEAR}"
if [ ! -f "$BAK_DB" ]
then
    wp db export ${BAK_DB} --add-drop-table
else
    echo -e "${ECHO_BLUE}Skipped:${ECHO_CLEAR} File already exists."
fi

echo ""

# Backup filesystem
echo -e "${ECHO_YELLOW}Attempting filesystem backup.${ECHO_CLEAR}"
if [ ! -f "$BAK_FILES" ]
then
    tar --create --verbose --listed-incremental ${BAK_FOLDER}/bak.snar --gzip --file ${BAK_FILES} .
    echo -e "${ECHO_GREEN}Success:${ECHO_CLEAR} Exported to '${BAK_FILES}'."
else
    echo -e "${ECHO_BLUE}Skipped:${ECHO_CLEAR} File already exists."
fi

echo ""

echo "------------------------------"

The first thing we do is setup some variables, customise these to accommodate your setup. Then we check for an interactive shell, this allows us to put contextual colours on the output while keeping your logs (in a cron context for example) nice and clean. Then we create the file structure if we need to before entering the WordPress root. We export the database and then a file system snapshot.

And that’s it, the script is simple. Every day the database is backed up and each month the filesystem (with changes also backed up daily to match the database). This allows us to customise the filename to determine “how often” each backup will be run.

Because we are tracking the changes as well as the changes to restore it is best to copy all of the backup files to a temporary location from the start of the month up until the day you wish to restore from. Then running this command will unarchive them in order based on their incremental archive order.

for bak in *_files.tar.gz;
do
    tar --verbose --extract --incremental --gzip --file $bak;
done

This goes through each backup and unarchives them in order. The first one building the root filesystem as it was at the start of the month and each one thereafter the changes since that date. It’s a pretty cool way to have detailed backups without the space requirement of so many copies.

But what about taking an offline copy? Simple. We can use rsync to take copies of the backups.

Run this on your local machine. Replace host with your host.

rsync --update -raz host:/var/www/backups/ ~/Documents/Website\ Backup/

Now go celebrate, you just got more awesome.