Networking
SCP
Copy syntax, the flags worth knowing, and when to reach for rsync or tar over SSH instead.
Cheatsheet #
| Task | Command |
|---|---|
| Up | scp file host:/tmp/ |
| Down | scp host:/tmp/file . |
| Directory | scp -r dir host:/srv/ |
| Keep mtime and mode | scp -p file host:/tmp/ |
| Non-standard port | scp -P 2222 file host:/tmp/ |
| Specific key | scp -i ~/.ssh/id_deploy file host:/tmp/ |
| Through a bastion | scp -J bastion file host:/tmp/ |
| Between two remotes | scp -3 a:/f b:/f |
| Cap bandwidth (Kbit/s) | scp -l 8000 big.tar host:/tmp/ |
| Quiet, for scripts | scp -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 bracketsPaths 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 #
| Situation | Better tool |
|---|---|
| Large or repeated transfers | rsync -avzP — incremental, resumable, shows progress |
| Directory trees that must match | rsync -av --delete |
| Many small files | tar over SSH; one stream instead of one round trip per file |
| Interactive browsing | sftp |
| Mounting instead of copying | sshfs |
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.tgzOpenSSH 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