Having a robust backups strategy in place is not a luxury, it’s a must. because this I wrote this post to explain how to implement a professional, automated, backup for a WordPress site using Borg for fast, deduplicated, local backups and Rclone for encrypted offsite recovery. Moreover, the same holds true for any web app: keep both your application files and your database encrypted, with backups both on-site and off-site.
This system is built on three core principles:
- Why a Local Backup on a Separate Volume? Speed and resilience. A local backup on a separate physical or logical volume ensures that you can restore service incredibly quickly. It also protects your backups from filesystem corruption, accidental deletion, or disk failure on your primary server volume.
- Why BorgBackup? Efficiency and security. Borg provides client-side encryption, ensuring your data is secure before it’s even written to the backup volume. Its powerful deduplication means that only new, changed data “chunks” are stored, saving immense amounts of disk space and making subsequent backups extremely fast.
- Why Rclone for Offsite Backup? Disaster recovery and universal compatibility. Rclone acts as a universal translator for cloud storage. It allows us to take our secure Borg archives and transfer them, with an additional layer of zero-knowledge encryption, to a low-cost object store like AWS S3. If the entire server or data center is compromised, this offsite copy is the ultimate failsafe.
Part 1: Fast & Efficient Local Backups with Borg
Installation and Setup
First, ensure your server’s package list is up to date and install Borg.
sudo apt update
sudo apt install borgbackup
Next, confirm your backup volume is mounted and persistent. Use df -hT to check your filesystems. You should see your primary disk and your separate backup volume. I used hetzner, so after adding the volume you can check the ID of the volume in hetzner dashboard with the ID of the volum in the fstab.
df -hT
To ensure the volume remounts after a reboot, verify it has a proper entry in /etc/fstab.
# Example fstab entry
/dev/disk/by-id/scsi-0HC_Volume_103775914 /mnt/HC_Volume_103775914 ext4 discard,nofail,defaults 0 0
Initializing the Repository
Create a directory for the backup repository and initialize it with Borg. We will use repokey encryption, which is highly secure.
mkdir /borgbackup
borg init --encryption=repokey /borgbackup
Borg will prompt you to create a strong passphrase. This is the password you will use to access the backups.
CRITICAL: Export and Secure Your Encryption Key The repository is protected by your passphrase and a key file stored inside the repository. For true disaster recovery, you must export this key and store it in a completely separate, safe location. Without both the key and your passphrase, the data is unrecoverable.
borg key export /borgbackup
Backing Up WordPress Files
Now, create your first backup. This command backs up the /var/www/html directory, shows verbose progress, and uses zstd compression for a great balance of speed and size reduction.
borg create --stats --progress -v -C zstd,6 /borgbackup::'wp-{now:%Y-%m-%d-%H-%M}' /var/www/html
-C zstd,6: This tells Borg to compress the data. zstd,6 is a modern compression method that offers a great balance between speed and compression ratio.
The output will show a detailed report, including the compression ratio.
------------------------------------------------------------------------------
Original size Compressed size Deduplicated size
This archive: 74.92 MB 27.77 MB 27.67 MB
All archives: 74.92 MB 27.76 MB 27.85 MB
------------------------------------------------------------------------------
Restoring Files
To restore, first list the available archives.
borg list /borgbackup
To perform a full restore, create a temporary directory, extract the archive, and then copy the files back into place.
# Create a temporary location for the restore
mkdir /tmp/restore
cd /tmp/restore
# Extract the desired archive
borg extract /borgbackup::wp-2025-10-20-13-08
# Copy the files back, preserving all permissions and attributes
cp -a /tmp/restore/var/www/html/. /var/www/html/
# Clean up the temporary directory
rm -rf /tmp/restore
Part 2: Securing the WordPress Database
A complete backup requires both files and the database.
Dumping the Database
First, get your database credentials from your wp-config.php file.
# Run these from your /var/www/html directory
grep DB_NAME wp-config.php
grep DB_USER wp-config.php
grep DB_PASSWORD wp-config.php
Ensure your database user has the PROCESS privilege, which is required for a consistent dump with mysqldump.
-- Connect to MySQL as root
GRANT PROCESS ON *.* TO 'your_wp_user'@'localhost';
Now, create a backup directory and dump the database to a timestamped .sql file.
mkdir /dbbackup
mysqldump -u [USERNAME] -p[PASSWORD] [DATABASE_NAME] > /dbbackup/$(date +\%Y-\%m-\%d_%H-%M-%S)_wpdb.sql
Restoring the Database
Restoring is done by importing the .sql file back into the database.
mysql -u [USERNAME] -p[PASSWORD] [DATABASE_NAME] < /path/to/your_dump_file.sql
Part 3: Offsite Disaster Recovery with Rclone and AWS S3
Step 1: Configure AWS S3
- Create an S3 Bucket: In the AWS Console, create a new S3 bucket in a geographic region far from your server. in a different continent is much better.
- Create an IAM Policy: Create a policy that grants the minimum required permissions to access only that specific bucket.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetBucketLocation" ], "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME-HERE" }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME-HERE/*" } ] } - Create a Dedicated IAM User: Create a new IAM user (e.g.,
rclone-uploader), attach the policy you just created, and generate an access key. Securely store the Access Key ID and Secret Access Key.
Step 2: Install and Configure Rclone
Install Rclone using the official script.
curl https://rclone.org/install.sh | sudo bash
Run the configuration utility. We will create two “remotes”: one that points to S3, and a second “crypt” remote that wraps the first one to provide encryption.
rclone config
- Create
s3-rawremote:nfor a new remote.- Name:
s3-raw - Storage:
s3 - Provider:
Amazon Web Services S3 env_auth:false- Enter the Access Key ID and Secret Access Key you saved from AWS.
- Select the correct region.
- Accept defaults for the remaining options.
- Create
s3-encryptedremote:nfor a new remote.- Name:
s3-encrypted - Storage:
crypt - Remote to encrypt:
s3-raw:YOUR-BUCKET-NAME-HERE/encrypted-data - Filename encryption:
2(Obfuscate) - Create and confirm a very strong password for the remote.
- Create and confirm a different strong password for the salt.
- Accept defaults and quit.
Why is it designed this way?
This “layered” approach is incredibly powerful and flexible:
- Modularity: What if you decide to stop using Amazon S3 and switch to Google Drive? You don’t have to re-encrypt anything. You just create a new “Post Office” remote for Google Drive (gdrive-raw) and then edit your s3-encrypted remote to point to gdrive-raw instead of s3-raw. Your encryption layer remains the same.
- Simplicity: The Rclone developers only had to write the encryption logic once (in the crypt remote). They didn’t need to add separate encryption code for all 70+ cloud providers.
Step 3: The First Encrypted Sync
Use rclone sync to transfer your local backup directories to your new encrypted S3 remote. The -P flag shows progress.
# Sync the Borg repository
rclone sync -P /borgbackup s3-encrypted:borg
# Sync the database backups
rclone sync -P /dbbackup s3-encrypted:db
You will see a folder named encrypted-data. Inside S3, you will find files and folders with completely random, meaningless names. This proves your data is encrypted before it ever touches Amazon’s servers.
rclone ls s3-encrypted:
Part 4: Full Automation with a Cron Job
Step 1: Create Secure Credential Files (Crucial!)
Never store passwords directly in scripts. Create dedicated, permission-restricted files.
For MySQL:
# Create and open the file
nano /root/.my.cnf
# Add the following content:
[mysqldump]
user = your_wp_user
password = "YOUR_DATABASE_PASSWORD_HERE"
# Set strict permissions so only root can read it
chmod 600 /root/.my.cnf
For Borg:
# Put your passphrase in the file
echo "YOUR_BORG_PASSPHRASE_HERE" > /root/.borg_passphrase
# Set strict permissions
chmod 600 /root/.borg_passphrase
Step 2: The Backup Script
Save the following script as /usr/local/bin/daily_backup.sh. This script automates every step we performed manually.
#!/bin/bash
Features of the script
- Comprehensive Backup: Backs up both the entire WordPress file directory and the MySQL database.
- Secure & Encrypted:
- Database credentials are not stored in the script; they are read from a secure
.my.cnffile. - Borg passphrase is not stored in the script; it is read from a restricted file in
/root/.borg_passphrase. - Backups are encrypted locally using BorgBackup.
- Database credentials are not stored in the script; they are read from a secure
- Efficient Storage: Uses BorgBackup for deduplicated backups, saving significant storage space over time.
- Automated Offsite Sync: Securely syncs the encrypted backup repository to a cloud storage provider using Rclone.
- Intelligent Retention Policy:
- Borg Archives: keeps one daily backup for 7 days, one weekly backup for 4 weeks, and one monthly backup for 6 months. This provides a rich history without using excessive space.
- Raw SQL Dumps: The local SQL dump files are automatically deleted after 30 days to save local disk space.
- Robust Logging & Error Handling: The script logs all its actions and will stop immediately if any command fails.
- Unique, Timestamped Backups: Every backup run creates a unique, timestamped archive, preventing overwrites and allowing for multiple backups per day.
Remember to make the script executable:
chmod +x /usr/local/bin/daily_backup.sh
Step 3: Scheduling with Cron
Finally, automate the script to run daily using cron.
# Open the root user's crontab
crontab -e
Add the following line to run the script at 2:00 AM every morning and log the output.
0 2 * * * /usr/local/bin/daily_backup.sh > /var/log/daily_backup.log 2>&1
Conclusion
You now have a fully automated enterprise-grade backup solution protecting your precious data, including encryption and offsite storage, and we’ve leveraged Borg’s smart retention policies, compression and deduplication to make backups efficient, while using Rclone to securely push copies offsite to AWS S3 storage provider. However, by Rclone you have access to over 70 cloude-native storage soltions.


