How to Use rsync for File Synchronization and Backup on Fedora

Use `rsync` with the `-avz` flags to efficiently sync files while preserving permissions, timestamps, and compressing data during transfer.

The scenario

You just copied a directory to an external drive using cp -r, and your laptop freezes. The terminal shows no progress indicator, your cooling fans spin at maximum, and the process refuses to respond to Ctrl+C. You needed a quick backup before a trip, but a recursive copy command locked up your entire session. This happens when cp tries to read and write millions of small files without any pause or delta checking. You need a tool that compares files before moving them, skips unchanged data, and survives network drops or interrupted transfers.

What rsync actually does

rsync solves this by calculating a checksum and size comparison for every file in the source tree before it touches the destination. It only transfers the bytes that actually changed. Think of it like a librarian who checks the catalog before shelving a book. If the book is already on the correct shelf with the correct edition, the librarian walks away. If the edition changed, the librarian swaps only the new pages. This delta-transfer algorithm keeps bandwidth low and prevents the system from choking on directory trees with thousands of tiny configuration files or build artifacts.

The tool also handles interrupted transfers gracefully. If your Wi-Fi drops mid-backup, rsync picks up exactly where it left off on the next run. It does not restart from zero. This behavior makes it the standard for both local disk mirroring and remote server synchronization across the Fedora ecosystem. The algorithm splits files into fixed-size blocks, computes a weak checksum and a strong cryptographic hash for each block, and matches them against the destination. Only mismatched blocks get transferred. This reduces disk I/O and prevents the copy process from monopolizing your system resources.

Run the dry run first. A dry run shows you exactly what rsync plans to do without modifying a single byte on disk. This step prevents accidental data loss when you are still learning the flag combinations.

Running your first sync

Start with a preview command. A dry run shows you exactly what rsync plans to do without modifying a single byte on disk. This step prevents accidental data loss when you are still learning the flag combinations.

Here is how to preview a local home directory backup to an external drive mounted at /mnt/backup.

rsync -avzn /home/youruser/ /mnt/backup/home_backup/
# -a preserves permissions, timestamps, symlinks, and ownership recursively
# -v prints each file as it is processed so you can track progress
# -z compresses data during transfer to save bandwidth and disk I/O
# -n performs a dry run. No files are copied or deleted.
# Trailing slash on source means copy the contents of this directory
# No trailing slash on destination means create this directory if missing

Review the output carefully. You will see lines starting with >f.st for files being transferred, >d.. for directories, and *deleting for files that exist in the destination but not the source. If the list looks correct, remove the n flag to execute the actual sync.

rsync -avz /home/youruser/ /mnt/backup/home_backup/
# Remove -n to perform the actual transfer
# The command will now copy only changed files and update timestamps
# Compression happens in memory, so CPU usage may spike briefly

Add the --delete flag when you want the destination to become an exact mirror of the source. This flag removes files from the backup location that you have deleted from your home directory. Use it cautiously. A typo in the source path with --delete enabled will wipe your backup drive.

rsync -avz --delete /home/youruser/ /mnt/backup/home_backup/
# --delete removes destination files that no longer exist in the source
# Always verify the source path contains a trailing slash to avoid overwriting the backup root
# The flag processes deletions after all transfers complete

Exclude patterns keep your backup lean. Large cache directories and build outputs waste space and slow down the sync process.

rsync -avz --delete \
  --exclude='.cache' \
  --exclude='node_modules' \
  --exclude='.local/share/Trash' \
  /home/youruser/ /mnt/backup/home_backup/
# --exclude takes a glob pattern and skips matching paths entirely
# Multiple excludes are chained with separate flags
# The Trash directory is excluded to prevent backing up deleted files

Run the dry run again after adding exclusions. Confirm the file count dropped to a reasonable number before committing to the real run. Test the command on a small directory first. A single mistyped path can erase hours of work.

Automating with systemd

Manual runs work for occasional backups. Automated runs require a systemd timer. Fedora prefers systemd timers over cron because timers integrate with the journal, handle dependencies correctly, and persist across reboots without extra configuration.

Create a service unit that defines the backup command. Place it in /etc/systemd/system/ so it survives package updates. Never edit files in /usr/lib/systemd/ because those ship with the base system and get overwritten on upgrades.

Here is the service unit configuration for a daily home directory backup.

[Unit]
Description=Daily rsync backup to external drive
After=local-fs.target
# local-fs.target ensures the external drive is mounted before rsync runs
# Network dependencies are unnecessary for local disk backups

[Service]
Type=oneshot
ExecStart=/usr/bin/rsync -avz --delete \
  --exclude='.cache' \
  --exclude='node_modules' \
  /home/youruser/ /mnt/backup/home_backup/
User=youruser
# Type=oneshot tells systemd the service runs once and exits
# Running as youruser preserves file ownership without requiring root
# StandardOutput=journal captures all rsync output for later review

Create a matching timer unit that triggers the service. The timer lives in the same directory with the same base name.

