Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

scp: copy files and directories over SSH

By updated

Improve this page

When you deploy a configuration to a server, fetch logs or move a file quickly between two machines, scp (Secure Copy Protocol) is the most direct tool. It relies on SSH to encrypt the transfer end to end: a single command, no FTP server to set up. For one-off transfers, scp remains unbeatable in simplicity.

What you will learn

  • Copy a file to or from a remote server
  • Transfer a complete directory with -r
  • Use a custom SSH port with -P (uppercase)
  • Know the difference between -p and -P
  • Recognise when to prefer rsync over scp

Where scp fits among Linux transfer tools

scp belongs to the file transfer tools of Linux. Each of them has its speciality, and the first table places them side by side. The second one answers the question you actually ask in front of a terminal: for this situation, which tool should I reach for? Read it as a decision grid, where scp wins every time the transfer is single and immediate.

CommandSpecialityWhen to use it
scpRemote copy over SSHQuick one-shot transfer
rsyncSmart synchronisation (delta)Backups, large files, resume
sftpInteractive transfer over SSHRemote browsing, long sessions
tar + sshArchive + transferTrees with complex permissions
curl / wgetHTTP/FTP downloadFetch from the web

When to use scp rather than the others:

SituationBest toolWhy
Copy a single file to a serverscpSimpler than rsync for one file
Back up a directory regularlyrsyncOnly transfers the differences
Transfer cut halfwayrsync -Pscp starts over from zero
Transfer a very large filersyncResume possible, controlled bandwidth
Fetch a log quicklyscpA single command, fast
Two-way synchronisationunisonrsync and scp are one-way

Understand scp in 2 minutes

The model below sums up the command: an option set, a source, the encrypted SSH tunnel the data travels through, and a destination. Keep the order in mind, it is the same as cp.

🧠 Mental model: How scp works

scp = Source → [Encrypted SSH tunnel] → Destination

The scp command reads from left to right: scp, its options (-r, -P, -p, -C), the local or remote source, the encrypted SSH tunnel, then the local or remote destination.scpOPTIONS-r recursive · -P port · -p permissions · -C compressionSOURCEreport.txt · admin@web-01:/var/log/app.logSSH TUNNELencryptedDESTINATION/tmp/ · admin@backup-01:/logs/
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Key points

  • scp copies files through an encrypted SSH tunnel
  • The user@host:path syntax points to a remote location
  • Without -r, scp only copies files (not directories)
  • -P (uppercase) for the port, -p (lowercase) to preserve permissions
  • No transfer resume: if the link drops, you start over
  • For recurring transfers, prefer rsync over scp

Golden rules

1
Use SSH keys rather than passwords Safer, and it allows automation without interaction.
2
Prefer rsync for large or recurring transfers rsync resumes interrupted transfers and only sends the differences.

Essential vocabulary

-r
Recursive: copies directories and their contents
-P (uppercase)
Set the SSH port (watch out: uppercase!)
-p (lowercase)
Preserve permissions and timestamps
-C
Enable compression during the transfer
-q
Quiet mode (no progress bar)
-v
Verbose mode (SSH connection details)
📚 Going further: 7 advanced options
-l
Limit the bandwidth (in Kbit/s)
-3
Copy between 2 servers through the local machine (already the default behaviour)
-R
Copy between 2 servers directly, the first one connecting to the second
-i
Point to a specific SSH private key file
-o
Pass SSH options (StrictHostKeyChecking, etc.)
-F
Use an alternative SSH config file
-c
Choose the encryption algorithm

The mechanism: scp opens an SSH session to the remote machine, then transfers the file inside that encrypted channel. Since OpenSSH 9.0, it no longer speaks the old SCP protocol but the SFTP protocol over the same connection; the command line has not changed, but the expansion of wildcards and of ~ is now handled differently. The -O option switches back to the old protocol for servers that do not implement SFTP.

Minimal syntax

