scp Command Linux: Copy Files to and from a VPS (rsync, sftp)
- November 14, 2026
- 9:00 am
- By Adrien Roche
- Tutorials

The scp command in Linux: syntax and the basic transfers
scp (secure copy) copies files between machines over SSH, so the user, key and port that work for ssh work for scp. The shape is always scp [options] SOURCE DESTINATION, and whichever side is remote is written user@host:path. It needs a working SSH login; if port 22 is not answering yet, enable SSH on Ubuntu and harden it first.
1. Local to remote (upload)
scp backup.tar.gz user@SERVER_IP:/home/user/
scp backup.tar.gz user@SERVER_IP:~/backups/renamed.tar.gz
scp *.conf user@SERVER_IP:~/nginx-conf/
A path ending in a slash drops the file inside that directory under its original name; a full path renames it on arrival. Several local files can precede the destination as long as the destination is a directory, and that directory must already exist: scp does not create parents.
2. Remote to local (download)
scp user@SERVER_IP:/var/log/nginx/access.log .
scp user@SERVER_IP:'/var/log/nginx/*.log' ./logs/
The dot means "here, same name". Quote remote wildcards so your local shell does not expand them. Remote-to-remote works too: scp -3 user@HOST_A:/data/dump.sql user@HOST_B:/data/ routes the copy through your machine, so only your machine needs credentials for both servers.
3. Directories with -r
scp -r ./site user@SERVER_IP:/var/www/
scp -r user@SERVER_IP:/etc/nginx ./nginx-backup
-r copies a directory and everything under it. The first command creates /var/www/site on the server; the second brings the whole nginx tree down into a local folder. Symlinks are copied as regular files, one reason rsync is the better tool for full system trees.
scp options you will actually use: -P, -i, -C, -p
scp -P 2222 file.txt user@SERVER_IP:/home/user/ # non-default SSH port
scp -i ~/.ssh/id_ed25519 file.txt user@SERVER_IP:~/ # use a specific key
scp -C dump.sql user@SERVER_IP:~/ # compress in transit
scp -rp ./site user@SERVER_IP:/var/www/ # keep times and modes
The port flag is a capital -P. Lowercase -p means "preserve modification times, access times and mode bits", and because ssh itself uses -p for the port, mixing them up is the most common scp mistake. To stop repeating the port and key on every call, define the host once in ~/.ssh/config; scp, rsync and sftp all read it:
Host vps
HostName SERVER_IP
User user
Port 2222
IdentityFile ~/.ssh/id_ed25519
Two more flags: -l 8000 caps the transfer at 8,000 Kbit/s (about 1 MB/s), and -v shows which keys are offered when authentication fails. Since OpenSSH 9.0, scp speaks the SFTP protocol underneath; the syntax is unchanged, but shell tricks embedded in a remote path (command substitution, for example) no longer run on the server, and -O restores the legacy protocol for a very old server that rejects the new mode.
rsync: mirrors, resumable transfers and excludes
rsync copies only what changed. The first run behaves like scp -r; later runs compare both sides and send just the differences: new files, and for changed files only the modified blocks. That makes it the tool for deployments, backups and anything large. It must be installed on both ends (sudo apt install rsync); some minimal images and containers ship without it.
1. The everyday command
rsync -avz --progress ./site/ user@SERVER_IP:/var/www/site/
rsync -avz --progress user@SERVER_IP:/var/www/site/ ./site-backup/
-a (archive) expands to -rlptgoD: recurse, keep symlinks as symlinks, preserve permissions, times, group, owner and device files; ownership is only preserved when the receiving side runs as root. -v lists files, -z compresses, --progress shows each file.
The trailing slash matters. ./site/ means "the contents of site"; ./site means "the directory itself", which would create /var/www/site/site. When unsure, add -n (--dry-run) and read the file list before anything is written.
2. Mirror with --delete
rsync -avzn --delete ./site/ user@SERVER_IP:/var/www/site/ # dry run first
rsync -avz --delete ./site/ user@SERVER_IP:/var/www/site/
--delete removes destination files that no longer exist in the source. A real mirror needs it, and it is also what turns a wrong trailing slash into a wiped directory, so dry-run every new --delete command once.
3. Exclude files
rsync -avz --exclude='node_modules/' --exclude='.git/' --exclude='*.log' ./app/ user@SERVER_IP:/srv/app/
rsync -avz --exclude-from='.rsyncignore' ./app/ user@SERVER_IP:/srv/app/
Patterns are relative to the transfer root; a leading slash anchors them, so /config.php excludes only the top-level file. --exclude-from reads one pattern per line.
4. Resume a partial transfer, custom port and key
rsync -avP -e 'ssh -p 2222 -i ~/.ssh/id_ed25519' ./disk.img user@SERVER_IP:/backup/
-P is short for --partial --progress: if the connection drops, the partial file is kept and re-running the same command continues from where it stopped. SSH options go through -e, and here the port is lowercase -p because it is ssh's flag, not rsync's; a Host entry in ~/.ssh/config removes the whole -e string.
sftp: interactive transfers over the same SSH login
sftp gives you an FTP-style prompt over SSH, handy for browsing before copying.
sftp -P 2222 -i ~/.ssh/id_ed25519 user@SERVER_IP
sftp> ls -l /var/www
sftp> lcd ~/Downloads
sftp> get /var/log/syslog
sftp> put -r ./site /var/www/
sftp> reget backup.iso
sftp> bye
Commands starting with l act on your machine (lcd, lls, lpwd); the rest act on the server. put -r and get -r copy directories, reget and reput resume an interrupted file, and !command runs a local shell command without leaving the session. sftp -b commands.txt user@SERVER_IP replays a file of these commands for cron jobs.
Copy files from Windows: PowerShell scp and WinSCP
Windows 10 (1809 and later) and Windows 11 ship the OpenSSH client, so scp, sftp and ssh exist in PowerShell with the same flags as on Linux:
cd C:\Users\you\Projects
scp -P 2222 -i $HOME\.ssh\id_ed25519 .\site.zip user@SERVER_IP:/home/user/
scp -r user@SERVER_IP:/var/www/site $HOME\Downloads
Keys live in C:\Users\you\.ssh\ next to known_hosts, and ssh-keygen -R SERVER_IP works there too. Relative paths after a cd sidestep any confusion between a drive letter and a hostname. rsync is not included; the cleanest way to get it is WSL: wsl rsync -avz /mnt/c/Users/you/site/ user@SERVER_IP:/var/www/site/.
For a GUI, WinSCP is the usual choice: it speaks SFTP and SCP, offers a two-pane drag-and-drop view, and its Synchronize command does roughly what rsync --delete does. It expects keys in PuTTY's .ppk format and offers to convert an OpenSSH key when you select one. For anything deployed repeatedly, a one-line scp or rsync in a script is easier to review and automate.
scp vs rsync vs sftp: which one to use
| Task | Best tool | Why |
|---|---|---|
| One file, one time | scp | Shortest command, nothing to install |
| A directory, first copy | scp -r or rsync -a | Equivalent result |
| Repeated deploys or backups | rsync | Only changed data is sent |
| Mirror with deletions | rsync --delete | scp cannot remove files |
| Resume a large file | rsync -P or sftp reget | scp restarts from zero |
| Browse, then pick files | sftp or WinSCP | Interactive listing |
| Thousands of small files | rsync or tar over ssh | Per-file overhead dominates scp |
| Windows without WSL | PowerShell scp or WinSCP | No native rsync |
Rule of thumb: scp when you can type the command from memory, rsync as soon as you will run it twice.
Speed tips that actually move the needle
- Compression is not free.
-C(scp) and-z(rsync) shrink text, SQL dumps and logs, but waste CPU on tarballs, images and video. Iftopshowssshdpinned at 100% during a transfer, drop the flag. - Bundle many small files. Per-file round trips dominate when you push a source tree; one stream is faster:
tar czf - ./site | ssh user@SERVER_IP 'tar xzf - -C /var/www/'. - Try another cipher on fast links. When the ssh process itself is the bottleneck,
scp -c aes128-gcm@openssh.comuses the AES instructions built into nearly every modern CPU. - Protect long transfers. Run them inside
tmuxorscreenso a dropped session does not kill the copy, or usersync -Pand re-run. - Verify and throttle.
sha256sum fileon both ends should match;scp -lorrsync --bwlimitkeeps a backup from starving the application.
Permissions, ownership and common errors
Permission denied on the destination path
scp: /var/www//html/: Permission denied means the login succeeded but that user cannot write there. Copy into your home directory and sudo mv the files, give the user ownership of the target (sudo chown -R user:user /var/www/site), or with rsync add --rsync-path="sudo rsync", which works when the login user has passwordless sudo, as set up in creating a user with sudo rights on Linux. Uploading as root avoids the error but leaves root-owned files that a web server cannot write: sudo chown -R www-data:www-data /var/www/site afterwards.
Permission denied (publickey)
The server refused authentication. Check the username, force the key with -i, add -v; on the VPS, sudo journalctl -u ssh -n 30 names the reason.
Remote host identification has changed
Expected after a reinstall or an IP reassignment: the host key no longer matches ~/.ssh/known_hosts. If you did rebuild the server, run ssh-keygen -R SERVER_IP and reconnect. If you did not, find out why before typing a password anywhere.
No such file or directory
scp does not create parent directories: run ssh user@SERVER_IP 'mkdir -p /var/www/site' first. rsync creates the last directory component only (--mkpath, rsync 3.2.3+, creates parents).
rsync: command not found, or protocol data stream (code 12)
rsync is missing on one side; sudo apt install rsync there and retry.
Uploaded scripts that refuse to run
Files that came from Windows have no execute bit and often carry CRLF line endings, which produces bad interpreter: No such file or directory. Fix both with chmod +x deploy.sh and sed -i 's/\r$//' deploy.sh; the shebang side is covered in what a .sh file is and how bash runs it. One last classic: scp file.txt user@SERVER_IP without a colon after the host is a local copy that silently creates a file named user@SERVER_IP.
All of this assumes a server you reach over SSH as root or a sudo user, where the transfer itself, not a quota, is the limit. A Linux VPS from rdp.monster is that kind of machine: full root access, dedicated CPU and RAM, unlimited (fair-use) bandwidth, no KYC, crypto accepted, from $8.99 per month, and the server is online about 10 seconds after payment confirms, so the first thing you run on it can be the scp from step one.
Frequently Asked Questions
Is the scp command deprecated?
scp command moves files over SFTP instead. The command, its flags and its user@host:path syntax are unchanged, so existing scripts keep working; only shell expansion inside remote paths behaves differently, and -O restores the legacy protocol for servers that need it. For new automation, rsync or sftp are the more capable choices, but scp itself is fine for one-off copies.Can I use scp with a password instead of an SSH key?
PasswordAuthentication yes, scp prompts for the password and the copy proceeds; there is no flag to pass the password on the command line, and tools such as sshpass that do so leave it in your shell history. Hardened servers usually disable password login, in which case scp fails with Permission denied (publickey) until you install a key with ssh-copy-id. Keys are also what let scripts run unattended.Does scp overwrite existing files without asking?
-n (--dry-run) prints what would change, --ignore-existing leaves files that already exist untouched, --update skips files that are newer on the destination, and --backup --backup-dir=DIR keeps the replaced versions. For anything irreplaceable, copy into a fresh directory and swap it in afterwards.How do I copy files directly between two VPS servers?
rsync -avz /data/ user@OTHER_IP:/data/ (or scp -r) so the data never crosses your home connection. That requires the first server to hold a key the second one accepts. If you would rather not place keys on servers, scp -3 user@HOST_A:/path user@HOST_B:/path copies through your machine using only your local credentials, at the cost of downloading and uploading everything once each.Adrien Roche, Infrastructure & Hosting Editor
Systems engineer with 10+ years operating Windows Server and Linux fleets. Adrien runs the rdp.monster infrastructure documentation and writes our guides on RDP, VPS hosting, server administration, networking and privacy tooling.




