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.
sudo 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:
rsync --version | head -n 1 tar --version | head -n 1 systemctl is-active cron
Expected output includes:
rsync 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.
sudo 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:
sudo find /srv/sample-app -type f -maxdepth 3 -print sudo cat /srv/sample-app/app.txt
Expected output includes:
/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.
sudo 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:
sudo find /var/backups/automated -maxdepth 1 -type d -print
Expected output:
/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.
sudo 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:
sudo bash -n /usr/local/sbin/raff-backup.sh && echo "Backup script syntax is valid"
Expected output:
Backup script syntax is valid
Step 5 — Run the first backup
Run the backup script manually before scheduling it.
sudo /usr/local/sbin/raff-backup.sh
Verify the archive, log entry, and rsync mirror:
sudo 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:
sample-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.
sudo 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:
sudo /usr/local/sbin/raff-backup-check.sh
Expected output:
Backup 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.
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 sudo cat /var/backups/automated/restore-test/sample-app/app.txt
Expected output:
Raff backup tutorial test file
Verify the restored directory structure:
sudo find /var/backups/automated/restore-test -maxdepth 3 -type f -print
Expected output includes:
/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.
sudo 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:
sudo cat /etc/cron.d/raff-backup sudo systemctl is-active cron
Expected output includes:
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 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.
sudo /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:
Backup 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.
sudo 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:
if [ ! -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:
Cleanup 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:
sudo systemctl status cron sudo ls -l /etc/cron.d/raff-backup sudo cat /etc/cron.d/raff-backup
Expected checks:
cron.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:
sudo systemctl restart cron sudo systemctl is-active cron
Expected output:
active
The backup script fails with permission denied
Cause: The script is not executable by root, or the backup directories have incorrect ownership.
Fix:
sudo 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:
backup 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:
sudo 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:
sample-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:
sudo 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:
/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