scp takes the argument order of cp: the source first, the destination second. The only difference is that one of the two can carry a user@machine: prefix. That prefix, and it alone, triggers the SSH connection; without it, scp simply falls back to a local copy.

Terminal window
scp [OPTIONS] SOURCE DESTINATION

The 3 usage scenarios

The direction of the transfer is not given by an option but by the position of the remote prefix. Spot where the : sits in the command and you know what leaves and what arrives. The third case is the only one involving two servers: the copy then travels through your workstation, which uses your bandwidth twice.

ScenarioSyntaxExample
Local → Remotescp file user@srv:/dest/scp config.conf admin@web-01:/etc/nginx/
Remote → Localscp user@srv:/file /local/scp admin@web-01:/var/log/app.log ~/logs/
Remote → Remotescp -3 user1@srv1:/f user2@srv2:/dest/scp -3 admin@web-01:/log admin@backup:/logs/

The -p vs -P trap

This is the number one source of errors with scp. The two options differ only by their case, and both exist: a mistake is therefore never rejected as an unknown option, it silently does something else. The block below shows the real message returned by OpenSSH 9.6, 9.9 and 10.0 when -p is typed instead of -P.

OptionMeaningExample
-p (lowercase)Preserves permissions and timestampsscp -p script.sh user@srv:/opt/
-P (uppercase)Custom SSH Portscp -P 2222 file user@srv:/tmp/

Memory aid: Port = uppercase P, permissions = lowercase p.

Terminal window
# Common mistake: -p instead of -P for the port
scp -p 2222 file.txt user@server:/tmp/
# scp: stat local "2222": No such file or directory
# -p enables preservation, "2222" becomes a source file to copy
# Correct: -P (uppercase) for the port
scp -P 2222 file.txt user@server:/tmp/

The essential options

Eight options cover almost every everyday transfer. The first five are the ones you will type weekly; -l and -i show up as soon as you work on a shared link or in a script. The "standard combo" block right after shows how they chain on a single line.

OptionMeaningUsage
-rRecursiveCopy a complete directory
-P <port>SSH portSSH server on a non-standard port
-pPreserveKeep permissions and timestamps
-CCompressionSpeed up slow connections
-vVerboseDebug SSH problems
-qQuietNo progress bar
-l <Kbit/s>LimitControl the bandwidth
-i <key>IdentityUse a specific SSH key

The standard combo:

Terminal window
# Simple upload to a server
scp config.conf admin@web-01:/etc/nginx/
# Complete directory with compression and custom port
scp -r -C -P 2222 ~/project/ deployer@prod:/var/www/app/
# Fetch logs while preserving timestamps
scp -p admin@web-01:/var/log/app.log ~/logs/

Transfer a complete directory

By default, scp only copies regular files. To copy a directory with all its content, the -r option (recursive) is mandatory. Without it, scp refuses the copy with two lines, measured on Debian 13 and AlmaLinux 10: the first gives the cause, the second the failed operation.

Terminal window
# Error: scp refuses to copy a directory without -r
scp folder user@server:/tmp/
# scp: local "folder" is not a regular file
# scp: failed to upload file folder to /tmp/
# Correct: -r for recursive
scp -r folder/ user@server:/tmp/

Transfer between two remote servers

The -3 option copies a file from one server to another using your local machine as a relay. It is the answer when the two servers are not allowed to talk to each other directly, a common case in a segmented network.

Terminal window
# Copy logs from web-01 to backup-01, through your machine
scp -3 admin@web-01:/var/log/app.log admin@backup-01:/logs/

On the three reference distributions, this transit through the local machine is already the default behaviour: the manual page of OpenSSH 9.6 (Ubuntu 24.04), 9.9 (AlmaLinux 10) and 10.0 (Debian 13) states it plainly under -3, "This mode is the default". The -R option asks for the opposite, a direct copy where the first server connects to the second one itself, which requires it to be able to authenticate there. Writing -3 remains useful: the command says what it does, and it keeps the same meaning on an older machine where the direct copy was the rule.

