In this tutorial, you’ll automate server backups on a Raff Ubuntu 24.04 VM using cron, rsync, compressed archives, retention cleanup, and a verified restore test.
Server backups protect application files, configuration, and operational data from accidental deletion, failed deployments, and broken updates. This tutorial creates a working backup workflow on a fresh Raff VM, runs it manually, schedules it with cron, verifies the archive, and restores a test file to confirm the backup is usable.
Raff Technologies runs over 10,000 VMs across its compute platform in Vint Hill, Virginia, on AMD EPYC hardware with NVMe storage.
Prerequisites:
- A Raff Ubuntu 24.04 VM
- SSH access with sudo privileges
- Basic familiarity with Linux paths and shell commands
This tutorial was written for a Raff VM with 2 vCPU, 4 GB DDR5 RAM, 40 GB NVMe storage, running Ubuntu 24.04 LTS.
Tested on Raff infrastructure by Aybars Altınyay, platform engineer and technical writer at Raff Technologies.
Step 1 — Install backup utilities
Install rsync, cron, and archive utilities used by the backup workflow.
bashsudo apt update
sudo apt install -y rsync cron tar gzip coreutils
sudo systemctl enable --now cron
Verify the required tools are installed and cron is active:
bashrsync --version | head -n 1
tar --version | head -n 1
systemctl is-active cron
Expected output includes:
bashrsync version 3.x.x
tar (GNU tar) 1.x
active
Step 2 — Create test application data
Create a small sample application directory so the backup and restore process can be verified on any fresh Ubuntu VM.
bashsudo mkdir -p /srv/sample-app/config
sudo tee /srv/sample-app/app.txt > /dev/null <<'EOF'
Raff backup tutorial test file
EOF
sudo tee /srv/sample-app/config/settings.env > /dev/null <<'EOF'
APP_NAME=raff-backup-demo
APP_ENV=production
EOF
sudo chown -R root:root /srv/sample-app
sudo chmod -R 750 /srv/sample-app
Verify the sample files exist:
bashsudo find /srv/sample-app -type f -maxdepth 3 -print
sudo cat /srv/sample-app/app.txt
Expected output includes:
bash/srv/sample-app/app.txt
/srv/sample-app/config/settings.env
Raff backup tutorial test file
Step 3 — Create backup directories
Create a dedicated backup directory structure for archives, logs, restore tests, and the rsync mirror.
bashsudo mkdir -p /var/backups/automated/{archives,logs,restore-test,rsync-mirror}
sudo chown -R root:root /var/backups/automated
sudo chmod -R 750 /var/backups/automated
Verify the directory structure:
bashsudo find /var/backups/automated -maxdepth 1 -type d -print
Expected output:
bash/var/backups/automated
/var/backups/automated/archives
/var/backups/automated/logs
/var/backups/automated/restore-test
/var/backups/automated/rsync-mirror
Step 4 — Write the backup script
Create a reusable backup script that archives the sample application, mirrors it with rsync, logs each run, and removes old archives after seven days.
bashsudo tee /usr/local/sbin/raff-backup.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BACKUP_ROOT="/var/backups/automated"
SOURCE_DIR="/srv/sample-app"
ARCHIVE_DIR="${BACKUP_ROOT}/archives"
RSYNC_MIRROR="${BACKUP_ROOT}/rsync-mirror"
LOG_FILE="${BACKUP_ROOT}/logs/backup.log"
RETENTION_DAYS=7
STAMP="$(date +%F-%H%M%S)"
ARCHIVE_FILE="${ARCHIVE_DIR}/sample-app-${STAMP}.tar.gz"
mkdir -p "$ARCHIVE_DIR" "$RSYNC_MIRROR" "$(dirname "$LOG_FILE")"
if [ ! -d "$SOURCE_DIR" ]; then
echo "$(date -Is) ERROR source directory not found: $SOURCE_DIR" | tee -a "$LOG_FILE"
exit 1
fi
tar -czf "$ARCHIVE_FILE" -C "$(dirname "$SOURCE_DIR")" "$(basename "$SOURCE_DIR")"
rsync -a --delete "$SOURCE_DIR"/ "$RSYNC_MIRROR"/sample-app/
find "$ARCHIVE_DIR" -type f -name "sample-app-*.tar.gz" -mtime +"$RETENTION_DAYS" -delete
echo "$(date -Is) backup complete: $ARCHIVE_FILE" | tee -a "$LOG_FILE"
EOF
sudo chown root:root /usr/local/sbin/raff-backup.sh
sudo chmod 750 /usr/local/sbin/raff-backup.sh
Verify the script syntax:
bashsudo bash -n /usr/local/sbin/raff-backup.sh && echo "Backup script syntax is valid"
Expected output:
bashBackup script syntax is valid
Step 5 — Run the first backup
Run the backup script manually before scheduling it.
bashsudo /usr/local/sbin/raff-backup.sh
Verify the archive, log entry, and rsync mirror:
bashsudo ls -lh /var/backups/automated/archives/
sudo tail -n 1 /var/backups/automated/logs/backup.log
sudo cat /var/backups/automated/rsync-mirror/sample-app/app.txt
Expected output includes:
bashsample-app-YYYY-MM-DD-HHMMSS.tar.gz
backup complete: /var/backups/automated/archives/sample-app-YYYY-MM-DD-HHMMSS.tar.gz
Raff backup tutorial test file
Step 6 — Add a backup health check
Create a health check script that confirms a recent archive exists, the archive can be listed, and the rsync mirror contains the expected file.
bashsudo tee /usr/local/sbin/raff-backup-check.sh > /dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BACKUP_ROOT="/var/backups/automated"
ARCHIVE_DIR="${BACKUP_ROOT}/archives"
RSYNC_MIRROR="${BACKUP_ROOT}/rsync-mirror"
LATEST_ARCHIVE="$(find "$ARCHIVE_DIR" -type f -name 'sample-app-*.tar.gz' -mtime -1 | sort | tail -n 1)"
if [ -z "$LATEST_ARCHIVE" ]; then
echo "Backup check failed: no archive from the last 24 hours"
exit 1
fi
tar -tzf "$LATEST_ARCHIVE" >/dev/null
if [ ! -f "$RSYNC_MIRROR/sample-app/app.txt" ]; then
echo "Backup check failed: rsync mirror is missing app.txt"
exit 1
fi
echo "Backup check passed: $LATEST_ARCHIVE"
EOF
sudo chown root:root /usr/local/sbin/raff-backup-check.sh
sudo chmod 750 /usr/local/sbin/raff-backup-check.sh
Run the health check:
bashsudo /usr/local/sbin/raff-backup-check.sh
Expected output:
bashBackup check passed: /var/backups/automated/archives/sample-app-YYYY-MM-DD-HHMMSS.tar.gz
Step 7 — Verify a restore test
Restore the latest backup archive into a separate test directory and confirm the restored file content is correct.
bashLATEST_ARCHIVE="$(sudo find /var/backups/automated/archives -type f -name 'sample-app-*.tar.gz' | sort | tail -n 1)"
sudo rm -rf /var/backups/automated/restore-test
sudo mkdir -p /var/backups/automated/restore-test
sudo tar -xzf "$LATEST_ARCHIVE" -C /var/backups/automated/restore-test
sudo cat /var/backups/automated/restore-test/sample-app/app.txt
Expected output:
bashRaff backup tutorial test file
Verify the restored directory structure:
bashsudo find /var/backups/automated/restore-test -maxdepth 3 -type f -print
Expected output includes:
bash/var/backups/automated/restore-test/sample-app/app.txt
/var/backups/automated/restore-test/sample-app/config/settings.env
Step 8 — Schedule backups with cron
Create a root-owned cron file that runs the backup daily at 02:15 and the health check daily at 03:00.
bashsudo tee /etc/cron.d/raff-backup > /dev/null <<'EOF'
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
15 2 * * * root /usr/local/sbin/raff-backup.sh >> /var/backups/automated/logs/cron.log 2>&1
0 3 * * * root /usr/local/sbin/raff-backup-check.sh >> /var/backups/automated/logs/backup-check.log 2>&1
EOF
sudo chown root:root /etc/cron.d/raff-backup
sudo chmod 644 /etc/cron.d/raff-backup
Verify the cron file is installed and readable by cron:
bashsudo cat /etc/cron.d/raff-backup
sudo systemctl is-active cron
Expected output includes:
bash15 2 * * * root /usr/local/sbin/raff-backup.sh >> /var/backups/automated/logs/cron.log 2>&1
0 3 * * * root /usr/local/sbin/raff-backup-check.sh >> /var/backups/automated/logs/backup-check.log 2>&1
active
Step 9 — Verify the complete backup workflow
Run the full workflow once more and verify that archives, logs, the rsync mirror, the health check, the restore test, and the cron schedule all work.
bashsudo /usr/local/sbin/raff-backup.sh
sudo /usr/local/sbin/raff-backup-check.sh
LATEST_ARCHIVE="$(sudo find /var/backups/automated/archives -type f -name 'sample-app-*.tar.gz' | sort | tail -n 1)"
sudo rm -rf /var/backups/automated/restore-test
sudo mkdir -p /var/backups/automated/restore-test
sudo tar -xzf "$LATEST_ARCHIVE" -C /var/backups/automated/restore-test
echo "Latest archive:"
echo "$LATEST_ARCHIVE"
echo "Restored file:"
sudo cat /var/backups/automated/restore-test/sample-app/app.txt
echo "Rsync mirror:"
sudo cat /var/backups/automated/rsync-mirror/sample-app/app.txt
echo "Cron schedule:"
sudo grep raff-backup /etc/cron.d/raff-backup
Expected output includes:
bashBackup check passed: /var/backups/automated/archives/sample-app-YYYY-MM-DD-HHMMSS.tar.gz
Latest archive:
/var/backups/automated/archives/sample-app-YYYY-MM-DD-HHMMSS.tar.gz
Restored file:
Raff backup tutorial test file
Rsync mirror:
Raff backup tutorial test file
Cron schedule:
15 2 * * * root /usr/local/sbin/raff-backup.sh >> /var/backups/automated/logs/cron.log 2>&1
0 3 * * * root /usr/local/sbin/raff-backup-check.sh >> /var/backups/automated/logs/backup-check.log 2>&1
The backup workflow is complete when the script creates a compressed archive, the rsync mirror contains the latest files, the health check passes, the restore test returns the original file content, and the cron schedule is installed.
Cleanup (Optional)
Use this section only if you want to remove the backup workflow and test data from the Raff VM.
⚠️ Warning: The following commands permanently delete the backup scripts, cron schedule, backup archives, logs, restore-test data,
rsyncmirror, and sample application files. Copy any backup archive you need before proceeding.
bashsudo rm -f /etc/cron.d/raff-backup
sudo rm -f /usr/local/sbin/raff-backup.sh
sudo rm -f /usr/local/sbin/raff-backup-check.sh
sudo rm -rf /var/backups/automated
sudo rm -rf /srv/sample-app
Verify cleanup completed:
bashif [ ! -e /etc/cron.d/raff-backup ] \
&& [ ! -e /usr/local/sbin/raff-backup.sh ] \
&& [ ! -d /var/backups/automated ] \
&& [ ! -d /srv/sample-app ]; then
echo "Cleanup complete"
fi
Expected output:
bashCleanup complete
Troubleshooting
The cron job does not run
Cause: The cron service is inactive, the cron file has incorrect permissions, or the schedule has not reached its next run time.
Fix:
bashsudo systemctl status cron
sudo ls -l /etc/cron.d/raff-backup
sudo cat /etc/cron.d/raff-backup
Expected checks:
bashcron.service is active
/etc/cron.d/raff-backup is owned by root
/etc/cron.d/raff-backup has 644 permissions
Restart cron after fixing the file:
bashsudo systemctl restart cron
sudo systemctl is-active cron
Expected output:
bashactive
The backup script fails with permission denied
Cause: The script is not executable by root, or the backup directories have incorrect ownership.
Fix:
bashsudo chown root:root /usr/local/sbin/raff-backup.sh
sudo chmod 750 /usr/local/sbin/raff-backup.sh
sudo chown -R root:root /var/backups/automated
sudo chmod -R 750 /var/backups/automated
sudo /usr/local/sbin/raff-backup.sh
Expected output includes:
bashbackup complete: /var/backups/automated/archives/sample-app-YYYY-MM-DD-HHMMSS.tar.gz
The restore test fails
Cause: The archive path is empty, the archive is corrupted, or the archive was deleted by retention cleanup.
Fix:
bashsudo ls -lh /var/backups/automated/archives/
sudo /usr/local/sbin/raff-backup.sh
LATEST_ARCHIVE="$(sudo find /var/backups/automated/archives -type f -name 'sample-app-*.tar.gz' | sort | tail -n 1)"
sudo tar -tzf "$LATEST_ARCHIVE" | head
Expected output includes:
bashsample-app/
sample-app/app.txt
sample-app/config/
The rsync mirror is missing files
Cause: The source directory was changed or deleted before the latest backup run.
Fix:
bashsudo find /srv/sample-app -type f -maxdepth 3 -print
sudo /usr/local/sbin/raff-backup.sh
sudo find /var/backups/automated/rsync-mirror/sample-app -type f -maxdepth 3 -print
Expected output includes:
bash/var/backups/automated/rsync-mirror/sample-app/app.txt
/var/backups/automated/rsync-mirror/sample-app/config/settings.env
Conclusion
You now have an automated backup workflow running on a Raff Ubuntu 24.04 VM with compressed archives, an rsync mirror, retention cleanup, a scheduled cron job, a health check, and a verified restore test. If you haven’t deployed your Raff VM yet, you can spin one up in 60 seconds at rafftechnologies.com.
Next: Secure Ubuntu 24.04 Server
Related: Install PostgreSQL on Ubuntu 24.04
Guide: Cloud Server Backup Strategies
