Software Engineering Wiki

Networking

SCP

Copy syntax, the flags worth knowing, and when to reach for rsync or tar over SSH instead.

Cheatsheet #

TaskCommand
Upscp file host:/tmp/
Downscp host:/tmp/file .
Directoryscp -r dir host:/srv/
Keep mtime and modescp -p file host:/tmp/
Non-standard portscp -P 2222 file host:/tmp/
Specific keyscp -i ~/.ssh/id_deploy file host:/tmp/
Through a bastionscp -J bastion file host:/tmp/
Between two remotesscp -3 a:/f b:/f
Cap bandwidth (Kbit/s)scp -l 8000 big.tar host:/tmp/
Quiet, for scriptsscp -q file host:/tmp/

-P is the port for scp, while ssh uses -p. Getting that wrong is the single most common scp error.

Syntax #

scp [options] source target
scp file.txt host:/remote/dir/             # local to remote
scp host:/remote/file.txt ./local/         # remote to local
scp user@host:/path/file .                 # explicit user
scp -r ./dir host:/srv/                    # recursive
scp host:'/var/log/*.log' ./logs/          # remote glob: quote it, the remote shell expands it
scp -3 src-host:/f dst-host:/f             # relay through this machine
scp file '[2001:db8::1]:/tmp/'             # IPv6 literal needs brackets

Paths with spaces are expanded twice — once locally, once by the remote shell — so they need quoting on both levels: scp host:'"/tmp/my file"' .

Everything in ~/.ssh/config applies, so a Host entry with ProxyJump, User and IdentityFile means scp file db-01:/tmp/ just works.

When not to use scp #

SituationBetter tool
Large or repeated transfersrsync -avzP — incremental, resumable, shows progress
Directory trees that must matchrsync -av --delete
Many small filestar over SSH; one stream instead of one round trip per file
Interactive browsingsftp
Mounting instead of copyingsshfs
rsync -avzP --delete ./dir/ host:/srv/dir/         # trailing slash on source = contents
rsync -avz --dry-run ./dir/ host:/srv/dir/         # always dry run --delete first
tar czf - ./dir | ssh host 'tar xzf - -C /srv'     # fast for thousands of small files
ssh host 'tar czf - /var/log/nginx' > nginx-logs.tgz

OpenSSH 9.0 switched scp to use the SFTP protocol underneath. Behaviour with remote globs and unusual filenames changed subtly; -O forces the legacy protocol if an old server or script depends on it.

Oneliners #

# Copy and verify
scp app.tar host:/tmp/ && ssh host 'sha256sum /tmp/app.tar' && sha256sum app.tar

# Fan a file out to many hosts
printf '%s\n' web{1..10} | xargs -P8 -I{} scp -q config.yaml {}:/etc/app/

# Pull the newest log from each host into a per-host file
for h in web1 web2; do scp -q "$h:/var/log/app.log" "app-$h.log"; done

# Copy without overwriting a newer remote file
rsync -avu file host:/srv/

# Resume an interrupted large transfer
rsync --partial --progress --append-verify big.iso host:/srv/

# Move rather than copy
scp big.tar host:/srv/ && rm big.tar

# Restrict a deploy key to file transfer only, in authorized_keys
# command="internal-sftp",restrict ssh-ed25519 AAAA... deploy@ci

Last updated 15 September 2026 · Edit this page