Rsync Source and Destination
Getting the source and destination paths right is the single most important rsync skill. A misplaced trailing slash can copy files into the wrong location, and a wrong remote syntax can fail silently.
Rsync is path-sensitive. The difference between /var/www/html/ (with trailing slash) and /var/www/html (without) produces completely different results. Understanding this rule prevents the most common rsync mistakes.
Basic Syntax
Every rsync command follows this structure:
rsync [OPTIONS] SOURCE DESTINATION
Both SOURCE and DESTINATION can be:
- A local path — absolute (
/var/www/html/) or relative (./app/) - A remote path — specified as
user@host:/path/
Transfer Directions
Rsync supports three transfer patterns:
| Direction | Syntax | Description |
|---|---|---|
| Local → Local | rsync -av /src/ /dest/ | Copy within the same server |
| Local → Remote (push) | rsync -avz /src/ user@server:/dest/ | Send files to a remote server |
| Remote → Local (pull) | rsync -avz user@server:/src/ /dest/ | Download files from a remote server |
Local to Local
Copy files on the same machine — useful for backups to mounted storage:
# Back up web application to local backup directory
rsync -av /var/www/html/ /backup/www/
# Back up database dumps to external drive
rsync -av /var/backups/db/ /mnt/external/db/
# Back up Nginx configuration
rsync -av /etc/nginx/ /backup/config/nginx/
Local to Remote (Push)
Send files from your local machine to a remote server over SSH:
# Deploy application to production
rsync -avz /var/www/html/ user@production:/var/www/html/
# Push database dump to backup server
rsync -avzP /var/backups/db/dump.sql.gz user@backup:/backups/db/
# Sync Docker Compose configs to another server
rsync -av docker-compose.yml user@server2:/opt/docker/
Remote to Local (Pull)
Download files from a remote server to your local machine:
# Pull production logs for analysis
rsync -avz user@production:/var/log/nginx/ ./logs/
# Download remote database dump
rsync -avzP user@server:/var/backups/db/latest.sql.gz ./
# Clone remote configuration for review
rsync -av user@server:/etc/nginx/ ./nginx-config-review/
The Trailing Slash Rule
This is the most critical concept in rsync path handling. It only applies to the source path — the trailing slash on the destination doesn't change behavior.
With Trailing Slash — Copy Contents
rsync -av /var/www/html/ /backup/www/
Result: Copies the contents of /var/www/html/ directly into /backup/www/:
/backup/www/
├── index.php
├── styles.css
└── images/
Without Trailing Slash — Copy the Directory Itself
rsync -av /var/www/html /backup/www/
Result: Copies the html directory itself into /backup/www/, creating a nested folder:
/backup/www/
└── html/
├── index.php
├── styles.css
└── images/
Visual Comparison
| Command | What Gets Created at Destination |
|---|---|
rsync -av app/ /backup/ | /backup/index.php, /backup/assets/ |
rsync -av app /backup/ | /backup/app/index.php, /backup/app/assets/ |
This is the #1 source of rsync mistakes. When in doubt, always use --dry-run first to see exactly what rsync will do:
rsync -av --dry-run /var/www/html/ /backup/www/
Remote Path Syntax
For remote paths, rsync uses the format user@host:/path/. The colon (:) separates the hostname from the path.
SSH-Based Remote Paths
# Standard SSH connection
rsync -avz /local/path/ user@192.168.1.100:/remote/path/
# Using hostname from SSH config
rsync -avz /local/path/ myserver:/remote/path/
# Non-standard SSH port
rsync -avz -e "ssh -p 2222" /local/path/ user@server:/remote/path/
# Specific SSH key
rsync -avz -e "ssh -i ~/.ssh/backup_key" /local/path/ user@backup:/remote/path/
Using SSH Config for Cleaner Commands
If you configure your SSH hosts in ~/.ssh/config, rsync commands become much cleaner:
Host production
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/deploy_key
Host backup-server
HostName 198.51.100.5
User backupuser
IdentityFile ~/.ssh/backup_key
Then your rsync commands simplify to:
# Instead of the full connection string:
rsync -avz -e "ssh -p 2222 -i ~/.ssh/deploy_key" /var/www/ deploy@203.0.113.10:/var/www/
# You can write:
rsync -avz /var/www/ production:/var/www/
rsync -avz /backup/ backup-server:/offsite/
Using SSH config entries is strongly recommended for servers you sync to regularly. It reduces errors, simplifies scripts, and keeps connection details in one manageable place.
Path Types: Absolute vs Relative
| Path Type | Example | Behavior |
|---|---|---|
| Absolute | /var/www/html/ | Always resolves to the same location |
| Relative | ./html/ or html/ | Resolves relative to current working directory |
Recommendations
- Use absolute paths in cron jobs, systemd timers, and scripts — they won't break if the working directory changes
- Relative paths are fine for interactive use where you can verify with
pwd
# Good for scripts and cron — always resolves correctly
rsync -av /var/www/html/ /backup/www/
# Risky in scripts — depends on current directory
rsync -av ./html/ /backup/www/
Practical Examples
Web Application Deployment
# Push PHP/Laravel app to production
rsync -avz --exclude='.env' --exclude='storage/logs/' \
/var/www/myapp/ user@production:/var/www/myapp/
# Deploy static site build
rsync -avz --delete dist/ user@webserver:/var/www/html/
# Sync Python/Django project (exclude virtualenv)
rsync -avz --exclude='venv/' --exclude='__pycache__/' \
/var/www/django-app/ user@staging:/var/www/django-app/
Server Backup
# Full application backup
rsync -avz /var/www/html/ /backup/daily/www/
# Database dumps to remote backup
rsync -avzP /var/backups/mysql/ user@backup-server:/offsite/db/
# Configuration files
rsync -av /etc/nginx/ /backup/daily/config/nginx/
rsync -av /etc/php/ /backup/daily/config/php/
Server Migration
# Migrate entire web directory to new server
rsync -avzP --delete /var/www/ user@new-server:/var/www/
# Migrate database dumps
rsync -avzP /var/backups/ user@new-server:/var/backups/
# Migrate user home directories
rsync -avzP /home/ user@new-server:/home/
Best Practices
| Practice | Why |
|---|---|
Always test with --dry-run | See what will happen before it happens |
| Use absolute paths in scripts | Prevents breakage from directory changes |
| Configure SSH keys | Enables automation without password prompts |
| Use SSH config entries | Simplifies commands and reduces errors |
Verify with pwd before relative paths | Ensures you're in the right directory |
Add -P for large remote transfers | Shows progress and enables resume |
Common Pitfalls
| Pitfall | What Happens | Prevention |
|---|---|---|
| Missing trailing slash on source | Creates nested directory instead of syncing contents | Be explicit: use source/ to copy contents |
| Wrong remote syntax | rsync: connection unexpectedly closed | Check user@host:/path format, verify SSH access |
| Relative paths in cron | Transfer targets wrong directory or fails silently | Always use absolute paths in automated jobs |
Forgetting -e "ssh -p PORT" | Connection refused on non-standard SSH ports | Use SSH config or specify port explicitly |
| Swapping source and destination | Overwrites source with (possibly empty) destination | Double-check direction before running |
Quick Reference
# Local backup (contents only)
rsync -av /var/www/html/ /backup/www/
# Push to remote server
rsync -avz /var/www/html/ user@server:/var/www/html/
# Pull from remote server
rsync -avz user@server:/var/www/html/ /local/backup/
# Non-standard SSH port
rsync -avz -e "ssh -p 2222" /src/ user@server:/dest/
# Using SSH config hostname
rsync -avz /src/ myserver:/dest/
# Always preview first
rsync -av --dry-run /source/ /destination/
What's Next
- Options and Flags — Learn every essential rsync flag in detail
- Operators and Keywords — Advanced path modifiers and filter syntax