16
SSH & Remote Transfer
- Key-based SSH
- Copy file remote
- rsync deploy directory
Must-know cold
ssh user@host·ssh -i KEY user@host·ssh -p PORT user@hostscp file user@host:/path/·sftp user@host·rsync -avz src/ user@host:dest/- Keys:
ssh-keygen -t ed25519·ssh-copy-id·chmod 600key ·700.ssh - Jump:
ssh -J bastion user@internal(put jump and dest in~/.ssh/config) - Auth fail:
ssh -vv -o IdentitiesOnly=yes -i KEY user@host - Hung session: newline then
~.
Toolchain map
Definition: OpenSSH client/server plus tools that speak SSH. Six jobs, not six daemons.
| Job | Tools / files |
|---|---|
| 1 Session | ssh, -t/-T, -N, -f |
| 2 Identity | ssh-keygen, ssh-copy-id, ssh-add, ssh-keyscan, authorized_keys |
| 3 Transfer | scp, sftp, rsync -e ssh, sshfs (optional) |
| 4 Tunnels | ssh -L -R -D -J |
| 5 Config | ~/.ssh/config, known_hosts, sshd_config, systemctl ssh/sshd |
| 6 Debug | ssh -v/-vvv, escape ~., ~? |
Auth quick facts
Definition: Password vs key auth and common SSH denial causes.
| Item | Meaning |
|---|---|
| Password auth | Interactive; often disabled in prod |
| Key auth | Public key in ~/.ssh/authorized_keys on the server |
Permission denied (publickey) | Key, path, modes, user, or sshd config — not “the network is down” |
| Host key warning | known_hosts mismatch: MITM or rebuilt host — verify before deleting the line |
1. Connection & session
ssh — session
Definition: Encrypted remote login and one-shot command execution (OpenSSH client).
| Option | Argument | Meaning | Example |
|---|---|---|---|
| (none) | user@host | Interactive login shell | ssh alice@10.0.0.5 |
| (none) | user@host cmd | Run remote command and exit (no TTY unless -t) | ssh alice@host 'uptime' |
-i | KEYFILE | Identity (private key) file | ssh -i ~/.ssh/id_ed25519 alice@host |
-p | PORT | SSH server port (default 22). Not scp -P | ssh -p 2222 alice@host |
-l | USER | Login name (alt to user@) | ssh -l alice host |
-t | — | Force TTY (sudo, htop, pagers) | ssh -t host sudo -i |
-T | — | Disable TTY | Scripts / git |
-N | — | No remote command — tunnels only | With -L/-R/-D |
-f | — | Background just before the remote command (after prompts; implies -n) | ssh -fN -L … |
-n | — | stdin from /dev/null | Background / scripts |
-q | — | Quiet | Scripts |
-4 / -6 | — | Force IPv4/IPv6 | |
-o | OPT=VAL | Override client config | ssh -o ConnectTimeout=5 host |
-o BatchMode=yes | — | No prompts (password, passphrase, host-key) | CI |
-o ConnectTimeout= | SEC | TCP connect timeout only — not auth | ssh -o ConnectTimeout=5 host |
Common -o options
| Option | Meaning |
|---|---|
IdentitiesOnly=yes | Do not offer extra agent keys; still uses defaults plus -i / IdentityFile |
PasswordAuthentication=no | Refuse the password method only — keyboard-interactive can still ask |
KbdInteractiveAuthentication=no | Refuse keyboard-interactive; pair with the row above for “no password prompt” |
StrictHostKeyChecking=yes / no / accept-new | Host key policy (ask is the usual default). Prefer accept-new carefully |
User= / Port= / ProxyJump= | Same meaning as ~/.ssh/config |
Flag combos
| Combo | Meaning |
|---|---|
ssh -i key -p 2222 user@host | Key + non-22 port |
ssh -t user@host 'sudo systemctl status nginx' | One-shot that needs a TTY |
ssh -o BatchMode=yes -o ConnectTimeout=5 user@host 'true' | Scripted reachability |
2. Identity & keys
ssh-keygen
Definition: Create and inspect key pairs. File modes are enforced by
ssh/sshd, not by this tool.
| Option | Argument | Meaning | Example |
|---|---|---|---|
-t | ed25519 / rsa | Type. Prefer ed25519 | ssh-keygen -t ed25519 |
-C | COMMENT | Label (user@laptop, not a secret) | ssh-keygen -t ed25519 -C 'agent' |
-f | FILE | Path (writes FILE + FILE.pub) | ssh-keygen -t ed25519 -f ~/.ssh/agent |
-N | '' | Empty passphrase (automation; the private key is the secret) | Scripts only |
-l | -f KEY | Fingerprint (public or private) | ssh-keygen -l -f ~/.ssh/id_ed25519.pub |
Authorize the server: append the .pub line to ~USER/.ssh/authorized_keys (one line, no wrap). ssh-copy-id does that over an existing login.
Modes (sshd StrictModes, client too):
chmod 700 ~/.sshchmod 600 ~/.ssh/authorized_keyschmod 600private keychmod 600 ~/.ssh/config(ignored if group/other-writable)chmod 644*.pubis fine
ssh-copy-id
Definition: Append a local public key to remote
authorized_keysusing a login that already works (password or another key).
| Option | Argument | Meaning | Example |
|---|---|---|---|
-i | KEY.pub | Public key to install | ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host |
-p | PORT | Remote SSH port (ssh -p, not scp -P) | ssh-copy-id -p 2222 user@host |
ssh-add
Definition: Load private keys into
ssh-agentso you type the passphrase once per agent lifetime.
| Option | Argument | Meaning | Example |
|---|---|---|---|
| (none) | KEY | Add private key | ssh-add ~/.ssh/id_ed25519 |
-l | — | List fingerprints in the agent | ssh-add -l |
-L | — | List public keys in the agent | ssh-add -L |
-d | KEY | Remove one identity | ssh-add -d ~/.ssh/id_ed25519 |
-D | — | Delete all identities from this agent | ssh-add -D |
Needs a running agent (SSH_AUTH_SOCK). On a desktop session you usually already have one. ssh-add -l → “Could not open a connection to your authentication agent” means there is no agent, not a bad key.
ssh-keyscan
Definition: Fetch a server’s host keys (not user keys) for
known_hosts. Does not log in.
| Option | Argument | Meaning | Example |
|---|---|---|---|
| (none) | HOST | Print host keys | ssh-keyscan 10.0.0.5 |
-p | PORT | Non-22 | ssh-keyscan -p 2222 host |
-t | TYPES | Key types | ssh-keyscan -t ed25519,rsa host |
-H | — | Hash hostnames in the output | Safer known_hosts lines |
-f | FILE | Hosts listed in FILE | Bootstrap |
Compare fingerprints with an out-of-band source before appending. Blind ssh-keyscan >> known_hosts trusts the network the first time, same as StrictHostKeyChecking=accept-new.
3. File transfer
scp
Definition: Copy files over SSH. OpenSSH ≥ 9.0 uses SFTP under the hood;
-Ois the legacy scp protocol if you need it.
| Option | Argument | Meaning | Example |
|---|---|---|---|
| (none) | SRC DEST | Copy | scp f user@host:/tmp/ |
-r | SRC DEST | Recursive. Follows symlinks | scp -r dir user@host:/opt/ |
-i | KEY | Identity | scp -i key f user@host: |
-P | PORT | Port (capital P) | scp -P 2222 f user@host: |
-p | — | Preserve mtimes/modes (not owner) | scp -p f user@host: |
-C | — | Compression | |
-q / -v | — | Quiet / verbose | |
-o | OPT | Pass-through to ssh | scp -o ProxyJump=bastion f user@int: |
-3 | — | Remote↔remote via the local host (default since OpenSSH 9.0) | Older clients need this flag |
-R | — | Direct remote↔remote (OpenSSH 9.0+; origin must auth to dest) | Opposite of -3 |
Directions: scp local user@host:remote · scp user@host:remote local · scp user@h1:a user@h2:b
scp does not use rsync’s trailing-slash rule: scp -r dir/ still copies the directory.
sftp
Definition: Interactive file protocol over SSH (ls/put/get/cd). Same auth as
ssh. Port flag is-P(likescp).
| Command / option | Meaning | Example |
|---|---|---|
sftp user@host | Open a session (home on remote) | |
-i / -P / -o / -J | Same idea as ssh/scp | sftp -P 2222 -i key user@host |
ls / cd / pwd | Remote listing (sftp’s own) | |
lls / lcd / lpwd | Local listing | |
put local [remote] | Upload | put app.conf /etc/app/ |
get remote [local] | Download | get /var/log/app.log |
mkdir / rm / rmdir / rename | Remote names | |
-b FILE | Batch commands from a file (no prompt) | sftp -b cmds.txt user@host |
Quit with bye / exit / quit. For trees and deltas, prefer rsync.
rsync
Definition: Delta copy, local or over SSH. Default remote shell is
sshwhen the dest isuser@host:path.
| Option | Argument | Meaning | Example |
|---|---|---|---|
| (none) | SRC DEST | Sync (trailing / matters) | rsync -a src/ dest/ |
-a | — | Archive (-rlptgoD). Usual start — not rsync’s default | |
-v | — | Verbose | rsync -av src/ dest/ |
-z | — | Compress in transit | rsync -avz src/ host:dest/ |
-n / --dry-run | — | Show what would happen | Always before --delete |
-h | — | Human numbers | |
--delete | — | Delete extra files in dest dirs being synced | Dangerous — mirror |
--progress | — | Per-file progress | rsync -av --progress src/ dest/ |
-P | — | --partial --progress (not port) | rsync -avP src/ dest/ |
-e | ssh cmd | Remote shell (port/key live here) | rsync -e 'ssh -i key -p 2222' src/ host:dest/ |
--exclude | PAT | Skip | rsync -a --exclude '.git' src/ dest/ |
-x | — | Don’t cross filesystem boundaries | |
-u | — | Skip files newer on receiver |
Trailing slash
| Form | Meaning |
|---|---|
rsync -a src/ dest/ | Copy contents of src into dest |
rsync -a src dest/ | Copy directory src into dest as dest/src |
sshfs
Definition: Mount a remote directory on the local filesystem (FUSE + SFTP). Optional package (
sshfs/fuse-sshfs), not part of OpenSSH itself.
| Command | Meaning | Example |
|---|---|---|
sshfs user@host:remote localmnt | Mount | sshfs alice@host:/opt/app /mnt/app |
sshfs -p PORT -o IdentityFile=KEY … | Port is -p (like ssh, not scp -P) | |
fusermount -u / fusermount3 -u | Unmount (Linux FUSE; name varies) | fusermount3 -u /mnt/app |
umount localmnt | Works if the mount is registered and you own it |
Needs FUSE, a mountpoint directory, and the same auth as ssh. A dropped SSH session leaves a stuck mount — unmount, then remount. For deploys, rsync is the usual tool; sshfs is for interactive browsing.
4. Tunnels, forwarding, jump hosts
ssh — tunnels
Definition: The SSH client can listen and proxy TCP (and a SOCKS listener) through the encrypted session.
HOSTin-L/-Ris not “your laptop” by default.
| Option | Argument | Meaning | Example |
|---|---|---|---|
-L | LPORT:HOST:RPORT | Listen locally. HOST:RPORT is reached from the SSH server. 127.0.0.1 = the remote | ssh -N -L 8080:127.0.0.1:80 user@bastion |
-R | RPORT:HOST:PORT | Listen on the server. HOST:PORT is reached from the client | ssh -N -R 9000:127.0.0.1:3000 user@host |
-D | PORT | Local SOCKS dynamic forward | ssh -N -D 1080 user@host |
-J | jump | ProxyJump — hop via bastion | ssh -J bastion user@internal |
-N | — | No remote shell (keep the tunnel only) | Pair with -L/-R/-D |
-f | — | Background after auth | ssh -fN -L … |
-A | — | Agent forwarding — the remote can use your agent. Avoid unless required |
-L use: you cannot reach bastion:80 from here, but bastion can. Local http://127.0.0.1:8080 is bastion’s port 80.
-R use: a process on the server connects to server:9000 (loopback by default) and SSH brings that back to your 127.0.0.1:3000. To listen on all remote interfaces you need both ssh -R *:9000:127.0.0.1:3000 (or 0.0.0.0:…) and GatewayPorts enabled on sshd. GatewayPorts alone does not change a loopback -R.
-D use: point a browser/tool at socks5://127.0.0.1:1080 (OpenSSH speaks SOCKS4 and SOCKS5). Traffic egresses from the SSH server. Not a VPN (no UDP, no TUN — that is ssh -w, out of scope here).
-J use: ssh -J alice@bastion bob@10.0.0.8. -i / most -o apply to the destination, not the jump. Put each hop in ~/.ssh/config.
5. Config & server
ssh config
Definition: Client defaults in
~/.ssh/config. For each keyword, the first obtained value is kept. LaterHost/Matchblocks may still fill keywords that earlier blocks left unset — put specific hosts aboveHost *.
| Keyword | Argument | Example line |
|---|---|---|
Host | alias / pattern | Host agent1 · Host agent-* |
HostName | DNS or IP | HostName 10.0.0.5 |
User | login | User jenkins |
IdentityFile | private key | IdentityFile ~/.ssh/id_ed25519 |
IdentitiesOnly | yes | IdentitiesOnly yes |
Port | N | Port 2222 |
ProxyJump | bastion | ProxyJump bastion |
ForwardAgent | no (prefer) | |
ServerAliveInterval | seconds | Keep NAT mappings |
Host bastion
HostName bastion.corp
User alice
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
Host agent-*
User jenkins
ProxyJump bastion
IdentityFile ~/.ssh/agent_key
IdentitiesOnly yes
Then: ssh bastion · ssh agent-01.
Host keys, sshd, systemd
Definition: Who you are talking to (
known_hosts), who may log in (authorized_keys), howsshdlistens (sshd_config).
| Path | Side | Role |
|---|---|---|
~/.ssh/config | client | Aliases, keys, jump, ports |
~/.ssh/known_hosts | client | Host key fingerprints (MITM check) |
~/.ssh/id_ed25519 + .pub | client | User key pair |
~/.ssh/authorized_keys | server (per user) | Public keys allowed to log in as that user |
/etc/ssh/ssh_config | client (system) | Defaults for all users |
/etc/ssh/sshd_config | server | Port, PermitRootLogin, PasswordAuthentication, AllowUsers, AuthorizedKeysFile |
/etc/ssh/ssh_host_* | server | Host keys (what ssh-keyscan prints) |
Debian/Ubuntu unit is often ssh.service (alias sshd). RHEL family is sshd.service.
| Command | Meaning |
|---|---|
sudo systemctl status ssh / sshd | Is the daemon up? |
sudo systemctl reload ssh | Re-read sshd_config (preferred over restart) |
sudo sshd -t | Config syntax check before reload |
sudo sshd -T | Dump effective config (-C user,host,addr if you use Match) |
Reload, do not blindly restart, if you only have this SSH session.
6. Debug & audit
ssh — debug
Definition: Client verbosity, host-key policy, and in-session escape characters.
| Option / key | Meaning | Example |
|---|---|---|
-v | Auth and config outline | ssh -v user@host |
-vv / -vvv | KEX, keys offered, which file matched | ssh -vvv -i key user@host |
-vv -o IdentitiesOnly=yes -i key | Stop “Too many authentication failures” from extra agent keys | |
~. | Disconnect (tilde at start of line, after Enter) | Interactive pty sessions — not ssh -T / typical ssh -N / -f |
~? | List escape sequences | Enter, then ~? |
~# | List forwarded connections | |
~C | Escape command line (add/cancel forwards) | Off by default unless EnableEscapeCommandline yes |
EscapeChar default is ~. It is only special at the beginning of a line and when a pty was allocated. Mid-line ~. is just characters. Background -f tunnels have no stdin for this — kill the client pid. man ssh “ESCAPE CHARACTERS”.
Server-side audit (this host as the SSH server): journalctl -u ssh / -u sshd, ss -tlnp | grep :22.
Flag combos
| Combo | Meaning |
|---|---|
ssh -v -o IdentitiesOnly=yes -i key user@host | Debug this key only |
ssh -vvv -p 2222 user@host | Port + protocol dump |
Common recipes
| Goal | Command |
|---|---|
| Login | ssh user@host |
| Key login | ssh -i ~/.ssh/id_ed25519 user@host |
| Remote command | ssh user@host 'df -h; uptime' |
| Debug auth | ssh -vv -o IdentitiesOnly=yes -i key user@host |
| Copy file up | scp ./app.conf user@host:/etc/app/ |
| Copy down | scp user@host:/var/log/app.log . |
| Interactive files | sftp user@host |
| Sync dir | rsync -avz ./build/ user@host:/opt/app/ |
| Dry-run sync | rsync -avn ./build/ user@host:/opt/app/ |
| Via bastion | ssh -J bastion user@internal |
| Local forward | ssh -N -L 8080:127.0.0.1:80 user@bastion |
| SOCKS | ssh -N -D 1080 user@host |
| New key | ssh-keygen -t ed25519 -C 'laptop' -f ~/.ssh/id_ed25519 |
| Install pubkey | ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host |
| Agent | ssh-add -l · ssh-add ~/.ssh/id_ed25519 |
| Hung session | Enter, then ~. |
Pitfalls
- Port letters:
ssh/ssh-copy-id/ssh-keyscan/sshfsuse-p.scp/sftpuse-P.scp -pis preserve.rsync -Pis--partial --progress. Modernssh -P tagis a config tag, not a port. scp -rfollows symlinks.rsync -acopies the link itself.scpdoes not use rsync’s trailing-slash rule.--deletecan wipe remote — dry-run first.- Private key or
~/.ssh/configgroup/other-writable → client ignores them.authorized_keystoo open →sshdignores it (StrictModes). PasswordAuthentication=nodoes not block keyboard-interactive prompts.- Remote
sudooften needsssh -t. -i/ most-owith-Japply to the destination, not the jump — use~/.ssh/configperHost.-L127.0.0.1is the SSH server’s loopback.-R127.0.0.1is your loopback.- Agent forwarding (
-A) lets a compromised remote use your keys. PreferProxyJump. - Host key change: verify rebuild vs MITM before editing
known_hosts. ssh-keyscan >> known_hostswithout checking the fingerprint is TOFU, not a second factor.sshfsis not OpenSSH; a dead connection needs an unmount. Not a deploy tool.- Escape
~.only at the start of a line, and only if a pty was allocated. It will not save assh -fNtunnel.~CneedsEnableEscapeCommandline yeson current OpenSSH. - Debian
systemctl restart sshvs RHELsshd—systemctl status 'ssh*'.
For more details, try man <command> in your terminal.