Everything from Chapter 7 applies here — the flags, the trailing-slash rule,
the dry-run habit. The only difference is the destination format. Instead of a
local path, you provide user@host:/path and rsync tunnels the
transfer through SSH automatically. No extra configuration needed if you can
already SSH into the machine.
Remote Path Syntax
rsync -avz~/projects/website/philip@server.example.com:/var/www/html/flagslocal sourceuserhostname or IPremote destination
Config aliases work here too. If server is
defined in ~/.ssh/config, you can write
rsync -av src/ server:/var/www/html/ and rsync picks up the
port, user, and key file from your config automatically.
Push vs Pull
Push — local → remote
rsync -avz src/ server:/dest/
Run on your local machine. Sends files to the server. Used for deploys — pushing a built website or updated config to a server.
Pull — remote → local
rsync -avz server:/src/ dest/
Run on your local machine. Fetches files from the server. Used for backups — pulling logs, databases, or a site snapshot to your machine.
Basic Push and Pull
philip@laptop — push to server
# Push local website build to the server's web rootphilip@laptop:~$rsync -avz ~/projects/website/ server:/var/www/html/sending incremental file listindex.htmlcss/style.cssjs/app.jssent 52,480 bytes received 131 bytes 21,044.40 bytes/sectotal size is 247,820 speedup is 4.71# Pull server logs back to your machine for analysisphilip@laptop:~$rsync -avz server:/var/log/nginx/ ~/logs/nginx/sending incremental file listaccess.logerror.logsent 1,823 bytes received 284,910 bytes 82,209.43 bytes/sec
Non-Standard Port — the --rsh Flag
When your SSH server runs on a port other than 22, you can't just add
-P — that means something else to rsync. Instead, use
-e (short for --rsh, remote shell) to pass the port
to SSH directly:
# -e tells rsync which shell command to use for the connection
rsync -avz -e "ssh -p 2222" src/ server:/dest/
# With a specific key file too
rsync -avz -e "ssh -p 2222 -i ~/.ssh/id_work" src/ server:/dest/
# Or just use a config alias — zero extra flags needed
rsync -avz src/ work:/dest/ ← port and key come from ~/.ssh/config
The config alias approach is almost always cleaner. Define
the port, user, and key in ~/.ssh/config once, and every tool
that uses SSH — including rsync, sftp, and scp — inherits those settings
from the alias.
Practical Scenarios
Deploy a website (with delete)
Mirror local build to server — removes files deleted from source
On a shared or metered connection, rsync can be told to cap its transfer rate
so it doesn't swamp the link — useful for background backups running during
working hours:
philip@laptop — capping transfer speed
# --bwlimit takes kilobytes per second (KB/s)philip@laptop:~$rsync -avz --bwlimit=5000 server:/home/philip/ ~/backups/server/sending incremental file listlarge-archive.tar.gz 512,000,000 100% 4.88MB/s 0:01:44 ← capped near 5 MB/s# Common values# --bwlimit=1000 ≈ 1 MB/s (cautious background transfer)# --bwlimit=5000 ≈ 5 MB/s (moderate)# --bwlimit=50000 ≈ 50 MB/s (fast LAN, still capped)
Resuming Interrupted Transfers
If a large transfer is interrupted (connection drop, power cut), rsync can
pick up where it left off rather than starting from scratch — but only for
files that were partially transferred:
# --partial: keep partially transferred files at the destination# so the next run can resume from where it stopped
rsync -avz --partial server:/backups/large-archive.tar.gz ~/backups/
# --partial-dir: store partial files in a hidden staging directory# cleaner than leaving half-transferred files in the destination
rsync -avz --partial-dir=.rsync-partial server:/backups/ ~/backups/
# -P is shorthand for --partial --progress together
rsync -avzP server:/backups/large-archive.tar.gz ~/backups/
-P is the flag to remember for large remote
transfers — it gives you a progress bar and enables resume on
interruption in a single letter.
Preserving Permissions Across Users
When pushing files to a server where Apache or Nginx serves them, you may
need to set permissions correctly rather than copying your local user's
permissions. Two approaches:
# --no-perms --no-owner --no-group: don't copy metadata — let the
# server's umask and ownership apply (simplest for web deploys)
rsync -rltvz --no-perms --no-owner --no-group \
~/projects/site/dist/ server:/var/www/html/
# --chmod: force specific permissions regardless of source
rsync -avz --chmod=D755,F644 \
~/projects/site/dist/ server:/var/www/html/
# D755 = directories get 755, F644 = files get 644# --chown: force ownership (requires root or sudo on the server)
rsync -avz --chown=www-data:www-data \
~/projects/site/dist/ server:/var/www/html/
A Complete Deploy Script
Putting it all together — a simple shell script that builds and deploys a
website, with a dry-run mode and a production mode:
#!/bin/bash
# deploy.sh — build and rsync to server
# Usage: ./deploy.sh (dry run)
# ./deploy.sh --live (real deploy)
SERVER="server"# ssh config alias
REMOTE_DIR="/var/www/html/"
LOCAL_DIR="$HOME/projects/website/dist/"
RSYNC_OPTS="-avz --delete
--exclude='.git'
--exclude='node_modules/'
--exclude='*.map'"# Build first
echo "Building..."
npm run build || { echo "Build failed"; exit 1; }
# Deploy
if [[ "$1" == "--live" ]]; then
echo "Deploying to $SERVER..."
rsync $RSYNC_OPTS "$LOCAL_DIR""$SERVER:$REMOTE_DIR"
echo "Done."
else
echo "Dry run (pass --live to deploy):"
rsync $RSYNC_OPTS --dry-run"$LOCAL_DIR""$SERVER:$REMOTE_DIR"
fi
philip@laptop — using the deploy script
# Preview what would be deployedphilip@laptop:~$./deploy.shBuilding...✓ Built in 3.4sDry run (pass --live to deploy):index.htmlcss/style.css(DRY RUN)# Happy with the preview — deploy for realphilip@laptop:~$./deploy.sh --liveBuilding...✓ Built in 3.4sDeploying to server...index.htmlcss/style.cssDone.
Common Errors and Fixes
troubleshooting rsync over SSH
# Error: rsync: failed to connect — wrong host or SSH not listeningrsync: [Receiver] failed to connect to server.example.com: Connection refused→ check: ssh server (does plain SSH work?)# Error: permission denied on the remote pathrsync: [sender] change_dir "/var/www/html" failed: Permission denied (13)→ fix: ensure your user owns or has write access to the remote path ssh server "sudo chown philip:philip /var/www/html"# Error: rsync not found on the remote machinebash: rsync: command not foundrsync: connection unexpectedly closed→ fix: install rsync on the server ssh server "sudo apt install rsync"# Warning: skipping non-regular file — symlink in sourceskipping non-regular file "current"→ add -l to preserve symlinks, or --copy-links to follow them# Error: port 22 blocked by firewall at work→ use -e to connect via port 443 if sshd listens there rsync -avz -e "ssh -p 443" src/ server:/dest/
Quick Reference
Command / flag
What it does
rsync -avz src/ server:/dest/
Push local to remote (archive + verbose + compress)
rsync -avz server:/src/ dest/
Pull remote to local
rsync -avzn src/ server:/dest/
Dry run — preview without transferring
rsync -avz --delete src/ server:/dest/
Mirror — delete remote files that no longer exist locally
-e "ssh -p 2222"
Use non-standard SSH port
-e "ssh -p 22 -i ~/.ssh/id_work"
Specify SSH key inline
--bwlimit=5000
Cap transfer at 5 MB/s (value in KB/s)
-P
Progress bar + resume partial transfers
--partial-dir=.rsync-partial
Store partial files in a staging directory
--chmod=D755,F644
Force directory/file permissions at destination
--no-perms --no-owner --no-group
Don't copy metadata — let server's umask apply
--exclude='node_modules/'
Skip a directory by name
--exclude-from=.rsyncignore
Load exclude patterns from a file
Next — Chapter 9: Automating rsync.
Running rsync by hand is fine for one-off transfers. For backups and deploys
you want them to happen automatically — on a schedule, without prompts, and
with some kind of rotation so you don't fill your disk. Chapter 9 covers
cron jobs, unattended key-based auth, backup rotation strategies, and
building a reliable home-server backup routine.