Use a specific SSH key

For automated transfers (scripts, CI/CD), use a dedicated key: it avoids storing a password in clear text and can be revoked on its own without affecting your other accesses.

Terminal window
# Specific SSH key
scp -i ~/.ssh/deploy_key app.tar.gz deployer@prod:/releases/
# Combined with a custom port
scp -i ~/.ssh/deploy_key -P 2222 app.tar.gz deployer@prod:/releases/

Compression and bandwidth

On slow connections or for large text files, compression speeds up the transfer. The gain is marginal on data that is already compressed, such as an archive or an ISO image. The second command caps the rate so as not to saturate a shared link.

Terminal window
# Compression enabled
scp -C database-dump.sql admin@backup:/dumps/
# Limit the bandwidth (in Kbit/s, not KB/s!)
scp -l 800 big-file.iso user@server:/data/
# 800 Kbit/s ≈ 100 KB/s

Unit of -l: Kbit/s

The -l option uses Kbit/s (not KB/s). To get 100 KB/s, you must specify 800 (100 × 8). It is a frequent source of errors.

Security good practices

scp inherits the whole SSH configuration: transport encryption is therefore never the weak point. The real risks lie elsewhere, in the rights of the account used and in what the file becomes once dropped. The four reflexes below answer three questions: which identity do I connect with, where do I write, and how do I check afterwards. The third point matters most in production: copying straight into /etc/ as root gives an automated transfer write access to the whole system configuration.

  • Use SSH keys rather than passwords for automation
  • Check the permissions after the transfer: ssh user@srv "ls -la /path/"
  • Prefer a non-root account for transfers, then move the file with sudo if needed
  • Monitor connections: sudo journalctl -u ssh on Debian and Ubuntu, sudo journalctl -u sshd on RHEL and AlmaLinux

When to switch to rsync

scp is ideal for one-off transfers. As soon as a transfer repeats or becomes large, rsync does better, and the table shows on which criteria. The only line where scp keeps the advantage is availability: it ships with the SSH client, whereas rsync must be installed on both ends.

Criterionscprsync
Transfer resumeNo, starts overYes, resumes where it stopped
Delta transferNo, copies everythingYes, only copies the changes
File exclusionNoYes, --exclude
Dry-runNoYes, -n to test
SimplicitySimplerMore options to learn
AvailabilityEverywhereNot always installed

Simple rule: one-off transfer → scp. Recurring or large transfer → rsync.

Pitfalls to avoid

These mistakes come back constantly with scp. Knowing them avoids failed transfers and files overwritten by mistake. Each one is presented the same way: the command that goes wrong, the symptom, the cause, then the correct form.

Mixing up -p and -P

Terminal window
scp -p 2222 file.txt user@server:/tmp/

Symptom: the port is ignored and scp tries to copy a file named 2222.

Cause: -p (lowercase) preserves permissions; -P (uppercase) changes the SSH port. Memory aid: Port = uppercase P.

Terminal window
scp -P 2222 file.txt user@server:/tmp/

Forgetting -r for directories

Terminal window
scp folder/ user@server:/tmp/

Symptom: not a regular file error, nothing is copied.

Cause: by default, scp only copies regular files, never directories.

Terminal window
scp -r folder/ user@server:/tmp/

No transfer resume

Terminal window
scp big-file.iso user@server:/data/ # network cut

Symptom: after a cut, the transfer starts over from zero.

Cause: scp does not support transfer resume, unlike rsync.

Terminal window
rsync -avP big-file.iso user@server:/data/

Spaces in remote paths

Terminal window
scp user@server:/home/user/My Folder/file.txt .

Symptom: error or file not found.

Cause: the shell treats the space as an argument separator. Without protection, scp receives two sources, …/My and Folder/file.txt.

Terminal window
scp "user@server:/home/user/My Folder/file.txt" .

