Backups with rsync
rsync copies directories fast and efficiently by transferring only the changed data — locally to another disk or to a remote server over SSH. This article covers the key flags -a, -v, -z, --delete, backing up a directory to another ForestsNet Cloud server, and a wrapper script you can schedule with cron.
What you'll need
- An ForestsNet Cloud VPS on Ubuntu 22.04/24.04, Debian 11/12, or AlmaLinux/Rocky Linux 8/9 — the source server you're backing up.
- A second server (for example, another ForestsNet Cloud VPS) or a separate disk/partition on the same server — somewhere to keep the copies.
- SSH access as
rootor a user withsudorights: to the source server, and — for the remote case — to the destination server too. - The
rsyncpackage — usually already on the system, but worth checking (Step 1).
ℹ️ Tip: the commands below are given with sudo. If you're already connected as root and there's no sudo on the system, run the same commands without the sudo prefix.
Key flags: -a, -v, -z, --delete
The -a (--archive) flag is the most important one: it's shorthand for -rlptgoD. rsync recurses into directories (-r), preserves symlinks as symlinks (-l), permissions (-p), modification times (-t), group (-g) and owner (-o), and device/special files (-D). Note that -a does not preserve ACLs (-A), extended attributes (-X), access times (-U), creation times (-N), or hard links (-H) — add those separately if you need them.
-v(--verbose) — prints the files being transferred and a short summary at the end. Use-vvfor more detail.-z(--compress) — compresses data on the fly before sending it over the network. Noticeably helpful on slow or bandwidth-limited links; on a fast link between servers in the same data center the gain is usually small, and close to zero for already-compressed files (archives, video, images).--delete— removes files on the receiving side that no longer exist on the sending side, so the copy stays an exact mirror instead of accumulating files for years.
⚠️ Important: --delete is a potentially dangerous flag — a wrong path in the command and you can delete the wrong thing in the wrong place. Before the first run of a command with --delete, always check it with -n (--dry-run) — rsync will show what would happen without changing anything on disk.
ℹ️ Tip: pay attention to the trailing slash on the source path. rsync -avz /var/www/ /mnt/backup/ copies the contents of the /var/www directory into /mnt/backup. But rsync -avz /var/www /mnt/backup (no trailing slash on the source) creates an extra nested www directory inside /mnt/backup. Keep the trailing slash on the source path — and, for consistency, on the destination too — the same in every command and in the script: it's the most common mistake when getting started with rsync.
Step 1. Make sure rsync is installed
rsync is needed on both sides — the source server and the server where the copies are kept.
Check whether it's already there:
rsync --versionIf the command isn't found, install the package.
Ubuntu / Debian:
sudo apt update
sudo apt install -y rsyncAlmaLinux / Rocky Linux:
sudo dnf install -y rsyncRepeat the check (and the install, if needed) on both servers.
Step 2. Set up SSH key access to the destination server
The wrapper script (Step 5) and cron (Step 6) will run rsync unattended — password login won't work here, you need SSH key authentication with no passphrase. Run the commands below on the source server, as the user the backup will run as (usually root).
- Generate a dedicated key for backups:
ssh-keygen -t ed25519 -f /root/.ssh/id_rsync_backup -N ""-N "" sets an empty passphrase — without it, a script launched from cron has no way to type the key's password.
⚠️ Important: a passphrase-less key is a deliberate trade-off for automation. Use it only for this task; on the destination server, where possible, set up a separate user that only has access to the backup directory.
- Copy the public key to the destination server —
backupin the example below can be any existing user on the destination server (includingroot) that has write access to the target directory:
ssh-copy-id -i /root/.ssh/id_rsync_backup.pub [email protected]The first time, ssh-copy-id will ask for a password (or use your current key) to log in to [email protected] — that's expected; after that, the new key logs in without a password.
Replace [email protected] with the user and address of your own destination server.
- Confirm the connection works without a password:
ssh -i /root/.ssh/id_rsync_backup [email protected] "echo OK"If you get OK back with no password prompt, you're ready for rsync itself.
Step 3. Local directory sync
The local option is useful when you just need to mirror a directory to another disk, partition, or mounted network storage on the same server — no SSH, no second server.
Start with a dry run using -n (--dry-run), which changes nothing on disk:
rsync -avzn --delete /var/www/ /mnt/backup/www/Check the output, and if it looks right, run it for real (without -n):
rsync -avz --delete /var/www/ /mnt/backup/www/ℹ️ Tip: if /mnt/backup/www/ doesn't exist yet, create it first: mkdir -p /mnt/backup/www. Newer rsync versions can create missing directories on the fly with the --mkpath flag, but a directory you created in advance works just as reliably on any version.
Step 4. Back up a directory to another server over SSH
The difference from the local case is that the destination is given as user@host:path, with a colon. Start with the same kind of dry run:
rsync -avzn --delete -e "ssh -i /root/.ssh/id_rsync_backup" /var/www/ [email protected]:/backup/myserver/www/Check the output, then run it for real:
rsync -avz --delete -e "ssh -i /root/.ssh/id_rsync_backup" /var/www/ [email protected]:/backup/myserver/www/What's new compared to the local case:
[email protected]:/backup/myserver/www/— the user, the IP (or domain) of the destination server, and the path on it.-e "ssh -i /root/.ssh/id_rsync_backup"— explicitly tells rsync to use SSH with the specific key file from Step 2. If the key were saved under a standard name (~/.ssh/id_ed25519orid_rsa) on the standard port 22, you could skip-eentirely — modern rsync already uses SSH by default.-eis needed when SSH needs non-default settings: a specific key file, or a non-standard port (-e "ssh -p 2222 -i /root/.ssh/id_rsync_backup").
As with the local case, create the destination directory in advance:
ssh -i /root/.ssh/id_rsync_backup [email protected] "mkdir -p /backup/myserver/www"ℹ️ Tip: this reuses the same SSH port you already use to administer the destination server, so there's nothing new to open in the firewall. If you're setting up ufw on the destination server from scratch, don't forget to allow SSH itself first — see Firewall with ufw.
Step 5. A wrapper script for regular backups
Instead of typing the command by hand, wrap it in a script: with logging, error handling, and protection against overlapping runs if the previous backup hasn't finished yet.
Create the file:
sudo nano /usr/local/sbin/rsync-backup.shContents:
#!/usr/bin/env bash
set -euo pipefail
# Source directory and backup destination
SRC="/var/www/"
DEST_USER="backup"
DEST_HOST="203.0.113.10"
DEST_PATH="/backup/myserver/www/"
SSH_KEY="/root/.ssh/id_rsync_backup"
LOG_FILE="/var/log/rsync-backup.log"
LOCK_FILE="/var/lock/rsync-backup.lock"
# Prevent overlapping runs if the previous backup is still going
exec 9>"$LOCK_FILE"
if ! flock -n 9; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Previous backup is still running, exiting" >> "$LOG_FILE"
exit 1
fi
echo "$(date '+%Y-%m-%d %H:%M:%S') Backup started" >> "$LOG_FILE"
if rsync -avz --delete -e "ssh -i ${SSH_KEY}" "$SRC" "${DEST_USER}@${DEST_HOST}:${DEST_PATH}" >> "$LOG_FILE" 2>&1; then
echo "$(date '+%Y-%m-%d %H:%M:%S') Backup finished OK" >> "$LOG_FILE"
else
echo "$(date '+%Y-%m-%d %H:%M:%S') Backup FAILED" >> "$LOG_FILE"
exit 1
fiMake the script executable and root-only:
sudo chmod 700 /usr/local/sbin/rsync-backup.shRun it by hand and check the log:
sudo /usr/local/sbin/rsync-backup.sh
tail -n 20 /var/log/rsync-backup.log✅ Done: if the log shows "Backup finished OK" and the files showed up on the destination server, the script is ready to be scheduled.
ℹ️ Tip: replace SRC, DEST_USER, DEST_HOST, DEST_PATH, and SSH_KEY at the top of the script with your own values — you don't need to touch the rest of the logic (logging, the overlap lock, error handling).
Step 6. Schedule the script with cron
Open root's crontab (the same user the SSH key was set up for in Step 2):
sudo crontab -eAdd a line — for example, a daily run at 02:30:
30 2 * * * /usr/local/sbin/rsync-backup.shThe script logs to /var/log/rsync-backup.log on its own, so redirecting output in crontab too isn't necessary.
For a detailed look at cron syntax, other scheduling options (hourly, specific weekdays), and common mistakes, see Automating backups with cron.
What's next
rsync with --delete only keeps the latest state of a directory, not versions or history: if a file quietly got corrupted a while back, there's no older copy left — the same --delete removed it there too. For versioned, encrypted, deduplicated backups, see the article on restic: Backups with restic.