Backing up is one of the most important things to do when running a website and this is not related to WordPress websites only. There are so many things that can go wrong, whether your hosting provider is down, or you forgot to pay the hosting bill, or maybe you accidentally locked yourself out of your VPS and your hosting provider’s support team wouldn’t respond.
There are many different ways you can backup your WordPress blog, and your hosting provider might actually be doing that for you, but wouldn’t it be safer if you didn’t have to rely all your data on a third-party? Especially if you’ve been blogging for years, you really cannot afford to loose all that data that you’ve worked so hard for.
Of course you can use different WordPress plugins for automated backups or manually back up using database and file server programs. Many of the ways are described in the WordPress Backups entry in the codex. But today it’s all about geeky backups, so we’re going to write a shell script. Make sure that you’re comfortable with SSH and some Unix/Linux in general. And also make sure that your hosting provider actually gives you SSH access and access to the task manager (known as crontab). This post is more of a tutorial rather than just a snippet, so if you’re in here for the short story, proceed to the download link.
Overview
Our shell script will be quite simple — it will create a full backup archive of the website’s directory, including the WordPress core, themes and plugins, and most importantly, the uploaded content. Next we’ll create a full database dump and add it to the archive. The archive will be gzipped to make sure we save some space, and dated, so that we know when the archives were created and which are safe to remove.
We will then setup a cron entry to run the shell script periodically and get rid of old backups so that we don’t run out of hard disk space. The script itself can also be ran manually, like immediately after you publish a new and important blog post, just to be on the safe side.
Figuring Out the Structure & Creating the Backup Script
If you’re still reading, I’m quite sure that you’re familiar with shell scripts, but if you’re not just keep in mind that they’re simple executable text files that can run one or a set of commands when executed. Just like batch scripts in Windows environments.
Before diving into writing the actual script, I’ll make a few assumptions so that you can follow along and adapt the code I’m writing to your own file structure and database credentials.
- I’ve got SSH access on example.org — my username is
usernameand the password ispassword - My home directory is at
/home/username/and my website’s root directory is/home/username/www/example.org/, so that’s where my WordPress files are - The MySQL database is running on
localhost. The username ismysqluser, the password ismysqlpassand the database name for example.org isexample_org - The shell script will be stored in
/home/username/scripts/and the backups will go to/home/username/backups/so make sure those directories exist before continuing
Make sure that you’ve logged on to your server via SSH and let’s start off by browsing to our scripts directory and creating a new executable file there with these three simple commands:
$ cd /home/username/scripts/ $ touch backup.script $ chmod +x backup.script
Now use your favorite text editor to edit the contents of the backup.script file. My preference is vi but you’re free to use vim, emacs, nano or any other plain/text file editor. We’ll go through the script step by step, but again, if you’re here for the short story, proceed to the download link.
Defining the Variables and Transforms
We’ll be using quite a lot of variables throughout the code so that it’ll be easier for you to manage the script afterwards or replicate it for other websites. Make sure you’re familiar with variables in bash and don’t forget the interpreter header at the very beginning of your script file.
#!/bin/bash NOW=$(date +"%Y-%m-%d-%H%M") FILE="example.org.$NOW.tar" BACKUP_DIR="/home/username/backups" WWW_DIR="/home/username/www/example.org/"
So we’ve defined the current date and time and decided on how to name our output backup file. Let’s proceed on to the database variables that we’ll use when making the actual MySQL tables dump and some transforms which I’ll explain in a second. Append the following code to your script file:
DB_USER="mysqluser" DB_PASS="mysqlpass" DB_NAME="example_org" DB_FILE="example.org.$NOW.sql" WWW_TRANSFORM='s,^home/username/www/example.org,www,' DB_TRANSFORM='s,^home/username/backups,database,'
So that first part is quite simple, now the transforms I have defined are not mandatory, but will really help in keeping your archives structure clean. We’ll be using these as the transform expressions when creating the archive, and they will make sure that the WordPress directory files are stored in a directory called www and the database dump goes alongside in a folder called database. This is for convenience, especially when it comes to browsing your backup archives — you won’t get those nasty 3-4 level home/username/www/example.org/ prefixes. When writing those look carefully for the slashes — one single mistake can result in an invalid transform expression. More about modifying file and member names with tar.
Creating the Files Archive and the MySQL Backup
This part is quite simple, now that we have defined all the variables above. We’ll use the variables in expressions with the tar and mysqldump commands, you should get familiar with these as they’re very powerful in your day-to-day server maintenance life. Append the following code to the backup script:
tar -cvf $BACKUP_DIR/$FILE --transform $WWW_TRANSFORM $WWW_DIR mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/$DB_FILE
The v flag for the tar command makes it run in verbose mode, useful for debugging when things go wrong. You might also like the --show-transformed-names flag for the tar command if your transform expression is not working for a reason. The second line creates an SQL dump of your database into the backups directory.
So in theory we’ll end up with a new archive in the backups directory and a database dump right next to it, but as I mentioned earlier, we’re going to take this further.
Merging, Cleaning Up & Compressing
The following commands will append the database file into the archive so that we won’t have to deal with two separate files, and that’s where the database transform variable is used, it’ll put it into the database directory within the archive, making the structure very clean. We then get rid of the database file since it’s in the archive already. Finally, we compress the whole archive so a .gz file is our final result. Append the following piece to your script:
tar --append --file=$BACKUP_DIR/$FILE --transform $DB_TRANSFORM $BACKUP_DIR/$DB_FILE rm $BACKUP_DIR/$DB_FILE gzip -9 $BACKUP_DIR/$FILE
That pretty much completes our backup script, and you can test it out by running it directly from the command line and then look at the backups directory. You can also try unarchiving the result file with tar -xvf to get a feeling of how the data is stored inside the archive and if you’re unhappy with that you can play around with the transforms to achieve a structure that you will be comfortable with.
Now that you’re comfortable with the script itself and the backups that it produces, it’s time to actually set it up for automatic execution, which brings us to the next part of this post.
Task Scheduling: WordPress Backups with Crontab
If you’re unfamiliar with Cron, you should check out this Wikipedia article — it pretty much explains what Cron is and how scheduled jobs are executed. For detailed information look for the Cron manual for your Linux distribution. I’m using Ubuntu Server, which usually sticks all the Cron related stuff in the /etc/crontab and the different parts to /etc/cron.* for daily, weekly, hourly, etc.
Before creating the cron entry make sure you know what you’re doing and if you don’t, refer to the Intro to Cron guide. Unless you need granular control over when your backups will be made, you might as well go with an entry to the weekly, daily or hourly cron which are set up by default in Ubuntu (and probably your OS too). So let’s create a new daily executable file:
$ cd /etc/cron.daily/ $ sudo touch example_org_backup $ sudo chmod +x example_org_backup
And then add the following contents to the script:
#!/bin/bash /home/username/scripts/backup.script
This is pretty straightforward, the backup script that we created earlier is launched every day (the time depends on the time and timezone settings on your operating system). Note that with the default setup will run the script as root and therefore create your backup files which will be owned by root and readable by everyone. This could be solved by either configuring cron to launch the script as a different user (perhaps username) or adding a chmod/chown command to the backup script itself to change the privileges or the owner/group of the resulted backup file.
So now we’re left with a backup script that creates archives of our WordPress application files, content files and a full database backup. The script is launched once daily (hourly, weekly or however you have configured it). Where should we take it from here? Well it’s pretty obvious that if your server fails, your backup files will not be accessible too, so you have to distribute the backup files to other destinations, and good ideas for storage servers Amazon S3, a different server via FTP, Dropbox and locally.
Amazon S3 and Dropbox require a special protocol but there are tools to work with that. FTP or SFTP is a good choice and might be bundled in the backup script itself, so that the backup is immediately sent to a remote server rather than keeping it on the application server. But in this tutorial we’ll use rsync to download your WordPress backups to your local machine.
Downloading Your Backup Files
If you’re running a Mac or a Linux environment locally then your system has all the UNIX tools to help you grab your files. If you’re running Windows, you should Google for “rsync windows” or some other tool that will let you grab your files over the SSH protocol.

