What rsync is
rsync is a utility for efficiently syncing files between servers. It transfers only the parts of files that changed, which makes it ideal for incremental backups and for moving site files between hosting providers without wasting bandwidth.
An order for transferring site files with no downtime
- Run a first full sync of files to the new server while the old site keeps running.
- Repeat the sync right before the switch — it copies only the files that changed since the first pass.
- Check the new server through the
hostsfile, without touching the domain's DNS. - Switch the domain and immediately run a final
rsyncwith the--deleteflag, to remove files deleted on the old server during the move. - Keep the old server reachable for a few more days in case a rollback is needed.
The real downtime with this order is only for switching DNS or the web server, usually one to five minutes, not the hours a full file copy takes.
Key rsync flags
| Flag | Description |
|---|---|
| -a | Archive mode (recursion + permissions + metadata) |
| -v | Verbose output |
| -z | Compression during transfer |
| --delete | Delete files on the destination that no longer exist on the source |
| --exclude | Exclude files/folders |
| --dry-run | A test run with no real changes |
| --link-dest | Hard links for incremental backups |
Basic sync between servers
# Sync a folder to a remote server
rsync -avz --delete /var/www/mysite/ user@backup-server:/backups/mysite/
# With exclusions
rsync -avz --delete --exclude='wp-content/cache/' --exclude='*.log' /var/www/mysite/ user@backup-server:/backups/mysite/
Checking the result without a real transfer
Before the first run on real data, always run the command with the --dry-run flag — rsync shows the list of files it would copy or delete without touching anything on disk:
rsync -avz --delete --dry-run /var/www/mysite/ user@backup-server:/backups/mysite/
Incremental backups with --link-dest
#!/bin/bash
DATE=$(date +%Y-%m-%d_%H%M)
DEST=/backups/mysite
LATEST=$DEST/latest
rsync -avz --delete --link-dest=$LATEST /var/www/mysite/ $DEST/$DATE/
# Update the pointer to the latest backup
rm -f $LATEST
ln -s $DEST/$DATE $LATEST
--link-dest creates hard links to unchanged files instead of copying them — each snapshot looks like a full copy but only actually uses disk space for files that changed.
Comparing file transfer scenarios with rsync
| Scenario | Command | Trait |
|---|---|---|
| One-off transfer of all files | rsync -avz source destination | no deletion on the destination |
| Mirroring (exact match) | rsync -avz --delete source destination | removes extra files on the destination |
| Daily backup with history | rsync -avz --link-dest=previous | saves disk space |
Common errors
| Message | Cause and fix |
|---|---|
| rsync: connection unexpectedly closed | The SSH connection dropped, or the remote server ran out of memory — retry with the --partial flag to resume |
| Permission denied (publickey) | The SSH key is not added on the remote server — copy it with ssh-copy-id |
| rsync error: some files/attrs were not transferred | Not enough rights to change file ownership — run with sudo on both sides, or drop -o -g from archive mode |
Rollback in case of problems
- Do not delete files on the old server until you confirm the new one works normally for at least a full day.
- If files turn out to be missing after the switch, run a reverse sync from the old server to the new one, without
--delete. - Find the cause of the mismatch (usually an
--excludepattern that caught files it should not have) and repeat the transfer.
When rsync does not fit
--delete removes files on the destination without warning. Always use --dry-run the first time. For databases, rsync copies the DBMS file "as is" and can transfer it in an inconsistent state if the database server is running during the copy — databases need mysqldump or pg_dump, not an rsync of the data files.
Automating with cron
# Daily backup at 3 AM
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
Checking the result
- Compare file counts:
find /var/www/mysite | wc -lshould match on the source and destination. - Check the checksum of a few key files:
md5sumon both sides. - Open the site through the hosts override and confirm all static files load without a 404.
Migrating a database that needs to stay in sync with the files is covered in migrating a MySQL database, and a general post-move site check is in post-migration check.