I Have Used scp for 30 Years and Never Knew About -3 Source

1---2title: "I Have Used scp for 30 Years and Never Knew About -3"3date: "2026-10-28"4published: true5tags: ["ssh", "scp", "linux", "security", "sysadmin"]6author: "Gavin Jackson"7excerpt: "Thirty years of copying files with scp and I only just found the -3 flag. How scp rides on ssh, the flags worth knowing, ssh-agent caching and chaining, and why the same features deserve tight limits on production servers."8---910# I Have Used scp for 30 Years and Never Knew About -31112![Blueprint-style diagram of a file moving between two servers by way of a workstation](/assets/scp/scp-hero-v3-blueprint.jpg)1314*The -3 flag in one picture: the bytes go through your workstation.*1516I have been copying files with `scp` since the mid nineties. It is one of those commands I could type in my sleep: source, destination, done. So I was genuinely surprised last week when I found out about the `-3` option, which copies a file from one remote host to another remote host, passing the data through the machine you are sitting at. Thirty years of daily use and the flag had been sitting in the man page the whole time. It was added in OpenSSH 5.7 in January 2011, so it has only been fifteen years of ignorance rather than thirty. Small comfort.1718That discovery sent me back to the man page to see what else I had missed. This post is what I found: how scp actually works, the flags worth knowing, how ssh-agent ties it all together, and why the features that make ssh so useful on your own machines are the same ones you should be switching off on production servers.1920## So what is scp, really?2122scp shipped with the original SSH in 1995 as a secure replacement for `rcp`, the Berkeley remote copy command. The important part is that scp was never really its own protocol. For most of its life, when you ran `scp file host:/path/`, your local scp simply ssh'ed to the far end, started a hidden scp process there in source or sink mode, and the two scp processes talked the old rcp wire protocol to each other inside the encrypted ssh channel. All of the hard parts, authentication, encryption, connection handling, came from ssh. scp was a thin file-copy skin over the top.2324That changed in OpenSSH 9.0 (April 2022), which switched scp to use the SFTP protocol by default instead of the legacy rcp-derived one. The old behaviour is still available with `-O` for ancient endpoints, but for anything modern, scp is now effectively an SFTP client with the classic command-line syntax.2526The practical consequence either way: everything ssh knows, scp inherits. The same `~/.ssh/config` file, the same keys, the same agent, the same jump hosts and port settings. Most of the "hidden scp features" below are really ssh features that scp gets for free.2728## The -3 flag2930The classic scenario: you are on your laptop, and you need to move a file between two servers that cannot see each other. Different networks, no direct route, no shared credentials.3132<pre><code>33scp -3 host-a:/var/log/app.log host-b:/srv/backup/34</code></pre>3536Historically, without `-3`, your local scp would connect to host-a and instruct the scp process there to write directly to host-b. The data travelled host-a to host-b over their own link: fast, but it required host-a to have network reachability to host-b and credentials to authenticate with, which usually meant private keys sitting on host-a.3738With `-3`, the bytes flow host-a to your workstation to host-b. Twice the network traffic and your own uplink becomes the bottleneck, but the two servers never need to know the other exists. Your workstation holds the only credentials, and nothing gets stored on either remote.3940Here is the twist. In OpenSSH 8.7 (August 2021), routing remote-to-remote copies through the local host became the default behaviour. On any modern install, `-3` is a no-op, and the old direct mode now lives behind the `-R` flag. OpenSSH 10.6 goes further and deprecates `-R` outright over security concerns. So the feature I had just discovered had already quietly become the standard behaviour years ago. I am not sure whether to feel behind or vindicated.4142> ## Which Behaviour Do You Have?43>44> Run `ssh -V` on your workstation. OpenSSH 8.7 or later: remote-to-remote copies already route through your machine, and `-R` restores the old direct mode if you want it. Older than 8.7: you need `-3` for the pass-through behaviour. If a server somewhere is old enough to care, `scp -O` forces the legacy protocol for talking to it.4546## Flags I should have known years ago4748The full list is in `man scp`, but these are the ones that earn their keep:4950- **-P** (capital) sets the port. Lowercase `-p` preserves modification times and mode bits. The capitalisation exists because `-p` was already taken, and it has bitten everyone exactly once.51- **-l** limits bandwidth in Kbit/s. `scp -l 8000 big.iso host:/tmp/` caps the transfer at roughly 1 MB/s. Useful on shared links, and polite in cron jobs.52- **-C** enables compression. A real win for logs, dumps and text over slow links, and a waste of CPU for already-compressed media on fast ones.53- **-r** copies directories recursively. Note that scp follows symlinks during the traversal, which occasionally surprises people copying trees full of them.54- **-B** is batch mode: never prompt for a password or passphrase. Scripts fail cleanly instead of hanging forever on a prompt nobody is watching.55- **-i**, **-o**, **-F** pass through an identity file, an arbitrary ssh option, or an alternate config file. `scp -o ProxyJump=bastion file db:/tmp/` works exactly as you would hope.56- **-J** specifies a jump host directly, with comma-separated hops for deeper chains.57- **-T** disables strict filename checking, a protection added after CVE-2019-6111, where a malicious server could trick the scp client into writing files outside the target directory. Leave it on unless an old script breaks.58- **-O** forces the legacy scp protocol, for embedded gear and old dropbear installs with no SFTP server.59- **-D** connects directly to a local sftp-server binary instead of going over ssh. Mostly a debugging tool.6061## Let the config file do the work6263If you take one thing from this post, make it this: stop typing connection details and put them in `~/.ssh/config`.6465<pre><code>66Host web167    HostName 203.0.113.1068    User deploy69    Port 222270    IdentityFile ~/.ssh/id_ed25519_web71</code></pre>7273Then `scp index.php web1:/var/www/` reads like English, and every ssh-family tool (ssh, scp, sftp, rsync, git) picks up the same settings. There is also a URI form, `scp://deploy@203.0.113.10:2222/var/www/`, which is handy when you want the whole destination in one copy-pasteable token.7475## Key caching: ssh-agent7677`ssh-agent` is a small daemon that holds your decrypted private keys in memory. You run `ssh-add ~/.ssh/id_ed25519` once after login, type the passphrase a single time, and every subsequent ssh, scp, sftp and rsync authenticates without asking again. The private key itself never leaves the agent; clients ask the agent to sign challenges rather than reading the key file.7879The knobs worth knowing:8081<pre><code>82ssh-add -l            # list loaded keys83ssh-add -t 8h ~/.ssh/id_ed25519   # forget the key after 8 hours84ssh-add -c ~/.ssh/id_ed25519      # ask for confirmation on each use85ssh-add -D            # remove all keys86</code></pre>8788Add `AddKeysToAgent yes` to your ssh config and keys load themselves into the agent on first use. The `-c` flag looks tedious but matters the moment you start forwarding your agent, which is the next topic.8990## Chaining hosts: agent forwarding and ProxyJump9192This is where it gets fun. Agent forwarding (`ssh -A`, or `ForwardAgent yes` in the config) exposes your local agent to the remote session as a socket. From the bastion, you can ssh onward to a database server using the same key that lives on your workstation. No private key file ever touches the bastion's disk, and you can chain the hops as deep as you like.9394<pre><code>95Host bastion96    HostName bastion.example.com97    ForwardAgent yes98</code></pre>99100The catch: while your session is connected, anyone with root on the bastion can use that socket to authenticate as you, to anywhere your key is accepted. They cannot steal the key, but they can borrow it for exactly as long as you are logged in. The rule of thumb is simple: never forward your agent to a host you would not hand your keys to, and use `ssh-add -c` so every borrowed use at least pops a confirmation on your screen.101102The modern alternative is `ProxyJump` (`-J`, or the `ProxyJump` config directive). The connection gets forwarded, not the agent: authentication happens end-to-end between your workstation and the final host, and the bastion just carries encrypted traffic it cannot use. For fixed paths like workstation to bastion to database, ProxyJump is the safer default, and `scp -J bastion file db:/path/` works the same way. Agent forwarding still earns its place for ad-hoc hopping across hosts you control.103104## A quick word on how good ssh is as a protocol105106Step back for a moment and look at what ssh actually is: an authenticated, encrypted transport that multiplexes logical channels over a single TCP connection. Shell sessions and command execution are just two channel types. The same connection can carry SFTP, forwarded agent sockets, X11, local and remote port forwards, a dynamic forward that turns ssh into a SOCKS proxy, and with `-w` a full layer-3 VPN between tun devices. Add `ControlMaster` and `ControlPersist` and your subsequent sessions ride over the existing connection with no re-authentication, which makes repeated scp calls effectively instant. Even the escape sequences are worth knowing: on a new line, `~.` kills a hung session and `~C` opens a command line to add port forwards mid-session.107108There is a very good visual guide to the tunnelling side of this that I saved to the [gav.ilicio.us](/gav-ilicio-us) archive a while back: [A Practical Guide to SSH Tunnels](https://labs.iximiuz.com/tutorials/ssh-tunnels). It covers local, remote and dynamic forwarding with clear diagrams, and it is the single best read on the topic I have found.109110## And this is why production servers should say no111112Everything above is a feature on your own machines and a liability on a production server. Each one is a pivot primitive: port forwarding turns a compromised account into a tunnel straight through your firewall policy, agent forwarding exposes your credentials to anyone with root on the box, and tun devices hand out network interfaces. The convenience and the attack surface are the same thing viewed from different ends.113114A reasonable baseline for a production `sshd_config`:115116<pre><code>117PermitRootLogin no118PasswordAuthentication no119AllowAgentForwarding no120AllowTcpForwarding no121X11Forwarding no122PermitTunnel no123GatewayPorts no124LogLevel VERBOSE125</code></pre>126127Two notes on that list. If a server genuinely needs TCP forwarding for something, scope it with `PermitOpen host:port` so it can only reach the one destination you intended, rather than `AllowTcpForwarding yes`. And `LogLevel VERBOSE` logs the key fingerprint used at login, which is what you want when you are later asking who connected with which key.128129For accounts that exist purely to receive files, such as backup targets, do not give them a shell at all:130131<pre><code>132Match Group sftp-only133    ForceCommand internal-sftp134    ChrootDirectory /srv/sftp/%u135    AllowTcpForwarding no136    X11Forwarding no137</code></pre>138139That gives you a working scp and sftp endpoint (SFTP-based scp included, another reason the OpenSSH 9.0 change matters) with no shell, no forwarding, and the account locked into its own directory tree.140141## Closing142143Thirty years in, and the man page still has things to teach me. The broader lesson is that scp was never a small tool with a short man page; it is the entire ssh stack wearing a copy command as a hat, and the ssh stack is deep. Next weekend's project is reading `ssh_config(5)` front to back. There is probably another `-3` in there somewhere.144145---146147**Related:**148149- 🔗 [A Practical Guide to SSH Tunnels](https://labs.iximiuz.com/tutorials/ssh-tunnels) - the visual guide to local, remote and dynamic forwarding, saved in the link archive150- 📂 [gav.ilicio.us](/gav-ilicio-us) - the rest of the saved links151- 📜 [OpenSSH release notes](https://www.openssh.org/releasenotes.html) - where the -3, -R and SFTP-default changes are recorded152