>_ SSHDock
FeaturesHow it worksBlogGuidesPricing
Launch Terminal
Back to Journal

How to copy files with rsync over SSH

Ayan Hussain
Sep 12, 2026 · 7 min read

Quick Answer: rsync synchronizes files and directories over SSH, only transferring what has changed since the last run. The basic command is rsync -avz source/ user@host:/destination/. Unlike scp, rsync skips unchanged files, resumes interrupted transfers, and can delete files on the destination that no longer exist at the source.


scp copies files. rsync synchronizes them. That difference matters more than it sounds.

When you use scp to transfer a directory, it copies every file every time. If you run it again after changing three files, it copies everything again. rsync compares the source and destination first, then only transfers what has actually changed. For a directory with 10,000 files where 3 changed, scp transfers all 10,000. rsync transfers 3.

That makes rsync the practical standard for deployments, backups, and anything you run repeatedly.

Quick-reference: most-used rsync flags

| Flag | What it does | |---|---| | -a | Archive mode: preserve permissions, timestamps, symlinks; recurse into directories | | -v | Verbose: print each file as it transfers | | -z | Compress data during transfer (useful on slow connections) | | --delete | Remove files from destination that no longer exist at source | | --dry-run / -n | Show what would happen without making any changes | | --progress | Show per-file transfer progress and speed | | --exclude | Skip files or directories matching a pattern | | --partial | Keep partially transferred files to enable resume | | --checksum | Compare files by checksum rather than timestamp+size | | --bwlimit=N | Limit bandwidth to N kilobytes per second |

How rsync works over SSH

By default, rsync uses SSH as its transport layer. When you specify a remote host in the format user@host:/path, rsync automatically invokes SSH to establish the connection. The same key-based authentication you use for regular SSH applies here — if you can ssh user@host, you can rsync to it.

You do not need any special setup on the remote server beyond the standard SSH daemon. There is no rsync daemon to configure unless you specifically want daemon mode (which is a different use case for LAN transfers without SSH).

Basic syntax

rsync [options] source destination

Remote paths use the user@host:/path format:

# Upload: local → remote
rsync -avz myproject/ user@192.168.1.100:/var/www/myproject/

# Download: remote → local
rsync -avz user@192.168.1.100:/var/www/myproject/ ./myproject/

The trailing slash rule (read this carefully)

rsync's behaviour changes depending on whether you include a trailing slash on the source path. This causes more confusion than any other rsync behaviour.

With a trailing slash — copies the contents:

rsync -avz myproject/ user@host:/var/www/myproject/
# Result: /var/www/myproject/index.html, /var/www/myproject/app.js

Without a trailing slash — copies the directory itself:

rsync -avz myproject user@host:/var/www/
# Result: /var/www/myproject/index.html, /var/www/myproject/app.js

In this specific example the end result is the same. But if /var/www/myproject/ already exists on the server with different contents, the behaviour diverges significantly. The rule of thumb: add a trailing slash to the source when you want to sync the contents, not create a nested directory.

Common real-world uses

Deploying a static website

rsync -avz --delete dist/ user@server:/var/www/mysite/

--delete removes files from the destination that no longer exist in the source. Without it, deleted files accumulate on the server. With it, the server mirrors the source exactly after each sync. Always double-check your paths before using --delete.

Backing up a server to local storage

rsync -avz --progress user@server:/home/ubuntu/ ~/backups/server-home/

--progress shows transfer speed and progress per file — useful for large backups.

Syncing with exclusions

rsync -avz \
  --exclude='node_modules' \
  --exclude='.git' \
  --exclude='.env' \
  --exclude='*.log' \
  myproject/ user@server:/var/www/myproject/

For larger exclusion lists, use a file:

# .rsyncignore
node_modules/
.git/
.env
*.log
.next/cache/
dist/

rsync -avz --exclude-from='.rsyncignore' myproject/ user@server:/var/www/myproject/

Resuming an interrupted transfer

Just run the same command again. rsync checks what arrived and only transfers the rest. Add --partial explicitly for very large files to keep partially received data between runs:

rsync -avz --partial large-database-dump.sql.gz user@server:/backups/

Using a non-standard SSH port

If your server runs SSH on a port other than 22, specify it with -e:

rsync -avz -e "ssh -p 2222" myproject/ user@server:/var/www/myproject/

You can include other SSH flags the same way:

rsync -avz -e "ssh -p 2222 -i ~/.ssh/my-key" myproject/ user@server:/var/www/myproject/

If you have the connection defined in ~/.ssh/config with the port already set, use the alias directly — rsync reads ~/.ssh/config the same way SSH does:

# No -e needed — config handles it
rsync -avz myproject/ my-server:/var/www/myproject/

See our SSH config file guide for how to define server aliases.

Always dry-run before using --delete

Before any destructive rsync command, run it with -n first:

rsync -avzn --delete myproject/ user@server:/var/www/myproject/

-n (equivalent to --dry-run) prints exactly what would be transferred or deleted without touching anything. Develop the habit of dry-running any command with --delete, especially if the destination path might be wrong.

Reusable deployment script

Here is a bash script that wraps rsync for a repeatable, safe deployment workflow. It runs a dry-run first, shows you the changes, then asks for confirmation before deploying:

