A reliable backup gives you a way back when an update fails, a file is deleted, or the database is damaged. This guide builds a simple backup system using standard Linux tools, so it does not require a paid WordPress backup service.
The finished script creates a complete site archive that contains both the WordPress files and a fresh database export. It also verifies the archive before treating it as a completed backup and automatically removes older copies.
What this setup does
- Exports the WordPress database.
- Stores the SQL export inside the WordPress folder.
- Creates one compressed archive containing the full site and SQL export.
- Checks that the archive can be read.
- Keeps the 10 newest backups.
- Runs automatically through cron.
Each run produces one dated .tar.gz file. Keeping the database export and site files together makes it easier to identify which files and database snapshot belong to the same backup.
This guide assumes:
- WordPress is stored in
/var/www/SITE_NAME - The database name is the same as
SITE_NAME - Backups are stored in
/backup - The script runs as root
Replace all example names with your own values.
Run the commands from a root shell unless a step says otherwise. Before using the script on a production server, confirm the WordPress path, database name, MySQL account, and intended archive owner.
1. Create the backup folder
Run:
The backup directory is kept outside the website's document root. Mode 700 allows only root to enter the directory or list its contents.
mkdir -p /backup
chmod 700 /backup
2. Create a MySQL login file
mysqldump needs database credentials, but placing the password on the command line or directly in the script can expose it. A separate, permission-protected client file keeps the script reusable and limits who can read the credentials.
Create:
/root/.mysql-SITE_NAME.cnf
Add:
[client]
user=MYSQL_USER
password=MYSQL_PASSWORD
host=localhost
Protect the file:
chmod 600 /root/.mysql-SITE_NAME.cnf
Do not place the password directly inside the backup script.
Use a MySQL account with only the permissions required to read and export this site's database. Never share the login file or include it in the site archive.
3. Create the backup script
The script stops when a command fails, creates the database export, compresses the entire WordPress directory, tests the temporary archive, and only then moves it into place as the finished backup. This prevents an obviously unreadable archive from being mistaken for a successful backup.
Create:
/root/wordpress-backup.sh
Add:
#!/usr/bin/env bash
set -Eeuo pipefail
site="SITE_NAME"
webroot="/var/www/$site"
backup_dir="/backup"
sql_dir="$webroot/sqlbackup"
sql_file="$sql_dir/$site.sql"
archive_owner="BACKUP_USER:BACKUP_GROUP"
backup_file="$backup_dir/${site}_$(date +%F).tar.gz"
temporary_file="${backup_file}.tmp"
mkdir -p "$backup_dir" "$sql_dir"
mysqldump
--defaults-extra-file="/root/.mysql-$site.cnf"
--single-transaction
--quick
--no-tablespaces
"$site" > "$sql_file"
tar -C "$webroot" -czf "$temporary_file" .
tar -tzf "$temporary_file" > /dev/null
chmod 600 "$temporary_file"
chown "$archive_owner" "$temporary_file"
mv "$temporary_file" "$backup_file"
mapfile -t backups < <(
printf '%sn' "$backup_dir"/"${site}"_*.tar.gz | sort -r
)
if (( ${#backups[@]} > 10 )); then
rm -f -- "${backups[@]:10}"
fi
Replace:
SITE_NAMEwith the WordPress folder and database name.BACKUP_USERandBACKUP_GROUPwith the account that should own the archives.
How the script works
set -Eeuo pipefailmakes the script stop on errors, unset variables, and failed pipeline commands.mysqldumpcreates a transactionally consistent SQL export without locking normal InnoDB writes for the duration of the backup.tarpackages the WordPress files and SQL export into a compressed temporary archive.tar -tzfreads the archive index as a basic integrity check.- The verified temporary file receives restrictive permissions and is renamed to its final dated filename.
- The retention section sorts matching archives by filename and deletes everything after the newest 10.
Security note: the SQL export remains in sqlbackup inside the WordPress folder after the script finishes. Ensure your web-server configuration denies all public access to that directory. A database export can contain sensitive site data and must never be downloadable from the website.
4. Protect the script
The script reveals server paths and the location of the MySQL login file. Restricting it to root prevents other local users from reading or changing the backup process.
Run:
chmod 700 /root/wordpress-backup.sh
5. Test the script
Always perform a manual run before scheduling the job. A successful exit alone is not enough: confirm that the expected archive exists and that its file list includes both the website and SQL export.
Run:
/root/wordpress-backup.sh
Check the result:
ls -lh /backup
tar -tzf /backup/SITE_NAME_YYYY-MM-DD.tar.gz | head
The archive should contain the WordPress files and:
./sqlbackup/SITE_NAME.sql
6. Schedule the backup
Cron runs the tested script without manual intervention. Choose a quiet time when server activity is normally low, and select a frequency that matches how often the site changes.
Edit the root crontab:
crontab -e
Example: run every three days at 1:00 AM:
0 1 */3 * * /root/wordpress-backup.sh
This cron expression runs at 1:00 AM on every third day of the month. Because months have different lengths, it does not guarantee an exact 72-hour interval across month boundaries.
Example: run on the first day of every month at 1:00 AM:
0 1 1 * * /root/wordpress-backup.sh
Use a separate script and MySQL login file for each site.
7. Check the scheduled job
A backup plan is useful only when jobs continue to run successfully. Check the archive timestamps after the first scheduled execution and periodically afterward. You should also test a restore on a separate system from time to time.
Show the root cron jobs:
crontab -l
After the next scheduled run, check:
ls -lht /backup
Only the 10 newest archives for each site should remain.
8. Basic restore steps
A restore replaces important production data, so inspect the selected archive in an empty temporary directory first. Confirm that it belongs to the correct site and date before copying files or importing SQL.
Extract the selected archive into an empty folder:
mkdir -p /tmp/wordpress-restore
tar -xzf /backup/SITE_NAME_YYYY-MM-DD.tar.gz
-C /tmp/wordpress-restore
Restore the files to the correct WordPress folder only after saving the current site.
Import the database:
mysql
--defaults-extra-file="/root/.mysql-SITE_NAME.cnf"
SITE_NAME
< /tmp/wordpress-restore/sqlbackup/SITE_NAME.sql
Check file ownership and permissions after restoring.
After restoring, test the home page, WordPress administration area, media files, forms, and other important site functions. Keep the pre-restore copy until you have confirmed that the recovered site works correctly.
Important limitation
These archives are stored on the same server. They protect against damaged files, failed updates, and accidental changes, but not against total server or disk loss.
Copy the /backup folder to another machine or remote storage.
Summary
This setup provides a straightforward local backup: it exports the database, archives the full WordPress directory, checks the archive, keeps a limited history, and runs from cron. The most important next step is to copy those archives off the server and periodically prove that you can restore one.

Comments