Wrapping the whole argument in quotes is enough with the SFTP protocol, used by default since OpenSSH 9.0. If you force the old protocol with -O, the path is also interpreted by a remote shell: you then need a double protection, "user@server:/home/user/My\ Folder/file.txt".

Silent overwrite without confirmation

Terminal window
scp user@server:/config/new.conf ~/config/important.conf

Symptom: the local file is overwritten without any warning.

Cause: scp overwrites the destination without asking for confirmation; there is no equivalent of cp -i.

Terminal window
cp ~/config/important.conf ~/config/important.conf.bak && scp user@server:/config/new.conf ~/config/important.conf

Unit of -l in Kbit/s, not KB/s

Terminal window
scp -l 100 file user@srv:/tmp/ # thinking it limits to 100 KB/s

Symptom: transfer much slower than expected, around 12.5 KB/s.

Cause: -l is expressed in Kbit/s; 100 Kbit/s are only ~12.5 KB/s.

Terminal window
scp -l 800 file user@srv:/tmp/ # 800 Kbit/s ≈ 100 KB/s

Hands-on practice

This lab has you handle scp on realistic files, from a simple upload to bandwidth limiting. Allow 15 minutes.

Prepare the ground

The script below creates test files representative of server administration: a configuration, a deployment script, a log file and a small project tree.

Terminal window
# Create the lab
mkdir -p ~/scp-lab/{local,received}
cd ~/scp-lab/local
# Typical configuration file
cat > config.conf << 'EOF'
# nginx configuration
server {
listen 80;
server_name example.com;
root /var/www/html;
}
EOF
# Deployment script
cat > deploy.sh << 'EOF'
#!/bin/bash
echo "Deployment in progress..."
systemctl restart nginx
echo "Deployment finished."
EOF
chmod +x deploy.sh
# Log file
for i in $(seq 1 50); do
echo "$(date -d "$i hours ago" '+%Y-%m-%d %H:%M:%S') INFO Event $i" >> app.log
done
# Directory with sub-directories
mkdir -p project/{src,docs,tests}
echo "print('hello')" > project/src/main.py
echo "# Documentation" > project/docs/README.md
echo "def test(): pass" > project/tests/test_main.py
echo "Lab created in ~/scp-lab"
ls -laR ~/scp-lab/local

To clean everything up at the end: rm -rf ~/scp-lab.

The 8 lab steps

These eight steps assume an accessible SSH server: a virtual machine, a test server, or your own workstation if sshd runs on it and your public key is in ~/.ssh/authorized_keys. Replace user@server everywhere with your real values. Work through them in order: steps 1 to 3 set the syntax, steps 4 to 8 add one option at a time so you can tell which one changes what. After each upload, check the result on the remote side with ssh user@server "ls -l /tmp/".

  1. Copy a file to a server, send config.conf to the remote /tmp/: scp ~/scp-lab/local/config.conf user@server:/tmp/. The user@server:/path syntax designates the remote destination; everything goes through SSH.

  2. Fetch a file from a server, bring back /etc/hostname: scp user@server:/etc/hostname ~/scp-lab/received/. Source and destination swap: the server becomes the source.

  3. Copy a complete directory, send project/ with -r: scp -r ~/scp-lab/local/project user@server:/tmp/. Without -r, scp refuses to copy a directory.

  4. Use a custom SSH port, for a server on port 2222: scp -P 2222 ~/scp-lab/local/deploy.sh user@server:/tmp/. Careful, it is uppercase -P.

  5. Preserve permissions, copy deploy.sh keeping its executable bit: scp -p ~/scp-lab/local/deploy.sh user@server:/tmp/. Lowercase -p preserves permissions and timestamps.

  6. Transfer with compression, send app.log compressed: scp -C ~/scp-lab/local/app.log user@server:/tmp/. -C speeds up the transfer of text files.

  7. Verbose mode for debugging, watch the SSH negotiation: scp -v ~/scp-lab/local/config.conf user@server:/tmp/. -v displays authentication, algorithms and detailed errors.

  8. Limit the bandwidth, cap the rate at 800 Kbit/s: scp -l 800 ~/scp-lab/local/app.log user@server:/tmp/. That is about 100 KB/s.

