Back to Blog
Linux

Automated Backups on Linux with rsync and cron

Too many production servers run with no proper backup system in place. A bad rm -rf, a failing disk, a migration gone wrong. Weeks of work gone. On Linux, rsync and cron are enough to build something solid without relying on third-party tools. This guide covers setup, configuration, and restore, from start to finish.

Pascal SARR
March 30, 2026
6 min read
Automated Backups on Linux with rsync and cron

Prerequisites

  • A Linux system (Ubuntu, Debian, CentOS...)
  • Terminal access with sudo rights
  • rsync installed (available by default on most distributions)
  • A backup destination: local disk, NAS, or remote server over SSH

Check that rsync is available:

hljs bash
rsync --version

If not:

hljs bash
sudo apt install rsync   # Debian/Ubuntu
sudo yum install rsync   # CentOS/RHEL

What rsync does (and why it is the right tool)

rsync synchronizes files between two locations. Its main advantage: it only transfers what has changed since the last run. No full copy every time, which saves both time and bandwidth.

Basic syntax:

hljs bash
rsync [options] source/ destination/

Common options:

OptionDescription
-aArchive mode: preserves permissions, timestamps, symbolic links
-vVerbose output, shows files being processed
-zCompresses data during transfer
--deleteRemoves files at the destination that no longer exist at the source
--excludeExcludes specific files or directories
-e sshUses SSH for remote transfers

Local example:

hljs bash
rsync -av /var/www/mysite/ /mnt/backup/mysite/

Remote example over SSH:

hljs bash
rsync -avz -e ssh /var/www/mysite/ user@192.168.1.100:/backups/mysite/

Setting up the backup directory structure

Splitting backups by frequency makes retention and management easier:

hljs bash
sudo mkdir -p /backups/daily
sudo mkdir -p /backups/weekly
sudo mkdir -p /backups/monthly

This structure lets you apply different retention policies per type. For example, keep 7 days of daily backups but 3 months of monthly ones.


The backup script

Create the file:

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

Content:

hljs bash
#!/bin/bash
 
# ============================================================
#  Automated backup script
#  Author  : Pascal SARR
# ============================================================
 
# --- VARIABLES ---
SOURCE="/var/www/"
BACKUP_ROOT="/backups"
DATE=$(date +"%Y-%m-%d")
DAY_OF_WEEK=$(date +"%u")       # 1=Monday ... 7=Sunday
DAY_OF_MONTH=$(date +"%d")      # 01 to 31
LOG_FILE="/var/log/backup.log"
 
# --- BACKUP TYPE ---
if [ "$DAY_OF_MONTH" = "01" ]; then
  BACKUP_TYPE="monthly"
elif [ "$DAY_OF_WEEK" = "7" ]; then
  BACKUP_TYPE="weekly"
else
  BACKUP_TYPE="daily"
fi
 
DEST="$BACKUP_ROOT/$BACKUP_TYPE/$DATE"
 
mkdir -p "$DEST"
 
echo "[$DATE] Starting $BACKUP_TYPE backup..." >> "$LOG_FILE"
 
rsync -az --delete \
  --exclude="*.log" \
  --exclude="node_modules/" \
  --exclude=".git/" \
  "$SOURCE" "$DEST/" >> "$LOG_FILE" 2>&1
 
if [ $? -eq 0 ]; then
  echo "[$DATE] $BACKUP_TYPE backup OK -> $DEST" >> "$LOG_FILE"
else
  echo "[$DATE] $BACKUP_TYPE backup FAILED" >> "$LOG_FILE"
fi
 
# --- RETENTION ---
find "$BACKUP_ROOT/daily/"   -maxdepth 1 -type d -mtime +7  -exec rm -rf {} \;
find "$BACKUP_ROOT/weekly/"  -maxdepth 1 -type d -mtime +28 -exec rm -rf {} \;
find "$BACKUP_ROOT/monthly/" -maxdepth 1 -type d -mtime +90 -exec rm -rf {} \;
 
