Automate file backups on VPS with rsync

Automate File Backup on VPS with rsync: Complete Guide

June 2026 · 8 min read

rsync is a backup tool built into every Linux system, capable of transferring files between local-to-local, local-to-remote, and remote-to-remote locations via SSH. Its strength lies in incremental sync — it only transfers files that have actually changed, not the entire dataset every time. This makes backups extremely fast and bandwidth-efficient.

Basic rsync Commands

# Install (usually pre-installed)
apt install rsync -y

# General syntax
rsync [options] source/ destination/

# Common options
-a   archive mode (recursive + preserve permissions/timestamps)
-v   verbose (show files being synced)
-z   compress during transfer
-P   progress bar + resume partial transfers
--delete  delete destination files that no longer exist in source
-n / --dry-run  test without making actual changes
The trailing slash matters: rsync -a /data/ (with /) = copy contents of the folder · rsync -a /data (no /) = copy the folder itself. This is a common mistake — always do a dry-run first.

Backing Up Local Files on Your VPS

# Backup /var/www to /backup/www
rsync -av --delete /var/www/ /backup/www/

# Test first with dry-run
rsync -avn --delete /var/www/ /backup/www/

# Backup multiple folders at once
rsync -av /var/www/ /etc/nginx/ /etc/php/ /backup/

Backing Up to a Remote Server Over SSH

rsync uses SSH to transfer data, meaning all transfers are automatically encrypted:

# Backup from VPS → remote server
rsync -avz -e ssh /var/www/ [email protected]:/backup/vps1/www/

# Specify SSH port and key
rsync -avz -e "ssh -p 2222 -i ~/.ssh/backup_key" \
  /var/www/ [email protected]:/backup/vps1/

# Backup in reverse: remote → VPS (pull)
rsync -avz [email protected]:/data/ /local/restore/

Setting Up SSH Key for Automated Backups

Use SSH keys instead of passwords so cron can run without manual input:

# Generate SSH key specifically for backups (no passphrase)
ssh-keygen -t ed25519 -C "backup@vps1" -f ~/.ssh/backup_key -N ""

# Copy public key to backup server
ssh-copy-id -i ~/.ssh/backup_key.pub -p 22 [email protected]

# Test that login works without a password
ssh -i ~/.ssh/backup_key [email protected] echo "OK"
Restrict backup key permissions: On the backup server, add command="rsync --server ...",restrict before the public key in ~/.ssh/authorized_keys so this key can only run rsync. This prevents key leaks from being catastrophic.

Creating a Backup Script

nano /usr/local/bin/backup.sh
#!/bin/bash
set -euo pipefail

DATE=$(date +%Y-%m-%d)
LOG="/var/log/backup.log"
REMOTE="[email protected]"
KEY="$HOME/.ssh/backup_key"

log() { echo "$(date '+%F %T') $*" | tee -a "$LOG"; }

log "=== Backup started ==="

# Backup web files
rsync -az --delete \
  -e "ssh -i $KEY -o StrictHostKeyChecking=no" \
  /var/www/ "${REMOTE}:/backup/www/" && log "www OK" || log "www FAILED"

# Backup Nginx config
rsync -az --delete \
  -e "ssh -i $KEY -o StrictHostKeyChecking=no" \
  /etc/nginx/ "${REMOTE}:/backup/nginx/" && log "nginx OK" || log "nginx FAILED"

# Backup MySQL (if present)
if command -v mysqldump &>/dev/null; then
  mysqldump --all-databases --single-transaction \
    > /tmp/all_dbs_${DATE}.sql
  gzip /tmp/all_dbs_${DATE}.sql
  rsync -az \
    -e "ssh -i $KEY -o StrictHostKeyChecking=no" \
    /tmp/all_dbs_${DATE}.sql.gz "${REMOTE}:/backup/mysql/"
  rm /tmp/all_dbs_${DATE}.sql.gz
  log "mysql OK"
fi

log "=== Backup done ==="
chmod +x /usr/local/bin/backup.sh
# Test it
/usr/local/bin/backup.sh

Set Up Cron for Automated Backups

crontab -e
# Backup every day at 2 AM
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Backup every hour (web files only)
0 * * * * rsync -az --delete -e "ssh -i ~/.ssh/backup_key" \
  /var/www/ [email protected]:/backup/www/ >> /var/log/backup.log 2>&1

Incremental Backups with --link-dest (Snapshot Style)