Progressive exercises

These exercises go from the first transfer to the diagnosis of a failure. Read the statement, look for the command, then unfold the solution.

Foundations level

The first three exercises check the basic syntax: which argument comes first depending on the direction of the transfer, and when -r becomes mandatory.

Exercise 1, First transfer. Send the file config.conf to the server web-01 into /tmp/.

Hint: syntax scp file user@server:/path/.

See the solution
Terminal window
scp config.conf admin@web-01:/tmp/

The basic scp syntax: local file first, remote destination second.

Exercise 2, Fetching logs. Fetch the file /var/log/syslog from the server web-01 into ~/logs/.

Hint: the remote source comes first.

See the solution
Terminal window
scp admin@web-01:/var/log/syslog ~/logs/

To fetch a file, the remote source is the first argument, the local destination the second.

Exercise 3, Directory transfer. Send the whole ~/project/ directory to /var/www/ on the server prod.

Hint: -r is mandatory for directories.

See the solution
Terminal window
scp -r ~/project/ deployer@prod:/var/www/

Without -r, scp refuses to copy a directory, it is the most frequent error.

Composition level

These two exercises bring in the options whose case matters: -P for the port, -p for permissions.

Exercise 4, Non-standard SSH port. Send backup.tar.gz to a server that uses SSH port 2222.

Hint: -P (uppercase) for the SSH port.

See the solution
Terminal window
scp -P 2222 backup.tar.gz admin@srv:/backups/

Watch the case: uppercase -P = port, lowercase -p = permissions.

Exercise 5, Transfer with preservation. Send a script while preserving its execution permissions.

Hint: -p preserves permissions and timestamps.

See the solution
Terminal window
scp -p deploy.sh admin@prod:/opt/scripts/

-p preserves modification times and permissions, including the executable bit.

Industrialisation level

Here the transfer involves two servers or combines several options on one line, as in a real deployment.

Exercise 6, Transfer between two servers. Copy /var/log/app.log from web-01 to backup-01 through your machine.

Hint: -3 uses your machine as a relay.

See the solution
Terminal window
scp -3 admin@web-01:/var/log/app.log admin@backup-01:/logs/

-3 routes the data through the local machine, which is already the default behaviour on current OpenSSH versions: writing it makes the intent explicit. It is -R that would request a direct connection between the two servers.

Exercise 7, Combined options. Send a directory with compression, port 2222 and permission preservation.

Hint: scp options combine freely.

See the solution
Terminal window
scp -r -C -P 2222 -p ~/project/ admin@srv:/var/www/

-r -C -P 2222 -p covers most needs of a somewhat demanding transfer.

Security level

The last exercise is about diagnosis: finding out why a transfer fails before touching anything.

Exercise 8, Debugging a failed transfer. Diagnose a failing transfer by displaying the SSH details.

Hint: -v displays the SSH connection details.

See the solution
Terminal window
scp -v config.conf admin@srv:/tmp/

-v shows the SSH negotiation: authentication, algorithms and detailed errors.

Troubleshooting

When a transfer fails, these diagnosis methods and these frequent errors cover the vast majority of cases. Start with the method, which tells you where to look, then match the message against the table.

Diagnosis methods

When a transfer fails, the first question to settle is: does the problem come from SSH or from scp? The four commands below answer in that order. If ssh user@server "echo OK" works but scp fails, the connection and authentication are not at fault: look at the write permissions on the destination or at the SFTP subsystem on the server side. If ssh already fails, there is no point debugging scp. The -v output is verbose: the useful line is almost always the last one before the failure.