echo "[$DATE] Cleanup done." >> "$LOG_FILE"

Make it executable:

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

Including a database

If the project uses a database, dump it before running rsync. Backing up raw data files is useless, only the SQL dump is actually usable at restore time.

PostgreSQL:

hljs bash
pg_dump -U postgres database_name > /tmp/db_backup.sql
rsync -az /tmp/db_backup.sql "$DEST/"

MySQL / MariaDB:

hljs bash
mysqldump -u root -p'password' database_name > /tmp/db_backup.sql
rsync -az /tmp/db_backup.sql "$DEST/"

For MySQL, avoid putting the password in plain text inside the script. Use a ~/.my.cnf file instead:

hljs ini
[mysqldump]
user=root
password=your_password

Test before automating

Run the script manually first:

hljs bash
sudo /usr/local/bin/backup.sh

Then check:

hljs bash
ls /backups/daily/
cat /var/log/backup.log

Do not move to the next step if the test fails. An untested backup script is worth nothing.


Scheduling with cron

cron runs commands on a schedule. To edit root's cron jobs:

hljs bash
sudo crontab -e

To run the backup every day at 2:00 AM:

code
0 2 * * * /usr/local/bin/backup.sh

Syntax reference:

code
┌──────── minute (0-59)
│ ┌────── hour (0-23)
│ │ ┌──── day of month (1-31)
│ │ │ ┌── month (1-12)
│ │ │ │ ┌ day of week (0=Sun, 7=Sun)
│ │ │ │ │
0 2 * * * /usr/local/bin/backup.sh

Some useful examples:

Cron expressionWhen it runs
0 2 * * *Every day at 02:00
0 3 * * 0Every Sunday at 03:00
0 1 1 * *The 1st of every month at 01:00
*/30 * * * *Every 30 minutes

Backup to a remote server

To send backups to a remote server, set up SSH key authentication. The script needs to connect without any user interaction.

Generate the key:

hljs bash
ssh-keygen -t ed25519 -C "backup-key"
# Leave the passphrase empty

Copy the public key to the target server:

hljs bash
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@SERVER_IP

Test the connection:

hljs bash
ssh user@SERVER_IP

If it connects without asking for a password, you are good.

Update the script:

hljs bash
DEST="user@SERVER_IP:/backups/$BACKUP_TYPE/$DATE"
 
rsync -az -e "ssh -i ~/.ssh/id_ed25519" --delete "$SOURCE" "$DEST/"

Email notifications

To receive a report after each run:

hljs bash
sudo apt install mailutils

Add these lines at the end of the script:

hljs bash
STATUS=$(tail -1 "$LOG_FILE")
echo "$STATUS" | mail -s "[BACKUP] Report for $DATE" you@email.com

Restoring a backup

Full directory:

hljs bash
rsync -av /backups/daily/2026-03-27/ /var/www/mysite/

Single file:

hljs bash
cp /backups/daily/2026-03-27/index.html /var/www/mysite/index.html

Database:

hljs bash
# PostgreSQL
psql -U postgres database_name < /backups/daily/2026-03-27/db_backup.sql
 
# MySQL / MariaDB
mysql -u root -p database_name < /backups/daily/2026-03-27/db_backup.sql

Run a restore test at least once in a non-critical environment before you actually need it in an emergency.


Pre-production checklist

  • rsync installed and working
  • Script created at /usr/local/bin/backup.sh and executable
  • Manual test passed without errors
  • Logs readable at /var/log/backup.log
  • Database included in the backup
  • Retention policy active
  • cron job configured
  • Passwordless SSH working (if remote backup)
  • Email notification configured
  • Restore tested at least once

On strategy: The 3-2-1 rule is the standard: 3 copies, on 2 different media, with 1 offsite. It is not overkill, it is what actually holds up when something goes wrong.

Tags

#Linux#Backup#Cron#rsync