Why rsync is the right tool? Well there’s no right or wrong when it comes to utility programs, there are many others that help you achieve the same (or almost the same) results. I picked rsync because it’s simple enough to use from the command line and in a task scheduler, and can take off the load and bandwidth by only grabbing the new files without having to re-download the old backups every time it runs.
To try rsync out create a new directory where you’d like to store your backup files and pump in the following:
$ mkdir my_backups && cd my_backups $ rsync example.org:/home/username/backups/* .
Of course replace the example.org domain with your own, and the full path according to your setup. Rsync will receive an incremental file list from the server and will download the files that don’t exist in the directory where you’re pushing these files, in our case it’s the “dot”, meaning current directory, i.e. my_backups.
Try running this from time to time as more backups are created. For testing purposes you may set the backup cron to launch hourly or fine-tune it to once every few minutes just to see how the backup files are then downloaded to your local machine via rsync.
You may also configure rsync so that it doesn’t ask you for your SSH credentials when launching but to use a password file or an RSA key for SSH in general. There’s a great post on Oracle on how to do it, and this way you can use rsync in your task scheduler on your local machine, so that backups are downloaded automatically. I personally like to use an ssh-rsa key for password-less access to all servers I manage via an RSA key pair, but it’s up to you on which method to use.
Recap & Conclusion
In this tutorial we went through the process of coding a bash script which generates a tar archive of our application directory where the WordPress core sits, together with the themes, plugins and the contents directory. A database dump of the WordPress tables are then created and appended to the created archive. The archive structure is laid out nicely using tar transforms. Our script finally compresses the archive for storage and bandwidth optimization.
The script is then configured for an hourly, daily or weekly launch using Crontab on the remote server, and another short script is configured to grab those backup files from the remote server to your local machine using rsync for incremental file downloads.
The final backup script could be downloaded right here but don’t forget to modify the variables and path names according to your WordPress installation and server setup, and make sure that the file is executable.
You can go further by creating a clean-up script or bundling it into the same backup script to get rid of old backup files (ones that are older than two days, months or weeks) so that your remote server doesn’t eventually run out of storage space. Don’t worry — by doing so, rsync won’t delete your backup files on your local machine when synchronizing.
We at Theme.fm are now working on quite a different approach at backups specifically for WordPress along with some other great tools to work with and manager you WordPress installation, themes and plugin files — but that deserves a different blog post ;)
We’d love to hear how you manage backups of your WordPress installations, and web server backups in general, so leave a note in the comments section. If you’re new to this and have adapted the above script, we’ve got another exercise for you — take the script further by syncing it to Dropbox, Amazon S3 or a remote FTP server and share your bash scripts (using a pastebin service) via the comments!



Thanks for this article – very interesting. The difficulty is that the majority of WordPress hosts don’t have SSH access so the potential of this code is limited.
At Fublo, we backup our WordPress installations over FTP and then commit them to subversion. Our ftp2svn script is on github and if combined with WP-DB-backup to the file system, then it can provide a quick and simple way to backup and provide revision tracking.
We often just point the script at the wp-content folder to archive themes, plugins and uploads – it saves archiving the entire WP install.
Hi James, you’re so right about SSH access, and the ftp2svn solution you mentioned looks quite neat actually, good thinking!
Thanks for this, very nice!
One thing I had to amend for my install was the mysqldump call to backup the database: the command you provide
Complains and prints out the mysqldump usage – which is naughty as it is redirected to a file that is later zipped up. I changed it to:
Which happily backs up all databases on that server.
Hi Michael, thanks for pointing that out. The problem’s with the leading – before the database name in the
mysqldumpcall. I updated the post so it now looks like this:Thanks again for the heads up and for your comment!
Love the script! Thanks a lot. I don’t believe you updated the linked file at the bottom of the article with this update (aka removed the -$DB_NAME). Also, I modified to script below for instances where the SQL server is not local:
# MySQL database credentials
DB_USER=”mysqluser”
DB_PASS=”mysqlpass”
DB_NAME=”example_org”
DB_HOST=”mysql_example_org”
DB_FILE=”example.org.$NOW.sql”
mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/$DB_FILE
Interesting approach, but I think ot won’t work on any larger WordPess Site. Gzip will fail with memory error. That is the weak point of almost every WordPress backup.
Hi Predrag, thanks for your comment. I guess I’ll have to try and find out at some point how scalable this solution is, still I don’t understand how “larger” are we talking about? 100 MB? 1 GB? 1 or more TB? Let me know if you’re aware of a different approach that’s scalable enough, besides, I have a feeling that the larger setups run dedicated servers with RAID disks and a fallback server when failing :)
Most of the WordPress users are on shared hosting, so they have to fit there.
As I saw, some backup methods do not create whoel backup at once but backup items separately, including, separate tables from databases. That produces several zip archives but significantly shorter, so there are less chances for issues with memory.
There also issues with disk space. If site occupies for example 70% of hosting plan disk space, it may lead to larg prolem of creating backup in one pass. That also needs to make separate backups.
Good point, shared hosting is a nightmare I’m never willing to return to, but obviously you make sense. Nevertheless “larger WordPress websites” are too good for share hosting, least would be VPS, dedicated or even distributed, although this backup solution won’t fit the latter. I also agree about the disk space issue, which is why I run cron jobs to clean old backups, plus my hosting provider alerts me when I’m using too much disk space. At that point I go and delete my 10 GB nginx log files and I’m back to normal hehe ;)
Cheers and thanks for your comment!
Sounds like a server configuration issue to me; you likely need more RAM.
We regularly gzip WP installations with the gzip files being over 15gigs.
Michael (right?) sounds good, for a second there I thought I’ll face a horrible nightmare when running gzip in a few months, now I know I’m safe ’til 15G :) Thanks for your comment!
have you tested a recovery with that database dump?
Hey Matt, yes I did, but not an automated one. Manual lockdown, unarchive, move, mysql. I’m also working on a full-blown automated backup/recovery solution for WordPress, but more on that later ;)
you may want to use the -r switch. i’ve seen several systems corrupt characters when redirecting output directly to a file.
Okay Matt, thanks for the heads up, will keep that in mind :) Cheers!
Hi Konstantin,
Thanks for the script! Very useful.
For my own use i added a line that emails the backup file to a gmail account.
Of course that could be any email account.
On my server (Linux) i had to:
- Add some megabytes to the postfix “Max size of a message” (set it to 51200000).
- Install mutt to email from script “apt-get install mutt”.
The lines i added to the script are:
Added to the var’s: EMAIL=”fillinyourname@gmail.com”
Added at the end: echo ‘Backup attached’ | mutt -a $BACKUP_DIR/$FILE.gz $EMAIL -s “WordPress Backup”
Of course this setup will fail with large WordPress sites, but if your backup file is below 25 mb (the GMail limit) this works fine. Perhaps someone else would like to try and split the backup file in separate files and email the parts? (Overcoming the 25 mb limit).
Thanks for your script!
Marius
Hi Marius, thanks for your snippet, although I don’t think that e-mail is the right place to store backups, I’d rather have it FTP/SCP over to a different secure server made especially for backups, or hey, maybe Dropbox or Amazon S3 :)
Thanks again!
Hi Konstantin,
I agree with you that email is not very secure, and probably not the ideal place to store a backup….but it is free!
(Gmail is…). To make it a bit more secure i added encryption to the backup script, and did some cleaning up after the script because i want to run it from CRON. My complete script is posted to:
http://www.mobiledev.nl/backup-complete-wordpress-site-with-a-script/
Of course with a reference back to you and your site.
Regards,
Marius
Hey there Marius, the encryption part looks cool! And thanks for linking back to this article :) Enjoy your day!
If you want to keep a week’s worth of backups, run the backup script daily and use
Then your files will be called example.org.Mon.sql, example.org.Tue.sql etc and will overwrite as needed. Alternatively, add this line at the bottom to delete files over 14 days (adjust as needed)
Wow, thanks for the tip Ross, this certainly will come in useful!
Thanks for this useful script. I just have a problem when setting up a task to execute it daily.
If I run the script via ssh, no problem. But when the crontab runs the script, the .tar file isn’t removed so I get two files each time (a .tar and a .tar.gz).
Does anybody have this problem ?
Nico, post your script on Gist I’ll take a look :) You can star out your passwords.
Hi Konstantin, the script is just the same that the one you posted. Actually, it’s working now. In my testing purpose, I was running it every 5 minutes on a shared hosting, an each time I was refreshing, the “.tar” was still there. I was probably not patient enough with gzipping.
Thanks for your help and knowledge you share :)
No probs Nico, glad you found it useful :)
Hello KK
I like this solution.
I would like to automate:
1. The backup
2. FTP over SSL
3. Delete the backup
How would that best be done ?
Timoto, check out Ross’ comment about how to delete old backups. To FTP the backup over SSL you’d rather use SCP (a command line utility) and set up RSA keys for password-less access between the two servers. Watch out for security though ;) Good luck!
So is it not possible or easy to turn this into a plugin that can automatically run from within WordPress instead of using SCP? This is a good start to an interesting backup solution that I’d be very interested in using as a plugin since it’s the most secure. I am not a PHP developer myself so I wouldn’t know how to turn it into a plugin unfortunately, but I’d love to see someone take the initiative to make that happen (or I’d love to help someone with that initiative from the front-end).
Micah, I don’t think that a backup plan would be more secure if it was implemented inside the application you’re trying to backup. The approach in this post will work even if your WordPress installation has been broken, all plugins deactivated, malicious code, empty database, etc, because it does it from outside, while activating a broken/malicious plugin on your existing WordPress site with a backup plugin can cause the backup plugin to fail as well. With that being said though, there are many other backup solutions for WordPress, one of them is VaultPress. Oh and
scpcan run from inside PHP withexecorsystembut that’s even more insecure :)Hope this helped and thanks for your comment :)
Nice article. Thanks.
For us who are on a hostingaccount with cpanel and WordPress.
How would cronjob scripts look for backup:
- full WP-site
- WP content
- DB
- parts of WP-site
etc…
I would like to put it in my home/user level – maybe with 2 copies than delete…or something like that.
Just a little backup on the hosting-account.
Looked the whole day for this info but hasn´t found it yet – not even in cpanel/wordpress-forums.
Can you recommend any place where a collection of cron job scripts is…?
(especially for wordpress-backups)
TIPS: Or could you write an article for cpanel enduser cronjob-sripts – for different backups and other things…
With a nice collection of useful scripts.
Interesting site this – I´ll come back and bookmark it!
Best Wishes!
Mike
Mike, shell scripts are not related to cpanel, they’re unix scripts that can be triggered from many different places and one of them is the cron daemon. Cpanel, however, can help you edit your cron configuration file where you’ll be able to add the script to the schedule with a “nice GUI”, so what I’m saying is that you shouldn’t limit your searches to cpanel but rather look for shell scripts, or even better, learn the basics of shell scripting and then you’ll be able to really customize and do whatever you like :)
Hope this helped :)
In trying your script i realised that tar on mac osx doesnt support –transform.. That sounds quite lame.. or am i missing something??
Amit, maybe, since OS X probably ships with the BSD version of tar, while most Linux distributions come with the GNU version. Anyways you shouldn’t run your website on an OS X, unless it’s an OS X Server ;) Thanks for your comment!
@Konstantin thanks for the script and solution, it relly helps.
I’d like a bit help to improve the script.
Suppose I have several domains in my account
/var/www/domains/domain1.com
/var/www/domains/domain2.com
etc…
I want to automate the backup of all the domains (files and db), but do it separate to each domain (i.e. – have separate backup file to each).
How can I use something like a forearch loop to loop all sub-directories that uner /var/www/domains/ and run your script for each one?
Thanks
Pingback: Fine Tuning Your WordPress Themes with Theme.fm
better way would be to parse the wp-config so you dont have to enter it for each site.
http://pastebin.com/jvEDJKyp