[Unit]
Description=Trigger daily rsync backup

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
# OnCalendar sets the exact daily trigger time
# Persistent=true runs the backup immediately if the system was off at 02:00

[Install]
WantedBy=timers.target
# WantedBy ensures the timer starts automatically on boot
# The timer unit is independent of the service unit lifecycle

Enable and start the timer. The --now flag activates it immediately and schedules it for the next cycle.

sudo systemctl enable --now daily-backup.timer
# enable creates the symlink for boot persistence
# --now starts the timer unit right away
# systemd automatically loads the matching service file when triggered

Check the timer schedule to confirm it loaded correctly.

systemctl list-timers --all | grep daily-backup
# --all shows inactive timers too
# The output shows the next trigger time and elapsed time since last run
# Verify the trigger aligns with your intended backup window

Verify the service logs after the first scheduled run. Use journalctl -xeu daily-backup.service to see the full execution context. The x flag adds explanatory notes from systemd, and e jumps to the end of the log buffer. Most sysadmins type journalctl -xeu <unit> muscle-memory style because it combines state, recent logs, and explanatory context in one view.

journalctl -xeu daily-backup.service
# -x adds explanatory context to log lines
# -e jumps to the end of the journal
# -u filters output to only the daily-backup.service unit
# Check for exit code 0 to confirm successful completion

Reboot before you debug. Half the time the symptom is gone.

Verify the backup

Trust the package manager. Manual file edits drift, snapshots stay. Backups drift too if you never test them. A backup that cannot be restored is just a storage tax.

Compare the source and destination trees to confirm the sync completed correctly. The --dry-run flag combined with --delete shows you what would happen if you ran the command again. If the output is empty, the backup is perfectly synchronized.

rsync -avzn --delete /home/youruser/ /mnt/backup/home_backup/
# Empty output means source and destination are identical
# Any listed files indicate a change that needs to be synced
# Use this command weekly to verify backup integrity

Test a restoration by copying a single file back from the backup location. Verify the permissions and timestamps match the original. If you use version control or important documents, open them after restoration to confirm they are not corrupted. Calculate a checksum on both the original and the restored file to guarantee byte-level accuracy.

sha256sum /home/youruser/documents/report.pdf
sha256sum /mnt/backup/home_backup/documents/report.pdf
# Matching hashes prove the file survived the transfer intact
# Mismatched hashes indicate disk corruption or interrupted writes

Monitor disk space on the backup volume. A full drive causes silent failures when rsync cannot allocate temporary buffers.

df -h /mnt/backup
# Check the Use% column to ensure at least 15% free space remains
# Low space causes write errors and incomplete syncs

Run journalctl first. Read the actual error before guessing.

Common pitfalls and error patterns

The most common mistake is trailing slash confusion. rsync treats the trailing slash as a directive about directory contents. /home/youruser/ means copy everything inside the directory. /home/youruser means copy the directory itself into the destination. Mixing them up creates nested backup folders like /mnt/backup/home_backup/youruser/youruser/. Always keep the trailing slash on the source path.

SELinux blocks unauthorized access to backup directories by default. If your sync fails with a permission denied error, check the context.

rsync: [sender] write failed on "/mnt/backup/home_backup/.config/": Permission denied (13)
rsync error: error in file IO (code 11) at receiver.c(358)

Apply the correct SELinux type to the mount point. Use semanage fcontext to make the change persistent across relabels, then apply it with restorecon. Direct chcon commands disappear after a filesystem relabel or reboot.

sudo semanage fcontext -a -t backup_t "/mnt/backup(/.*)?"
sudo restorecon -Rv /mnt/backup
# semanage updates the SELinux policy database permanently
# restorecon applies the new context to existing files recursively
# SELinux denials show up in journalctl -t setroubleshoot with a one-line summary

Network backups require firewall adjustments. Open the rsync port if you are syncing over SSH or the native rsync daemon. Remember to reload the firewall after every rule change. Otherwise the runtime config and the persistent config diverge.

sudo firewall-cmd --permanent --add-service=rsync
sudo firewall-cmd --reload
# --permanent saves the rule across reboots
# --reload applies the change to the running firewall immediately
# Verify with firewall-cmd --list-services to confirm the rule loaded

Remote syncs over SSH fail silently when SSH keys are not configured. Test the SSH connection manually before adding it to the systemd service. The service will inherit the user's SSH agent environment, but headless timers sometimes miss the agent socket. Use Environment=SSH_AUTH_SOCK=/run/user/1000/keyring/ssh in the service file if authentication fails. Check journalctl -u daily-backup.service for Permission denied (publickey) messages.

Snapshot the system before the upgrade. Future-you will thank you.

When to use rsync versus alternatives

Use rsync when you need delta transfers, resume capability, and exact directory mirroring. Use tar with --listed-incremental when you want a single archive file for long-term cold storage. Use btrfs send/receive or zfs send when your filesystem supports native snapshots and you want block-level efficiency. Use rclone when you are syncing to cloud storage providers like S3, Google Drive, or Backblaze. Stay on rsync for local disk mirroring and SSH-based server backups.

Where to go next