VPS Automated Backups with rsync and cron: A Step-by-Step Technical Guide

Data loss is not a question of if but when. A misconfigured script, a ransomware attack, or an accidental rm -rf can wipe out months of work in seconds. Automated backups are your safety net, and on a Linux VPS, the combination of rsync and cron gives you a powerful, flexible, and cost-effective backup solution. This guide walks through setting up automated backups with rsync and cron, covering incremental vs full backup strategies, off-site replication, and best practices for keeping your data safe. When choosing a VPS for backups, find VPS plans with enough storage for backups to ensure you have adequate capacity.

Why rsync and cron?

Before exploring third-party backup tools, understand why rsync + cron is a compelling combination:

  • rsync — Transfers only the parts of files that changed (delta encoding) rather than copying entire files each time. This makes backups fast and bandwidth-efficient.
  • cron — The Linux job scheduler that runs your backup script on a predefined schedule (daily, hourly, weekly) with zero manual intervention.
  • No proprietary formats — Your backups are plain files and directories on disk. You can restore them with any standard Linux tool.
  • Incremental by default — rsync naturally performs incremental backups after the initial full copy, saving storage space and transfer time.
  • SSH-based security — rsync over SSH uses the same encryption as your SSH connections, keeping data secure in transit.

Prerequisites

Before setting up backups, ensure you have:

  • A Linux VPS with Ubuntu 22.04 or 24.04 (or any modern distribution)
  • SSH access with a non-root sudo user
  • rsync installed (sudo apt install rsync if missing)
  • Enough disk space for backup storage (see provider comparison above)
  • (Optional) A second VPS or cloud storage destination for off-site backups

Step 1: Create a Backup Script

Create a backup script at /usr/local/bin/backup.sh:

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

Add the following content, customizing the variables for your environment:

#!/bin/bash

# Backup configuration
BACKUP_DIR="/backups/daily"
SOURCE_DIRS="/var/www /etc /home"
RETENTION_DAYS=7
DATE=$(date +%Y-%m-%d_%H-%M-%S)
LOG_FILE="/var/log/backup.log"

# Create backup directory
mkdir -p "$BACKUP_DIR/$DATE"

# Perform backup with rsync
for dir in $SOURCE_DIRS; do
    BASENAME=$(basename "$dir")
    rsync -avz --delete --link-dest="$BACKUP_DIR/latest"         "$dir/" "$BACKUP_DIR/$DATE/$BASENAME/"
done

# Update the 'latest' symlink
rm -f "$BACKUP_DIR/latest"
ln -s "$BACKUP_DIR/$DATE" "$BACKUP_DIR/latest"

# Remove backups older than retention period
find "$BACKUP_DIR" -maxdepth 1 -type d -mtime +$RETENTION_DAYS -exec rm -rf {} \;

# Log completion
echo "$DATE: Backup completed successfully" >> "$LOG_FILE"

Make the script executable:

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

Step 2: Set Up the cron Schedule

Edit your root crontab to run the backup script on a schedule:

sudo crontab -e

Add one of these entries depending on your backup frequency needs:

# Daily backup at 2:00 AM
0 2 * * * /usr/local/bin/backup.sh

# Twice daily (2:00 AM and 2:00 PM)
0 2,14 * * * /usr/local/bin/backup.sh

# Hourly backup (for high-traffic databases)
0 * * * * /usr/local/bin/backup.sh

Verify the cron job is registered:

sudo crontab -l

Full vs Incremental Backup Strategy

The script above uses rsync’s --link-dest feature to create incremental backups that appear as full copies. Here is how it works:

  • First run: rsync copies all files from source directories to /backups/daily/YYYY-MM-DD_HH-MM-SS/ and creates a latest symlink pointing to it.
  • Subsequent runs: rsync compares the current state of source files against the latest directory (via --link-dest). Unchanged files are hard-linked from the previous backup — they take no additional disk space. Only changed or new files are actually written.
  • Net effect: Each backup directory looks like a complete snapshot. You can browse any date’s backup independently. But storage usage is minimal because unchanged files are shared via hard links.

This is the ideal strategy for most VPS users: you get the restore convenience of full backups with the storage efficiency of incremental backups.

Step 3: Off-Site Backup to a Second VPS

Keeping backups on the same VPS that hosts your data is risky — if the server fails or gets compromised, your backups fail with it. A proper backup strategy requires an off-site copy. The easiest way is to rsync your backups to a second VPS.

Set up SSH key-based authentication between your primary and backup VPS:

On the primary VPS, generate an SSH key specifically for backups:

sudo ssh-keygen -t ed25519 -f /root/.ssh/backup_key -N ""

On the backup VPS, create a backup user and add the public key:

sudo adduser backupuser
sudo mkdir -p /home/backupuser/.ssh
# Copy the contents of /root/.ssh/backup_key.pub from primary VPS
sudo nano /home/backupuser/.ssh/authorized_keys

Back on the primary VPS, test the connection:

sudo ssh -i /root/.ssh/backup_key backupuser@backup-vps-ip

Now extend your backup script to push to the remote VPS. Add this to the end of /usr/local/bin/backup.sh:

# Off-site sync to backup VPS
REMOTE_USER="backupuser"
REMOTE_HOST="backup-vps-ip"
REMOTE_DIR="/backups/primary-vps"

rsync -avz --delete -e "ssh -i /root/.ssh/backup_key"     "$BACKUP_DIR/" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"

Now every backup run creates a local incremental snapshot and syncs it to your off-site VPS. If your primary server goes down, you restore from the backup VPS.

Database Backups: A Special Case

Filesystem-level backups (like rsync) are not safe for active databases because the database files are constantly being written to. For MySQL/MariaDB, add a pre-backup database dump to your script:

# Database backup (MySQL example)
DB_USER="root"
DB_PASS="your_password"
DB_BACKUP_DIR="$BACKUP_DIR/$DATE/databases"
mkdir -p "$DB_BACKUP_DIR"

databases=$(mysql -u "$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" | grep -Ev "(Database|information_schema|performance_schema)")
for db in $databases; do
    mysqldump -u "$DB_USER" -p"$DB_PASS" --single-transaction "$db" | gzip > "$DB_BACKUP_DIR/$db.sql.gz"
done

For PostgreSQL, use pg_dump with similar logic. The key is to dump databases to flat files before the rsync runs, so the backup captures a consistent snapshot.

Monitoring and Alerts

An automated backup you never check is not a backup — it is a hope. Set up basic monitoring:

  • Check the log file: Add grep -i "error\|fail" /var/log/backup.log to a separate monitoring cron.
  • Email alerts: Use mail or msmtp to send a notification if the backup script fails.
  • Manual verification: Once a month, restore a random file from a backup to confirm the process works end-to-end.

You can also integrate with monitoring tools like Uptime Kuma or Healthchecks.io to ping them after each successful backup — if the ping stops arriving, you know something is wrong.

Restoring from a Backup

Restoration is straightforward because rsync backups are plain directories:

# Restore a single directory from a specific date
rsync -avz /backups/daily/2026-07-10_02-00-00/www/ /var/www/

# Restore everything from the latest backup
rsync -avz /backups/daily/latest/ /

For off-site restoration, reverse the rsync direction:

rsync -avz -e "ssh -i /root/.ssh/backup_key" backupuser@backup-vps-ip:/backups/primary-vps/latest/ /

Conclusion

Setting up automated backups with rsync and cron on your VPS takes less than an hour and gives you peace of mind that your data is protected. The combination of local incremental snapshots and off-site replication covers the two most common failure scenarios: accidental deletion and complete server loss. Before provisioning your backup infrastructure, find VPS plans with enough storage for backups to ensure both your primary and backup VPS have adequate disk capacity for your workload.

Leave a Reply