Terminal window
# Verbose mode: all SSH and transfer details
scp -v file.txt user@server:/tmp/
# Test the SSH connection before scp
ssh -v user@server "echo OK"
# Check that the SSH port is open
nc -zv server 22
# Test with a small file to rule out a size problem
echo "test" | ssh user@server "cat > /tmp/test.txt"

Frequent errors

These five messages cover almost every failure. Four of them actually come from SSH, not from scp: only not a regular file is specific to the copy. Take REMOTE HOST IDENTIFICATION HAS CHANGED! seriously: the message means that the host key presented no longer matches the one stored in ~/.ssh/known_hosts. The benign cause is a reinstalled server, but removing the entry without checking with the administrator amounts to accepting a possible interception.

ErrorProbable causeSolution
Permission deniedNo write rights, or destination directory missingAdjust permissions or copy to an allowed directory (/tmp/)
connect to host ... port 22: Connection refusedsshd stopped, firewall, or different portStart sshd, open the port, or use -P <port>
REMOTE HOST IDENTIFICATION HAS CHANGED!Reinstalled server, or MITM attack (rare)Check with the admin, then ssh-keygen -R server and reconnect
not a regular fileCopying a directory without the -r optionAdd -r to copy recursively
No space left on deviceDestination disk full or quota reachedCheck with ssh user@server "df -h", free some space

scp cheat sheet

Keep it at hand: the general syntax and the options you use most every day.

General syntax

Three forms are enough for every case. What sets them apart is the position of the user@host: prefix: on the right to send, on the left to fetch, on both sides for a transfer between servers. The final dot in the second form designates the current directory, a handy shortcut when you bring back a log to analyse it locally.

SyntaxMeaningExample
scp file user@host:/path/Send a filescp app.conf admin@srv:/etc/
scp user@host:/path/file .Fetch a filescp admin@srv:/var/log/app.log .
scp -3 user1@srv1:f user2@srv2:/Transfer between serversscp -3 a@s1:/log b@s2:/log/

Common options

Options combine freely and their order does not matter, as long as each option expecting a value is followed by its own. Two warnings about the last two lines of the table. -o StrictHostKeyChecking=no disables the host key check: keep it for a disposable lab, never for a production script, where it cancels the protection against interception. As for -c, it is only useful to interoperate with an old server; the OpenSSH default choice is safer than most algorithms you might be tempted to force.

SyntaxMeaningExample
-rRecursive copy (directories)scp -r folder/ user@srv:/tmp/
-P <port>SSH port (uppercase)scp -P 2222 f.txt user@srv:/
-pPreserves permissions and timestampsscp -p script.sh user@srv:/
-CEnables compressionscp -C dump.sql user@srv:/
-vVerbose mode (debug)scp -v f.txt user@srv:/
-qQuiet modescp -q f.txt user@srv:/
-l <Kbit/s>Limits the bandwidthscp -l 800 f user@srv:/
-i <key>SSH private keyscp -i ~/.ssh/key f user@srv:/
-o <option>One-off SSH optionscp -o StrictHostKeyChecking=no f srv:/
-c <algo>Encryption algorithmscp -c aes256-ctr f user@srv:/

Mastery checklist

You master scp when you can tick each of these points without hesitating. A point you cannot tick sends you back to the matching section above.

  • I can send a file to a remote server.
  • I can fetch a file from a remote server.
  • I can copy a complete directory with -r.
  • I can use a custom SSH port (-P).
  • I can preserve permissions and timestamps with -p.
  • I can transfer between two servers with -3.
  • I know the difference between -p and -P.
  • I know when to use rsync instead of scp.
  • I can diagnose a connection problem with -v.

Key points

  • -r: mandatory to copy a directory
  • -P (uppercase) for the port, -p (lowercase) for permissions
  • -3: relay through your machine, already the default on current OpenSSH
  • -v: essential to diagnose SSH problems
  • No resume: if the transfer is interrupted, it starts over
  • Prefer rsync for regular or large transfers

Knowledge check

Check that the essentials of this guide stuck. The questions cover only what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn