How I Backup My Wp Site

So I recently saw this little yellow bar appear in my WordPress wp-admin dashboard that told me to upgrade my site to the new version of WordPress, which at the time of this writing, is WordPress 3.8.1. Usually before you make any new software upgrades it is said that backing up your data is a good idea, so this little yellow bar got me thinking, “man, I should probably be backing my site up huh?” This question sent me down an interesting path of unix discovery and exploration that I hope is helpful to anyone else looking for info on backing their site up.

I started looking at the various options for backing up a WordPress site and ended up inevitably on the WordPress Codex which gave me a few options. I could back up via a plugin, rely on my hosting provider to backup for me, or come up with my own solution for backing up my data.

For some reason I like to do things the hard way, so early on, I decided that it would be best to back up my site harnessing the power of unix and not opting for any WordPress plugins or third party extensions. I also feel like I should note that most hosting providers will back up your site for you, but I think that relying solely on this is a mistake. Most users usually don’t have much control over how their hosting providers are backing up their content, and it could be difficult/time consuming to get that data back if anything happens. Plus, it doesn’t hurt to back up your site with more than just one method.

In the beginning I wasn’t really sure where to start, so I took to the net and found an excellent blog post by the guys over at theme.fm which can be found here. I pretty much followed their lead adding in my personal tweaks in here and there and I am really pleased with the solution that I came up with.

My first step was making sure that I had ssh access to my web server. I use HostGator so here is how I did it with them. I headed over to GatorBill at gbclient.hostgator.com and under hosting packages clicked View Hosting Packages. Then I just found my package and clicked Enable Shell Access. Simple enough right? Next I just opened up the terminal application and sshed into my web server to make sure everything was working okay. Here’s how if you are following along:

% ssh -p 2222 username@yoursite.com

You’ll notice the -p 2222 in this ssh command, this is necessary with HostGator because they use port 2222 for their ssh connections. Without specifying the port ssh will default to port 22. You will also need to use the same user name and password that you login to cPanel with, again this is all for HostGator specific users.

Once I was up and running on my server I started working on creating a scripts and a backups folder that would hold all of the necessary data. The next part was writing the bash script that would back all of my data up. What I came up with was a modified version of theme.fm’s script. I added in an automatic clean up snippet to my script which removes old backups conserving my server space.

#!/bin/bash

#-------------------------------------------
# WP backup script: Maintained by Dan Fowler
# Website: dsfcode.com
# Version 1.0.0
#-------------------------------------------

# This script creates a compressed backup archive of the given directory and the given MySQL table

# Set your WP site and backup vars
DATE=$(date +"%Y-%m-%d-%H%M"); STORE=10;
SITE="site.com"; FILE="$SITE.$DATE.tar.gz"; TRANS='s;home/user_name;;';
BACKUP_DIR="/home/user_name/backups"; WP_DIR="/home/user_name/public_html/";

# MySQL database info
DB_USER="user_name"; DB_PASS="database_password";
DB_NAME="wordpress_db_name"; DB_FILE="site.com.$DATE.sql";

# MySQL dump and backup creation
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/$DB_FILE;
tar -czf $BACKUP_DIR/$FILE --transform $TRANS $WP_DIR $BACKUP_DIR/$DB_FILE &> /dev/null;
rm $BACKUP_DIR/$DB_FILE;

# If backups exist only store $STORE backups at a time removing the oldest backup
if ls $BACKUP_DIR/$SITE.*.tar.gz &> /dev/null
then
     BNUM=`ls $BACKUP_DIR/$SITE.*.tar.gz | wc -l`;
     BRM=`ls $BACKUP_DIR/*.tar.gz | sort | head -1`;
     [[ $BNUM > $STORE ]] && rm $BRM;
fi

As you can see I make use of a lot of bash variables here, if you want to use this script to back up your site you just need to update the variables with your site’s information. Once you have saved a script like this to your server you will need to make it executable:

% chmod +x scripts/site_backup.sh

Now all you need to do is tell cron how often it needs to run the script, if you don’t know what cron is or are looking for more information on how to work with crontabs you can look here. You can easily add to your crontabs by running the following command:

% crontab -e

This command will open your crontabs with the editor specified by your EDITOR or VISUAL bash environment variables.

I have cron run my script every Monday at 2am which looks something like this:

0 2 * * 1 /home/username/scripts/site_backup.sh

Now that cron has been set up to run your script automatically. It would probably be nice to automatically sync these backups to your local machine. We can do this pretty easily with the rsync command. I use the following script on my local machine to sync files automatically:

#!/bin/bash

#------------------------------------------------
# WP backup sync script: Maintained by Dan Fowler
# Website: dsfcode.com
# Version 1.0.0
#------------------------------------------------

USER="user_name"; DOMAIN="site.com";
L_DIR="/Users/user_name/Path/to/backup/";
S_DIR="/home/user_name/backups/site.com.*.tar.gz";

# Get the most recent backup
BAKUP=`ssh -p 2222 $USER@$DOMAIN "ls $S_DIR" | sort | tail -1`;

# Sync the backup locally
rsync -ae 'ssh -p 2222' $USER@$DOMAIN:$BAKUP $L_DIR;

Once that script is in place I used mac OS’s launchd daemon manager to automatically run it. Since OS X is a unix based operating system I could have used cron to manage my automated tasks, but most people will advise against it. Launchd was developed to replace cron and it is also much more flexible than cron is. One very important reason why I chose launchd over cron was if your computer is asleep when the cron job is scheduled, the job will not run until your computer is awake for the next scheduled job. Where as launchd will run this job once the computer wakes up again. This isn’t a problem on the web sever because the web server is hopefully always on.

Launchd is however a little harder to work with than cron. You can get more information on launchd over at Mac’s Developer Library. To get my tasks up and running I just put the following into a ~/Library/LaunchAgents/com.site.backup.plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.site.backup</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/user_name/Path/to/scripts/backup_sync.sh</string>
    </array>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>8</integer>
        <key>Minute</key>
        <integer>0</integer>
        <key>Weekday</key>
        <integer>1</integer>
    </dict>
</dict>
</plist>

Once you have your plist file in place you can run the following to load the daemon:

% launchctl load ~/Library/LaunchAgents/com.site.backup.plist

This will get your job up and running and should automatically start the next time your turn on your computer. To run this automatically I also needed to set up ssh so that my local machine does not need to input a password every time the rsyc script runs. Here is a good tutorial for getting the ssh credentials set up if you have never done it before.

And there you have it, my automated backups are in place and I can sleep a little more sound knowing my site’s data is safe. Over all I’m really happy with the way this method of backing up my site turned out. I haven’t done extensive testing or used this method for long, so don’t hesitate to let me know if you have any ideas or suggestions for improvements.

Here is a link to the example code I used above.