If you manage a VPS, you're moving files around constantly. We are talking about backups, deployments, and migrations—these tasks happen daily. And if you're doing it wrong, you're wasting both time and bandwidth. Two tools handle this work: rsync for syncing directories and large transfers, and scp for quick one-off file copies.
Starting with OpenSSH 9.0, the scp command still looks and feels the same, but under the hood, it now relies on the SFTP protocol by default instead of the legacy SCP protocol. This change was introduced because the older protocol had security flaws like CVE-2019-6111. If you really need the old behavior, you can force it with the -O flag, but in almost every case, it is better to use the new default for security reasons.
We will show you exactly how to use both tools. By the end of this guide, you’ll know which commands to use, when to use them, and how to avoid the most common mistakes.
Understanding Rsync vs SCP
Before jumping into commands, you need to understand what makes these tools different.
Rsync: the smart file syncing tool
Rsync (Remote Sync) only transfers what has changed. It uses a delta transfer algorithm that compares files and sends only the differences. If you sync a 10GB directory and only 50MB has changed, Rsync will transfer just 50MB instead of the full 10GB.
This makes Rsync ideal for backups that run repeatedly, deployments where only a few files change, and any situation where you’re syncing the same directories multiple times. The first sync takes longer because everything needs to be copied, but every sync after that is significantly faster.
SCP: the quick copy command
SCP (Secure Copy Protocol) is much simpler. You tell it to copy a file from point A to point B, and it copies the entire file. There is no comparison process, no delta transfer, just a straightforward copy operation.
This makes SCP the right choice for one-time transfers when you just need to grab a log file or copy a configuration file quickly. The syntax is simple, and you don’t need to worry about additional flags or options.
The key difference is this: Rsync becomes more efficient with repeated use, while SCP remains the same speed every time because it always copies the entire file.
Basic rsync usage: syncing files
Start with the most common Rsync operations you’ll use daily.
Upload local files to your VPS
“`bash title="Upload a local directory to the VPS" rsync -avz /local/directory/ username@your-vps-ip:/remote/directory/
Same flags, same logic, just a different direction.
### Sync and delete files (mirror directories)
Sometimes you want the destination to exactly match the source, including deleting files that no longer exist in the source. This is called **mirroring**.
```bash title="Mirror a local directory to the server"
rsync -avz --delete /local/directory/ user@server:/remote/directory/
The --delete flag removes any files from the destination that don’t exist in the source. This keeps your backups clean and prevents old files from piling up.
Critical warning: This permanently deletes files. If you accidentally swap your source and destination, you could wipe out important data. Always double-check your paths before using --delete.
Test with dry run before executing
Before running any rsync command that modifies files, especially one using --delete, test it first with a dry run.
“`bash title="Preview a destructive rsync mirror" rsync -avz –delete –dry-run /local/ user@server:/remote/
The `--dry-run` flag shows you exactly what would happen without actually doing it. Rsync prints every action it would take. If something looks wrong, you catch it before any damage happens.
Always use a dry run before major syncs. This simple habit has saved teams from accidentally deleting production data many times.
### Exclude files and directories
You may not always want to sync everything. For example, you might want to skip log files, temporary directories, or dependency folders.
### Exclude specific patterns
```bash title="Exclude logs temp and cache directories"
rsync -avz --exclude '*.log' --exclude 'temp/' --exclude 'cache/' /local/ user@server:/remote/
Exclude using a file for cleaner commands
Create a text file called exclude-list.txt with your patterns:
“`text title="Define reusable rsync exclusion patterns" .log .tmp node_modules/ .git/ temp/ cache/
Then reference that file:
```bash title="Sync with an exclusion file"
rsync -avz --exclude-from='exclude-list.txt' /local/ user@server:/remote/
This approach is cleaner when you have many exclusions. You maintain the list in a file and reuse it across different Rsync commands.
Resume interrupted transfers
Your internet connection might drop halfway through transferring a 50GB file. Frustrating, but with Rsync, you don’t start over from the beginning.
“`bash title="Resume a large file transfer with progress" rsync -avz –partial –progress /local/large-file.tar.gz user@server:/remote/
- `--partial`: Keeps partially transferred files so Rsync can resume them.
- `--progress`: Shows transfer progress in real time so you know how much is complete and how much remains.
When you run the command again after your connection is restored, Rsync picks up where it left off instead of starting from zero.
Show detailed transfer statistics with `--info=progress2`:
```bash title="Show overall rsync transfer progress"
rsync -avz --info=progress2 /local/ user@server:/remote/
The --info=progress2 flag shows overall transfer statistics instead of per-file updates. When syncing thousands of files, this prevents the screen from filling with line-by-line updates and gives you a much clearer ETA for the entire operation.
Pro tip: Use -P as a shorthand for --partial --progress.
“`bash title="Transfer a file with the rsync -P shortcut" rsync -avzP /local/file user@server:/remote/
- `-P`: Combines `--partial` and `--progress`. Keeps partial files and shows progress in real time.
## Advanced rsync techniques
### Atomic transfers
Atomic transfers prevent partially uploaded files from being visible during a sync. This is important when deploying to a live server so visitors don’t access incomplete files.
```bash title="Delay file updates until the sync completes"
rsync -avz --temp-dir=/tmp --delay-updates /local/ user@server:/remote/
This command writes files to /tmp first, then with --delay-updates, rsync moves all completed files to their final locations at the end of the sync (not during). This ensures files appear atomically: they are either fully present or not present at all.
rsync:// protocol
In addition to rsync over SSH, rsync can also connect using the native rsync:// protocol. This is mainly used for accessing public mirrors (for example, downloading Linux distributions).
Example:
“`bash title="Download an rsync module from a public mirror" rsync rsync://mirror.example.com/repo/ /local/repo/
For most production use cases, SSH is recommended because it is more secure and flexible.
## Basic SCP usage: quick file copies
SCP is simpler than rsync. You tell it what to copy and where to put it; that is it.
Upload a single file:
```bash title="Upload one file with SCP"
scp /local/file.txt username@your-vps-ip:/remote/path/
Download a single file:
“`bash title="Download one file with SCP" scp username@your-vps-ip:/remote/file.txt /local/path/
To copy entire directories (with all subdirectories and files), add the `-r` flag:
```bash title="Upload a directory recursively with SCP"
scp -r /local/directory username@your-vps-ip:/remote/path/
For faster transfers of text files, source code, and uncompressed data, use the -C flag:
“`bash title="Upload a compressed SCP transfer" scp -C /local/file.txt user@server:/remote/
- `-C` (compression): Compresses data during transfer.
- Best for text files, source code, and other uncompressed data.
- Not useful for already-compressed files like .zip, .tar.gz, or .jpg.
We typically use scp when we need to quickly grab a log file or a small config file:
```bash title="Download the Nginx error log with SCP"
scp admin@web-server:/var/log/nginx/error.log ~/Desktop/
Fast, simple, done. For repeated or large transfers, rsync is the better option.
Need reliable VPS storage for rsync and SCP workflows?
Use Virtarix VPS infrastructure with root access, NVMe storage, snapshots, backups, and predictable room for file transfers, sync jobs, and backup routines.
Common transfer operations
Transferring files between remote servers
Sometimes you need to move files directly between two VPS instances.
With scp:
“`bash title="Copy a file between two remote servers with SCP" scp username1@source-server:/path/to/file username2@destination-server:/path/
Your local machine acts as the intermediary – data flows through your SSH connection.
With rsync:
```bash title="Attempt a remote-to-remote rsync transfer"
rsync -avz username1@source-server:/path/ username2@destination-server:/path/
For large transfers, SSH into one server first and run rsync from there (avoids routing through your home internet):
“`bash title="Run rsync from the source server" ssh username1@source-server rsync -avz /path/ username2@destination-server:/path/
### Using SSH keys instead of passwords
Typing passwords for every transfer wastes time. SSH keys solve this problem.
Generate a key pair:
```bash title="Generate an Ed25519 SSH key"
ssh-keygen -t ed25519 -C "your-email@example.com"
This command generates two files:
-
id_ed25519→ your private key (keep this secret) -
id_ed25519.pub→ your public key (share this with servers).
Never share your private key (id_ed25519) with anyone. Treat it like your password. Only the .pub file is safe to upload to servers.
Copy the public key to your server:
“`bash title="Install the public SSH key on the VPS" ssh-copy-id username@your-vps-ip
Now you can SSH and transfer files **without typing passwords**.
Use keys with `rsync`:
```bash title="Sync with a specific SSH private key"
rsync -avz -e "ssh -i /path/to/private-key" /local/ user@server:/remote/
Use keys with scp:
“`bash title="Copy with a specific SSH private key" scp -i /path/to/private-key /local/file user@server:/remote/
SSH keys are essential for **automated scripts and scheduled backups**, since scripts cannot enter passwords interactively.
### Working with custom SSH ports
Many people change SSH from the default port `22` to a custom port to reduce automated attack attempts. If your server uses a custom port, you need to specify it in your commands.
With `rsync`:
```bash title="Sync through custom SSH port 2222"
rsync -avz -e "ssh -p 2222" /local/ user@server:/remote/
You specify the port inside the -e flag as part of the SSH command.
With scp:
“`bash title="Copy through custom SSH port 2222" scp -P 2222 /local/file user@server:/remote/
Notice that rsync uses lowercase `-p` inside the SSH command, but scp uses uppercase `-P` directly. This inconsistency can be annoying, but that is just how these tools work.
### Limiting bandwidth to avoid network saturation
If you’re on a shared network and don’t want your file transfer to consume all available bandwidth, you can limit the transfer speed.
With `rsync`:
```bash title="Limit rsync bandwidth to 1000 KB/s"
rsync -avz --bwlimit=1000 /local/ user@server:/remote/
The –bwlimit flag limits speed in kilobytes per second (KBps). For example, –bwlimit=1000 means 1000 KBps (roughly 1 MB/s or 8 Mbps).
This is especially useful when running backups during business hours, as it prevents slowing down everyone else on the network.
Troubleshooting common transfer issues
Here are the issues you’re most likely to run into and how to fix them quickly.
Problem: permission denied (publickey)
This error means SSH key authentication failed. To fix it:
- Make sure you actually copied your public key to the server:
“`bash title="Install the public SSH key on the server" ssh-copy-id username@server-ip
2. Check your file permissions. SSH is very strict:
```bash title="Restrict SSH directory and key-file permissions"
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
- Verify the authorized_keys file is owned by your user:
“`bash title="Set authorizedkeys ownership" chown username:username ~/.ssh/authorizedkeys
4. Verify your SSH agent is running and has your key loaded:
```bash title="Start ssh-agent and load a private key"
eval $(ssh-agent)
ssh-add ~/.ssh/your-private-key
Example of error logs
“`text title="Show Bitwarden container error messages" bitwarden-api | [ERROR] Database connection failed bitwarden-web | [ERROR] SSL certificate not found
This issue is almost always caused by permissions or a missed `ssh-copy-id` step.
### Problem: connection timed out
Your firewall is blocking the SSH connection. Check these one by one:
1. Is SSH actually running on the server?
```bash title="Check the SSH daemon status"
sudo systemctl status sshd
- Is the firewall allowing SSH connections?
“`bash title="Show UFW firewall status" sudo ufw status
3. Can you reach the server at all?
```bash title="Ping the VPS"
ping your-vps-ip
- If you changed SSH to a custom port, are you specifying that port in your command?
These four checks cover most timeout issues.
Problem: rsync failed to set times or permission errors
This means you don’t have write permissions on the destination directory.
- Option 1: Transfer to a directory where you have write permissions (recommended).
- Option 2: Use sudo on the remote side:
“`bash title="Run remote rsync with sudo" rsync -avz /local/ user@server:/path/ –rsync-path="sudo rsync"
The `--rsync-path` flag tells rsync to run with sudo on the remote server. Use carefully, as sudo access is powerful.
### Problem: SCP works but rsync fails
This usually means `rsync` is not installed on the remote server. Install it:
```bash title="Install rsync on the remote server"
ssh user@server "sudo apt install rsync"
Then re-run your rsync command.
Problem: transfers are extremely slow
Slow transfers can have several causes. Diagnose step by step:
- Are you using compression? If not, add -z to rsync or -C to scp.
- Test your network connection independently:
Safety check: This legacy example downloads an executable-sized test file over plain HTTP and discards it. Do not execute or open downloaded content; prefer an HTTPS endpoint from a provider you trust and verify the advertised size or checksum when retaining a file.
“`bash title="Download a speed-test file to /dev/null" wget -O /dev/null http://speedtest.tele2.net/100MB.zip
3. Check if the remote disk is slow (high I/O wait times):
```bash title="Monitor extended disk I/O statistics"
iostat -x 1
- If transferring many tiny files, create a tar archive first and transfer that single file. Small files add significant overhead.
Practical examples you will actually use
Daily automated backups
Use rsync with a cron job to back up production servers every night at 2 AM:
“`bash title="Mirror a production website into a backup" rsync -avz –delete -e "ssh -i /home/user/.ssh/backup-key" prod-server:/var/www/ /backups/website/
The --delete flag keeps backups clean by removing files that were deleted from the source. This runs completely unattended.
### Quick deployment of code changes
When pushing code to production, sync only changed files:
```bash title="Deploy changed files without Git or node_modules"
rsync -avz --exclude '.git/' --exclude 'node_modules/' /local/project/ user@prod-server:/var/www/site/
This is much faster than uploading the entire project every time. Only the files you actually changed get transferred.
Grabbing log files for debugging
For troubleshooting, use scp to quickly download log files:
“`bash title="Download an application log with SCP" scp admin@web-server:/var/log/app.log ~/Desktop/
## Essential commands quick reference
Here is a condensed cheat sheet of the commands you’ll use most often.
### rsync commands
#### Upload to server
```bash title="Upload a directory with rsync"
rsync -avz /local/ user@server:/remote/
Download from server
“`bash title="Download a directory with rsync" rsync -avz user@server:/remote/ /local/
#### Mirror directories (sync + delete)
```bash title="Mirror a directory with rsync"
rsync -avz --delete /local/ user@server:/remote/
Upload with progress
“`bash title="Upload with resumable progress" rsync -avzP /local/ user@server:/remote/
#### Limit bandwidth
```bash title="Limit rsync bandwidth to 1000 KB/s"
rsync -avz --bwlimit=1000 /local/ user@server:/remote/
Exclude files
“`bash title="Exclude log files from an rsync upload" rsync -avz –exclude='*.log' /local/ user@server:/remote/
#### Use SSH key
```bash title="Upload with an Ed25519 SSH key"
rsync -avz -e "ssh -i ~/.ssh/id_ed25519" /local/ user@server:/remote/
Remote to remote transfer
“`bash title="Attempt a remote-to-remote rsync transfer" rsync -avz user1@host1:/path/ user2@host2:/path/
### SCP commands
#### Upload with SCP
```bash title="Upload a file with SCP"
scp file.txt user@server:/remote/
Download with SCP
“`bash title="Download a file with SCP" scp user@server:/remote/file.txt /local/
#### Upload directory with SCP
```bash title="Upload a directory recursively with SCP"
scp -r /local/dir user@server:/remote/
Download directory with SCP
“`bash title="Download a directory recursively with SCP" scp -r user@server:/remote/dir /local/
#### SCP with identity file
```bash title="Upload a file with an Ed25519 identity"
scp -i ~/.ssh/id_ed25519 file.txt user@server:/remote/
Remote to remote copy with SCP
“`bash title="Copy a file between two remote hosts with SCP" scp user1@host1:/path/file user2@host2:/path/
### rsync vs SCP comparison
| Capability | Rsync | SCP |
| --- | --- | --- |
| Transfer model | Can compare source and destination and transfer changed data | Copies the selected files over SSH |
| Partial transfer | `--partial` can retain partial files; test the chosen resume workflow | Resume behavior depends on the client and invocation; test before relying on it |
| Exclusions | Supports rules such as `--exclude` | Select source paths explicitly or use a separate file-selection step |
| Metadata | Options control permissions, ownership, times, links, and deletion behavior | Behavior depends on client flags; verify required metadata |
| Performance | Measure the same dataset, network, compression, cipher, and repeated-transfer case | Measure the same inputs rather than assuming a winner |
| Safety | Review source/destination, trailing slashes, deletion flags, permissions, and dry-run output | Review source/destination, overwrite behavior, permissions, and a test copy |
## Final thoughts
Rsync is objectively better for most file transfer scenarios because it only transfers what has changed. This saves time and bandwidth, especially for repeated transfers like backups and deployments. Use rsync when efficiency matters. Use scp when the speed of typing the command matters more than the speed of the transfer.
Always use SSH keys instead of passwords for security and convenience. Always keep both tools updated with the latest security patches. And always do a dry run before using --delete. That is how you efficiently transfer files on a VPS.
Ready to run file sync and backup workflows on Virtarix VPS?
Compare VPS sizes for rsync, SCP, backup transfers, root access, NVMe storage, IPv4 + IPv6, snapshots, and storage headroom.
Storage VPS S
For backups, media and small archives
- ✓ 2 CPU cores
- ✓ 4 GB RAM
- ✓ 200 GB NVMe
- ✓ Unlimited
Storage VPS M
For growing files and app storage
- ✓ 4 CPU cores
- ✓ 8 GB RAM
- ✓ 400 GB NVMe
- ✓ Unlimited