#!/bin/bash
# deploy.sh — rsync deployment script

set -e

# ── Configuration ──────────────────────────────────────────────────────────────
SOURCE="./dist/"                        # Local build output directory
REMOTE_USER="ubuntu"
REMOTE_HOST="203.0.113.42"
REMOTE_PATH="/var/www/mysite/"
SSH_KEY="$HOME/.ssh/my-server-key"
SSH_PORT="22"

RSYNC_OPTS="-avz --delete --progress"
EXCLUDE_OPTS="--exclude='.DS_Store' --exclude='*.map'"
SSH_OPTS="ssh -i $SSH_KEY -p $SSH_PORT"
# ──────────────────────────────────────────────────────────────────────────────

echo "==> Dry run — showing what would change:"
rsync $RSYNC_OPTS $EXCLUDE_OPTS -n \
  -e "$SSH_OPTS" \
  "$SOURCE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH"

echo ""
read -p "Deploy these changes? [y/N] " confirm
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
  echo "Aborted."
  exit 0
fi

echo "==> Deploying..."
rsync $RSYNC_OPTS $EXCLUDE_OPTS \
  -e "$SSH_OPTS" \
  "$SOURCE" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH"

echo "==> Done. Site deployed to $REMOTE_HOST:$REMOTE_PATH"

Save as deploy.sh, make it executable with chmod +x deploy.sh, and run it with ./deploy.sh.

rsync on Windows

rsync is not included with Windows by default. You have two good options:

Option 1: WSL (Windows Subsystem for Linux) — the recommended approach. If you have WSL installed, open a WSL terminal and use rsync directly:

# Inside WSL terminal
rsync -avz myproject/ user@server:/var/www/myproject/

WSL uses its own SSH agent. Add your private key with ssh-add ~/.ssh/my-key inside the WSL session.

Option 2: cwRsync — a native Windows rsync port available at itefix.net. After installing, use it in PowerShell or Command Prompt with the same syntax. Paths use forward slashes:

rsync -avz /c/Users/YourName/myproject/ user@server:/var/www/myproject/

Option 3: Git Bash — if you have Git for Windows installed, Git Bash includes a version of rsync. Open Git Bash and use the standard Linux syntax.

For most Windows developers, WSL is the cleanest option since it gives you a full Linux environment for all SSH-related tools.

rsync vs scp vs sftp

| | rsync | scp | sftp | |---|---|---|---| | Delta transfer (only changed files) | Yes | No | No | | Resume interrupted transfer | Yes | No | Partial | | Exclude patterns | Yes | No | No | | Delete remote files | Yes (--delete) | No | Yes (manual) | | Interactive browsing | No | No | Yes | | Bandwidth limiting | Yes (--bwlimit) | No | Depends on client | | Windows native support | No (needs WSL/cwRsync) | Yes (OpenSSH) | Yes (OpenSSH) | | Best for | Deployments, backups, recurring syncs | One-off file copies | Interactive file management |

Use scp for quick one-off copies when you know exactly what you are transferring. Use sftp when you need to browse a remote directory interactively. Use rsync for everything you will run more than once. For a detailed comparison of SCP and SFTP, see our SCP vs SFTP guide.

Troubleshooting

"rsync: command not found" on the remote server

rsync must be installed on both machines. On Ubuntu/Debian:

sudo apt install rsync

On CentOS/RHEL:

sudo yum install rsync

"Permission denied" on the destination path

You are trying to write to a directory your SSH user does not own. Either:

# Change ownership on the server
sudo chown -R ubuntu /var/www/mysite

# Or upload to your home directory first, then move with sudo
rsync -avz dist/ ubuntu@server:~/deploy-tmp/
ssh ubuntu@server "sudo mv ~/deploy-tmp/* /var/www/mysite/"

"ssh_exchange_identification: Connection closed"

An SSH error, not an rsync error. Usually means the SSH daemon reset the connection due to a firewall rule, fail2ban ban, or the SSH service not running. Check that your port is open and the SSH daemon is active:

ssh -v user@server  # debug the SSH connection first

Files transfer but timestamps are wrong

Without -a (archive mode), rsync does not preserve timestamps. Make sure you are using -a or at least -t (preserve modification times):

rsync -avz source/ user@host:/dest/
#       ^--- -a includes -t
AH
Written by
Ayan Hussain

Full-Stack Developer and creator of SSHDock. Ayan builds browser-based developer tools and writes about SSH security, Linux server management, and modern web engineering.

More about the author →GitHub
SSHDOCK

Full terminal in your browser. Local credential storage, jump host tunneling, live CPU/memory/disk metrics, and a mobile soft-key bar. Free to use. Built by developers, for developers.

© 2026 SSHDock

GitHub (@ayanhackss)
App
Launch TerminalFeaturesHow it worksBlog & TutorialsChangelogGuides
Cloud Guides
AWS EC2DigitalOcean DropletGoogle Cloud Compute EngineMicrosoft Azure Virtual MachineLinode Compute InstanceVultr Cloud Compute
Legal & Contact
Privacy PolicyTerms of ServiceDisclaimerContact UsAbout Creator
Modern, minimal browser SSH client
TerminalSitemap