Mastodon

How to Back Up a WordPress Database and Files on a VPS with a Cron Job

  1. Home
  2. Guides
  3. How to Back Up a WordPress Database and Files on a...

Managed WordPress hosting takes daily backups for granted because the host runs them for you. On a self-managed VPS, nothing backs anything up unless you build it yourself. If a bad plugin update corrupts the database, a bug in your own code wipes a table, or the server itself fails, whatever exists on disk at that moment is all you have, and if that includes a broken database with no prior copy, there is nothing to restore to.

A WordPress backup has two parts that need to be captured separately: the database, which holds every post, page, comment, and setting, and the wp-content directory, which holds your themes, plugins, and uploaded media. Both change independently and both need to be restorable on their own. By the end of this guide you will have a script that captures both, keeps a rolling window of recent backups, runs automatically every night through cron, and copies the result somewhere other than the disk it was taken from.

This assumes you have WordPress running on a VPS following the WordPress on Nginx guide, with a database name, database user, and site directory already in place.

Step 1 - Choosing a Backup Location

Backups need somewhere to live that is not inside the WordPress installation itself, so a filesystem issue or a bad deploy affecting the site does not also take out the backups sitting next to it. Create a dedicated directory outside the web root:

sudo mkdir -p /var/backups/wordpress
sudo chown $USER:$USER /var/backups/wordpress

This directory is local storage on the same VPS for now; step 7 covers copying backups off the server entirely, which matters more than where they sit locally.

Step 2 - Creating a Dedicated MySQL Credentials File

Passing a database password directly on the command line with mysqldump -p works, but MySQL prints a warning that doing so is insecure, and the password sits in your shell history and script file in plain text. A cleaner approach is a credentials file that mysqldump reads automatically:

nano ~/.my.cnf

Add the following, replacing wp_user and your_password with your WordPress database credentials:

[client]
user = wp_user
password = your_password

Restrict the file so only your user can read it, since it contains a plaintext password:

chmod 600 ~/.my.cnf

With this file in place, mysqldump and mysql pick up the credentials automatically without either appearing in a command line argument.

Step 3 - Writing the Backup Script

Create the script file:

sudo nano /usr/local/bin/wp-backup.sh

Paste the following, replacing wordpress_db with your database name and /var/www/yoursite.com/public with your actual site directory:

#!/bin/bash
set -e

BACKUP_DIR="/var/backups/wordpress"
SITE_DIR="/var/www/yoursite.com/public"
DB_NAME="wordpress_db"
TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S)
RETENTION_DAYS=7

mysqldump "$DB_NAME" > "$BACKUP_DIR/db_$TIMESTAMP.sql"
tar -czf "$BACKUP_DIR/files_$TIMESTAMP.tar.gz" -C "$SITE_DIR" wp-content

find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete

The mysqldump command needs no username or password flags because it reads them from ~/.my.cnf. Make the script executable:

sudo chmod +x /usr/local/bin/wp-backup.sh

Step 4 - Testing the Script Manually

Run the script once by hand before trusting it to a schedule. A script with a typo in a path fails just as silently at 3am as it does right now, so confirming it works manually first is worth the minute it takes:

/usr/local/bin/wp-backup.sh

Check that both files were created:

ls -lh /var/backups/wordpress

Output:

-rw-r--r-- 1 deploy deploy  842K db_2026-07-05_03-00-01.sql
-rw-r--r-- 1 deploy deploy   41M files_2026-07-05_03-00-01.tar.gz

Two timestamped files confirm the script captured both the database and the files correctly. With the script verified, the next step is scheduling it to run without you.

Step 5 - Scheduling the Script with Cron

Open your user's crontab:

crontab -e

Add a line to run the backup every night at 3am and log the output:

0 3 * * * /usr/local/bin/wp-backup.sh >> /var/log/wp-backup.log 2>&1

Save and close the editor; cron picks up the new entry automatically. The redirect at the end sends both normal output and errors to a log file, so a failure shows up somewhere instead of vanishing silently.

Step 6 - Confirming Cron Actually Ran

Do not assume the cron job is working just because you did not see an error. The morning after adding it, check the log file for the expected output:

cat /var/log/wp-backup.log

And confirm a new pair of backup files landed with the expected timestamp:

ls -lh /var/backups/wordpress

If nothing appears, check that the script path in crontab matches exactly, and that cron is running under a user with permission to read the site directory and write to the backup directory.

Step 7 - Copying Backups Off the Server

A backup stored on the same disk as the site it backs up does not protect you against the one failure that matters most: losing the server entirely. The minimum viable off-site copy is an rsync push to a second machine you control, run on the same schedule as the backup itself. Add a second cron line that runs shortly after the backup script, giving it time to finish first:

15 3 * * * rsync -az /var/backups/wordpress/ [email protected]:/backups/wordpress/

Replace [email protected] with a server or storage host you control, set up with SSH key authentication so the rsync can run unattended without a password prompt. If you do not have a second server available, tools like rclone support pushing to object storage providers instead, which is worth setting up once you have this basic rsync version working and want a destination that is not tied to a second VPS you are also paying for.

Your WordPress Site Now Backs Itself Up

You now have a nightly backup of your WordPress database and files, rotated automatically to keep the last seven days, with a copy pushed off the server rather than sitting only on the disk it was taken from. Test a restore occasionally rather than assuming the backups are good; import a database dump into a throwaway database and confirm it loads before you actually need it under pressure. From here, pair this with SSL via Let's Encrypt and Redis object caching if you have not set those up yet.

Get a VPS

Follow along with your own server. Plans from £7.98/month.

View VPS Plans

More Guides

How to Migrate WordPress from Shared Hosting to a VPS

Move a live WordPress site from shared hosting to a VPS: database export with my...

How to Set Up a Fresh Ubuntu 24.04 VPS

Step-by-step: configure a fresh Ubuntu 24.04 VPS from first SSH login to a secur...

Securing Your VPS with UFW and Fail2Ban

How to configure UFW and Fail2Ban on Ubuntu to protect your VPS from port scans...