Skip to main content

Using tar and gzip to create file backups

In this lesson you will create compressed, timestamped backups of your WordPress files using tar and gzip. You'll decide what's worth backing up and what to skip, then write a script that archives every site on your server.

tar bundles a whole directory tree into a single archive file, and gzip compresses it. They're two separate tools that are almost always used together, and you've probably run into a .tar.gz file before. We'll end up with one per site, much like the compressed database dumps from the mysqldump lesson.

Files vs uploads

A WordPress install is made up of a few different kinds of files. There's the WordPress core, the plugins and themes, and then the uploads and other files in wp-content that are generated as the site is used.

Much of this is reproducible. Core, plugins and themes can be reinstalled, and a lot of our configuration already lives in the config repository. The uploads directory, however, is usually the one irreplaceable part, and often the largest.

You could back up wp-content/uploads on its own to save space, but I prefer to archive the entire document root. It captures anything a plugin might have dropped somewhere unexpected, and it means a single file holds everything I need to bring a site back. As with database backups, I'd rather have one complete copy per site.

Excluding caches

Not everything in the document root is worth archiving. Page and object caches, for example, are regenerated automatically and only bloat our backups. We can leave them out with the --exclude option:

tar -czf backup.tar.gz --exclude='wp-content/cache' \
    -C /sites/uncached.org/public_html .

You can add as many --exclude options as you need, one for each path you want to leave out.

Preserving ownership

This is another reason we run our backups as root. In our setup the site files are mostly owned by www-data, and tar records that ownership inside the archive as it reads each file. On a restore, extracting as root puts the files back under their original owner, so a site comes back exactly as it was.

The files backup script

You can find the full up to date script in the config repository on GitHub, but I will explain some of the logic used to write that script here. It follows the same pattern as our database backup script.

We start by generating a timestamp, then loop through all the public_html directories in the /sites/ directory, skipping anything that isn't a WordPress site:

TIMESTAMP=$(date +%Y%m%d-%H%M%S)
for docroot in /sites/*/public_html; do
    [ -d "$docroot" ] || continue
    [ -f "$docroot/wp-config.php" ] || continue

Next we work out the site name and prepare a destination directory and file for it under /backups:

    site=$(basename "$(dirname "$docroot")")
    dest_dir="/backups/$site"
    mkdir -p "$dest_dir"
    dest_file="$dest_dir/${TIMESTAMP}.tar.gz"

Finally we create the archive, excluding our caches. We check tar's exit status by hand, because a file simply changing while it's read is only a warning, not a reason to throw the backup away:

    echo "[$site] archiving files to $dest_file"
    tar -czf "$dest_file" --exclude='wp-content/cache' \
        -C "$docroot" .

    status=$?
    if [ "$status" -eq 0 ]; then
        echo "[$site] done"
    elif [ "$status" -eq 1 ]; then
        echo "[$site] some files changed while archiving, keeping backup" >&2
    else
        echo "[$site] tar failed" >&2
        rm -f "$dest_file"
    fi
done

The tar options are -c to create an archive, -z to compress it with gzip, and -f to write it to a file. The -C option changes into the document root before archiving the . directory, so the paths stored inside the archive are relative to the site root, rather than starting with /sites/....

I called this script backup-files.sh and placed it in the /config/bin/ directory. Like our database script, it needs to run as root to read every site's files and record their ownership. To invoke it as my karl user I'll need sudo:

sudo /config/bin/backup-files.sh
ls -lh /backups/uncached.org/

Our /backups/uncached.org/ directory now holds a timestamped .tar.gz archive, right alongside the .sql.gz database dumps from the previous lesson.

Since all of this lives under /backups, it's already protected by the 0700 permissions we set on that directory earlier, so only root can access it.

This article is for premium members only. One-time payment of $196 unlocks lifetime access to all existing and future content on wpshell.com, and many other perks.