| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | openssh-clients 9.9p1-25.el10_2rsync 3.4.4-1.el10_2nmap-ncat 7.92-5.el10curl 8.12.1wget 1.24.5 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Fedora, Debian, Ubuntu, and other Linux distributions with OpenSSH client tools |
| Privilege | SSH access to the remote host as a user that can read the source path and write the destination path; ncat and HTTP examples also need a reachable TCP port on the receiving host |
| Scope | One-time and repeatable file copy with scp, rsync, SFTP batch mode, tar over ssh, ssh with cat, ncat, and HTTP download or upload with curl or wget. Does not cover NFS exports or SSHFS mounts. |
| Related guides | scp command rsync command SSH copy folder local to remote OpenSSH authentication methods curl command |
You usually copy files between Linux servers when you deploy code, move logs, or pull backups. Most methods below use SSH for encryption. Pick the tool by job shape: scp for a quick file, rsync for directories you will sync again — see scp vs rsync comparison when that choice is not obvious. SFTP batch mode fits scripted transfers, and tar over ssh helps when you have many small files. ncat and plain HTTP are valid fallbacks when SSH is not the right fit, but they are not encrypted unless you wrap them in TLS yourself.
Quick reference: copy files between Linux servers
| Method | Example command | Jump to |
|---|---|---|
scp (push file) |
scp /path/file user@remote-host:/tmp/ |
scp push |
scp (pull file) |
scp user@remote-host:/path/file /local/dir/ |
scp pull |
scp (recursive) |
scp -r /path/dir user@remote-host:/tmp/ |
scp directories |
scp (server to server) |
scp user1@server1:/path/file user2@server2:/path/ |
server-to-server scp |
rsync (push) |
rsync -av /path/dir/ user@remote-host:/tmp/dir/ |
rsync |
rsync (pull) |
rsync -av user@remote-host:/path/dir/ /local/dir/ |
rsync |
| SFTP batch | sftp -b batch.txt user@remote-host |
SFTP batch |
tar over ssh |
tar -C /src -czf - dir | ssh user@remote-host 'tar -xzf - -C /dest' |
tar over SSH |
ssh + cat |
ssh user@remote-host 'cat > /remote/file' < /local/file |
ssh cat |
ncat |
ncat --send-only remote-host 19000 < /local/file (receiver listens with ncat -l) |
ncat |
| HTTP download | curl -fsS http://remote-host:8080/file -o /local/file |
HTTP download |
| HTTP upload | curl -X PUT --data-binary @/local/file http://remote-host:8080/upload |
HTTP upload |
Choose the right copy method
| Goal | Prefer |
|---|---|
| Copy one file right now | scp |
| Copy from one remote server to another | scp user1@server1:/path user2@server2:/path |
| Sync a directory repeatedly | rsync -av |
Script put / get without a shell |
SFTP -b batch file |
| Thousands of small files | tar streamed over ssh |
One file and scp is not available, but SSH works |
ssh user@host 'cat > /dest' < /local/file |
| Temporary transfer on a trusted LAN with no SSH | ncat listener plus sender |
| File is already on a web server or REST API | curl or wget over HTTP or HTTPS |
| Browse remote paths interactively | Interactive sftp (see SFTP chroot guide for restricted uploads) |
On OpenSSH 9 and later, scp often uses the SFTP protocol internally. The scp command remains convenient for one-liners even though rsync and SFTP are better fits for sync and automation.
Method 1: Copy a file with scp
scp is the fastest one-liner when you need to push or pull a single file. Set REMOTE once if you run several examples against the same host:
REMOTE=root@192.168.56.156Push a local file to the remote server:
scp /root/file-copy-lab/src/hello.txt "$REMOTE:/tmp/hello-scp.txt"scp returns exit status 0 on success. Depending on how it is run, you may also see a progress meter, so verify the destination when demonstrating the result:
ssh "$REMOTE" cat /tmp/hello-scp.txtSample output:
alphaPull the file back to the local machine:
scp "$REMOTE:/tmp/hello-scp.txt" /tmp/hello-pull.txtRead the local copy:
cat /tmp/hello-pull.txtSample output:
alphaInclude the trailing colon on user@host: even when the path is the remote home directory. Without it, scp treats the target as a local path.
Method 2: Copy a directory with scp
Recursive scp copies an entire directory tree in one command.
scp -r /root/file-copy-lab/src "$REMOTE:/tmp/copy-src-scp"List the files that landed on the remote host:
ssh "$REMOTE" 'find /tmp/copy-src-scp -type f | sort'Sample output:
/tmp/copy-src-scp/hello.txt
/tmp/copy-src-scp/sub/nested.txtscp -r re-copies everything on each run. For directories you update often, rsync is more efficient.
Method 3: Copy between two remote servers with scp
When both servers are reachable from the machine where you run the command, scp can copy directly between them. The local host pulls from the first remote and pushes to the second; the file does not need to land on your laptop unless you want it there.
scp user1@server1:/tmp/file.txt user2@server2:/tmp/In the lab, copy from vm2 to the local host loopback to show the two-remote syntax on one client:
scp "$REMOTE:/tmp/hello-scp.txt" root@127.0.0.1:/tmp/server-to-server-scp.txtConfirm the file on the destination host:
cat /tmp/server-to-server-scp.txtSample output:
alphaOpenSSH can also copy origin to destination without streaming through your workstation when you pass -R, but that mode requires the first remote host to authenticate to the second. The default two-remote form above is simpler when your SSH keys and known_hosts entries are already set up on the client machine.
Method 4: Copy files with rsync
rsync compares source and destination and sends only differences. Trailing slashes matter: src/ means copy the contents of src into the destination directory.
Push a directory to the remote server:
rsync -av /root/file-copy-lab/src/ "$REMOTE:/tmp/copy-src-rsync/"Sample output:
sending incremental file list
created directory /tmp/copy-src-rsync
./
hello.txt
sub/
sub/nested.txt
sent 244 bytes received 111 bytes 64.55 bytes/sec
total size is 13 speedup is 0.04Pull the same tree back to the local host:
rsync -av "$REMOTE:/tmp/copy-src-rsync/" /tmp/copy-pull-rsync/Sample output:
receiving incremental file list
created directory /tmp/copy-pull-rsync
./
hello.txt
sub/
sub/nested.txt
sent 73 bytes received 236 bytes 88.29 bytes/sec
total size is 13 speedup is 0.04The -a flag preserves permissions and timestamps where possible; -v lists each path as it is transferred. See the rsync command cheat sheet for --delete, --dry-run, and bandwidth limits.
Method 5: Copy files with SFTP batch mode
Interactive sftp suits manual browsing. For scripts, put commands in a batch file and pass -b.
Create a batch file that uploads one file:
printf 'put /root/file-copy-lab/src/hello.txt /tmp/hello-sftp.txt\n' > /tmp/sftp-batch.txtRun the batch non-interactively:
sftp -b /tmp/sftp-batch.txt "$REMOTE"Sample output:
sftp> put /root/file-copy-lab/src/hello.txt /tmp/hello-sftp.txtVerify the upload:
ssh "$REMOTE" cat /tmp/hello-sftp.txtSample output:
alphaAdd get remote-path local-path lines to download files, or put -r for a directory upload. For passwordless automation, use SSH keys as in these examples.
Method 6: Copy many small files with tar over ssh
scp and rsync spend time per file. When you need to move a directory with thousands of small files, stream a tarball over SSH instead of opening many separate transfers.
Create the destination directory on the remote host first:
ssh "$REMOTE" 'rm -rf /tmp/copy-tar && mkdir -p /tmp/copy-tar'Stream a compressed archive from local to remote:
tar -C /root/file-copy-lab -czf - src | ssh "$REMOTE" 'tar -xzf - -C /tmp/copy-tar'Confirm the extracted tree:
ssh "$REMOTE" 'find /tmp/copy-tar/src -type f | sort'Sample output:
/tmp/copy-tar/src/hello.txt
/tmp/copy-tar/src/sub/nested.txtTo copy from remote to local, reverse the pipe: ssh user@remote-host 'tar -C /path -czf - dir' | tar -xzf - -C /local/dest.
Method 7: Copy a file with ssh and cat
When you already have SSH access and only need to push one file, redirect stdin on the remote side with cat. The transfer rides inside the SSH session, so it is encrypted like scp.
Push a local file to the remote host:
ssh "$REMOTE" 'cat > /tmp/ssh-cat-recv.txt' < /root/file-copy-lab/ssh-cat.txtConfirm the file on the remote host:
ssh "$REMOTE" cat /tmp/ssh-cat-recv.txtSample output:
ssh-cat-payloadThis pattern is handy in scripts when scp is missing but ssh is installed. It does not preserve timestamps or resume partial transfers the way rsync does.
Method 8: Copy a file with ncat
ncat (from the nmap-ncat package on RHEL) can move a file over a plain TCP socket. One host listens, the other connects and streams bytes. There is no encryption and no authentication, so use it only on trusted networks or for a quick rescue copy.
Open the destination port on the receiving host if a host firewall is active:
ssh "$REMOTE" 'firewall-cmd --add-port=19000/tcp'Start a listener on the receiver that writes incoming data to a file:
ssh "$REMOTE" 'rm -f /tmp/nc-recv.bin; ncat -l 19000 > /tmp/nc-recv.bin' &Wait until the listener has bound port 19000 before sending:
until ssh "$REMOTE" "ss -ltn | grep -q ':19000 '"; do sleep 0.2; doneFrom the sender, connect and stream the local file with --send-only so ncat closes after the transfer:
ncat --send-only 192.168.56.156 19000 < /root/file-copy-lab/payload.txtWait for the background listener to finish writing the file:
waitVerify the received file:
ssh "$REMOTE" cat /tmp/nc-recv.binSample output:
nc-transfer-payloadRemove the temporary firewall rule when you are done:
ssh "$REMOTE" 'firewall-cmd --remove-port=19000/tcp'On Debian and Ubuntu, install the ncat package if you want to use these commands. netcat-openbsd and other nc implementations have similar purposes but their options can differ. Prefer SSH-backed tools for routine server copies.
Method 9: Download a file over HTTP
When the source file is already served over HTTP, download it with curl or wget. This is common for vendor installers, public artifacts, or an internal file share behind a web server.
On the host that serves the file, publish it with a simple static server:
ssh "$REMOTE" 'mkdir -p /tmp/http-serve; printf "http-download-payload\n" > /tmp/http-serve/data.txt'Open the port if a firewall blocks inbound HTTP:
ssh "$REMOTE" 'firewall-cmd --add-port=18888/tcp'Start Python's built-in HTTP server on port 18888 and record its PID:
ssh "$REMOTE" 'cd /tmp/http-serve; nohup python3 -m http.server 18888 </dev/null >/tmp/http-serve.log 2>&1 & echo $! >/tmp/http-serve.pid'Wait until the server is listening before downloading:
until ssh "$REMOTE" "ss -ltn | grep -q ':18888 '"; do sleep 0.2; doneDownload the file from the other host with curl:
curl -fsS http://192.168.56.156:18888/data.txt -o /tmp/http-download.txtRead the downloaded copy:
cat /tmp/http-download.txtSample output:
http-download-payloadwget works the same way when you only need a download:
wget -qO /tmp/http-download-wget.txt http://192.168.56.156:18888/data.txtStop the test server and remove the temporary firewall rule:
ssh "$REMOTE" 'kill "$(cat /tmp/http-serve.pid)" 2>/dev/null || true; rm -f /tmp/http-serve.pid; firewall-cmd --remove-port=18888/tcp'For production URLs, use https:// and verify certificates. See the curl command cheat sheet for headers, auth, and retry flags.
Method 10: Upload a file over HTTP with curl
HTTP upload is less universal than download because the server must expose an endpoint that accepts PUT or POST. REST APIs, object storage gateways, and custom upload handlers fit this pattern.
The receiver below is a minimal Python handler that saves a PUT body to disk. Copy it to the remote host:
cat > /tmp/put-server.py <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_PUT(self):
n = int(self.headers.get("Content-Length", 0))
with open("/tmp/http-put-recv.bin", "wb") as f:
f.write(self.rfile.read(n))
self.send_response(201)
self.end_headers()
def log_message(self, fmt, *args):
pass
HTTPServer(("0.0.0.0", 18889), Handler).serve_forever()
PYCopy the script to the remote host:
scp /tmp/put-server.py "$REMOTE:/tmp/put-server.py"Open the upload port and start the handler in the background:
ssh "$REMOTE" 'firewall-cmd --add-port=18889/tcp; nohup python3 /tmp/put-server.py </dev/null >/tmp/put-server.log 2>&1 & echo $! >/tmp/put-server.pid'Wait until the upload handler is listening:
until ssh "$REMOTE" "ss -ltn | grep -q ':18889 '"; do sleep 0.2; doneUpload a local file with curl:
curl -fsS -X PUT --data-binary @/root/file-copy-lab/put.txt http://192.168.56.156:18889/uploadConfirm the uploaded bytes on the receiver:
ssh "$REMOTE" cat /tmp/http-put-recv.binSample output:
http-put-payloadMany real APIs expect POST with multipart form data instead of raw PUT. Swap -X PUT for -F file=@/path/to/file when the API docs require it.
Stop the upload handler and remove the temporary firewall rule:
ssh "$REMOTE" 'kill "$(cat /tmp/put-server.pid)" 2>/dev/null || true; rm -f /tmp/put-server.pid; firewall-cmd --remove-port=18889/tcp'Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Permission denied (publickey) |
SSH key or account access | Confirm ssh user@remote-host works; see OpenSSH authentication |
Host key verification failed |
Remote host key not in known_hosts |
SSH once interactively or use ssh-keyscan before scripting remote-to-remote copies |
scp: remote mkdir failed |
Parent directory missing on remote | ssh user@remote-host mkdir -p /dest/path before rsync or scp |
rsync: command not found on remote |
rsync not installed on one side |
Install rsync on both hosts or use scp / tar over ssh |
| Transfer hangs | Firewall, routing, or DNS | Check route and SSH port 22; see check internet connection |
scp copied to local cwd unexpectedly |
Missing : on user@host:path |
Always write user@host:/path or user@host:. for home directory |
ncat: No route to host or connection refused |
Firewall or nothing listening | Open the TCP port on the receiver; confirm ss -tlnp shows the listener |
curl: (7) Failed to connect on HTTP copy |
Server not running or port blocked | Start the HTTP service; open the port in firewalld or the cloud security group |
HTTP upload returns 405 Method Not Allowed |
Endpoint does not accept PUT |
Match the API method (POST, multipart form, or signed URL) documented by the service |
References
- OpenSSH
scp(1)manual — https://man.openbsd.org/scp.1 - OpenSSH
sftp(1)manual — https://man.openbsd.org/sftp.1 rsync(1)manual — https://download.samba.org/pub/rsync/rsync.1- Nmap Ncat file transfer — https://nmap.org/ncat/guide/ncat-file-transfer.html
- Python
http.servermodule — https://docs.python.org/3/library/http.server.html - curl manual — https://curl.se/docs/manpage.html
Summary
Copying files between Linux servers usually comes down to SSH-backed tools. Use scp when you want a single push or pull command, rsync -av when you will sync the same tree again, SFTP batch mode when a script needs explicit put and get lines, and tar piped through ssh when many small files make per-file tools slow. When SSH file tools are not the right fit, ssh with cat still encrypts a single-file push, ncat can move bytes over an open port on a trusted LAN, and curl or wget handle HTTP downloads or REST-style uploads when a web server or API is already in place.
Define REMOTE=user@host once, confirm plain ssh works before copying, and match trailing slashes on rsync paths. On modern OpenSSH, scp is still fine for one-offs even though rsync is the better default for repeat directory syncs. Treat ncat and plain HTTP as exceptions: open only the ports you need, and prefer HTTPS or SSH for sensitive data. Persistent shared folders belong to NFS or similar mount workflows, not the one-time copy commands covered here.
For deeper flag coverage, open the scp and rsync cheat sheets after you have a working transfer path.