This technique uses hard links to make it appear as though you have a full backup every day, but only disk space for changed files is actually used:

#!/bin/bash
DEST="/backup"
DATE=$(date +%Y-%m-%d)
PREV=$(ls -d ${DEST}/[0-9]* 2>/dev/null | sort | tail -1)

rsync -az --delete \
  ${PREV:+--link-dest="$PREV"} \
  /var/www/ "${DEST}/${DATE}/"

# Keep only the last 30 days
find "$DEST" -maxdepth 1 -type d -name '[0-9]*' | \
  sort | head -n -30 | xargs rm -rf

Checking Backup Logs

# View recent logs
tail -50 /var/log/backup.log

# Check if backup succeeded today
grep "$(date +%F)" /var/log/backup.log | grep "done"

# Check disk usage on remote
ssh [email protected] du -sh /backup/

rsync is a powerful and reliable backup tool for VPS — no additional software needed, uses existing SSH, and incremental sync means daily backups take only seconds after the first full sync. The key is setting up alerts for backup failures and regularly testing restores.

Verify Backup is Working Correctly

# Test rsync with dry-run first
rsync -avzn --delete /var/www/ [email protected]:/backup/www/
# -n = dry-run (no actual changes, but shows what would happen)

# Check if backup cron is running
grep "backup" /var/log/syslog | tail -10

# View recent backup logs
tail -30 /var/log/backup.log

# Check backup size on remote
ssh [email protected] "du -sh /backup/ && ls -lt /backup/ | head"
Test restores regularly: Setting up rsync without ever testing a restore is like having no backup at all — try restoring at least one file from backup at least once a month to confirm it actually works.

Common Issues & Troubleshooting

Error: "Permission denied (publickey)" with backup key

The backup SSH key must be authorized on the destination server, and the key path must be correct:

# Test the key directly
ssh -i ~/.ssh/backup_key -o BatchMode=yes [email protected] echo "OK"
# If it fails — check that the public key is in authorized_keys on destination

# Check key file permissions
chmod 600 ~/.ssh/backup_key
chmod 644 ~/.ssh/backup_key.pub

# See SSH debug output
ssh -v -i ~/.ssh/backup_key [email protected]

Error: rsync deletes files on destination that you didn't want deleted (--delete issue)

This usually comes from wrong trailing slashes or incorrect paths — always do a dry-run first:

# Always dry-run before using --delete for real
rsync -avn --delete /var/www/ /backup/www/

# If you accidentally deleted files and have --link-dest backups, restore is possible
ls /backup/
# Find the snapshot from the day before that still has the files

Error: "connection timed out" or "Connection refused" to backup server

Check that the SSH port is open on the destination server's firewall and your source IP isn't blocked:

# Test SSH to destination first
ssh -p 22 [email protected]

# If timeout — check firewall on destination
# If connection refused — check SSH daemon is running

Error: backup succeeds but no new files appear on destination

This might be a path issue or rsync comparing timestamps and finding no changes:

# Use --checksum instead of timestamp comparison
rsync -avz --checksum /var/www/ [email protected]:/backup/www/

# Check verbose output to see which files were sent
rsync -avzv /var/www/ [email protected]:/backup/www/ 2>&1 | head -30

Error: cron backup doesn't run even though manual execution works

Cron has a different environment than your shell — it doesn't have the same PATH or environment variables:

# Use full paths for all commands in backup script
/usr/bin/rsync -avz ...
/usr/bin/ssh -i /root/.ssh/backup_key ...

# Or add PATH to crontab
0 2 * * * PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin /usr/local/bin/backup.sh

Frequently Asked Questions

How is rsync different from cp?

cp copies every file every time, while rsync first checks size and timestamp and only transfers files that have actually changed. Plus, rsync can transfer over SSH, making it secure and bandwidth-efficient. After the first full sync, daily backups take only seconds.

Is the --delete flag dangerous?

--delete removes destination files that no longer exist in the source. If you call it correctly, it's excellent and keeps your backup a true mirror of the source. But if the path is wrong, it could delete data unintentionally. Always dry-run with -n before running it manually, and keep multiple snapshots with --link-dest as a safety net.

How do I restore an entire server?

rsync can restore just as well — swap source and destination: rsync -avz [email protected]:/backup/www/ /var/www/ For a full server restore, restore piece by piece (files, database, config) and test each part before going live.