Cheatsheets:LinuxCLI/ssh
Quick reference for smart people — part of our Linux cheatsheets collection.
The everyday OpenSSH client: log into a remote machine, run one-off commands, copy files, and tunnel ports over an encrypted channel. Everything here is typed on your local Linux machine; the remote side only needs a running sshd.
New to SSH? Every command has a built-in manual — try man ssh, man ssh_config or tldr ssh for short examples.
Basic example: set up key login
ssh user@host # first connection: type the password
ssh-keygen -t ed25519 -C "user@laptop" # create ~/.ssh/id_ed25519 and its .pub
ssh-copy-id user@host # install the public half on the server
ssh user@host # no password from now on
The private key never leaves your machine; the server only stores the public half, one key per line, in ~/.ssh/authorized_keys.
Connecting
ssh host # same username as your local one
ssh user@host # log in as another user
ssh -p 2222 user@host # non-standard port
ssh user@host uptime # run one command instead of a shell
ssh user@host 'df -h; free -h' # several commands: quote them for the remote shell
ssh -t user@host tmux attach # -t: force a TTY, for interactive programs
ssh -J bastion user@host # jump through bastion (ProxyJump)
ssh -J user@bastion:2222 user@host # jump host with its own user and port
ssh -o ProxyCommand="ssh -W %h:%p bastion" user@host # same, on OpenSSH < 7.3
ssh -v user@host # debug the connection; -vv and -vvv go further
| Flag | Effect |
|---|---|
-p port |
connect to a port other than 22 |
-i key |
authenticate with a specific private key |
-t / -T |
force / disable pseudo-terminal allocation |
-N |
no remote command: forward ports only |
-f |
go to the background just before running the command |
-J host |
connect via a jump host; comma-separated for several |
-L / -R / -D |
port forwarding (see below) |
-A |
forward your local ssh-agent to the server (see the caution below) |
-o OPT=value |
set any ssh_config option on the command line |
Escape sequences are recognised only at the start of a line, inside an interactive session:
| Escape | Action |
|---|---|
~. |
disconnect |
~^Z |
suspend ssh, back to the local shell |
~# |
list the forwarded connections |
~C |
command line: add -L, -R or -D to the live session
|
~? |
list all escapes |
Keys
ssh-keygen -t ed25519 # new key in ~/.ssh/id_ed25519 (+ .pub)
ssh-keygen -t ed25519 -f ~/.ssh/id_work -C "deploy@laptop" # a named key, with a comment
ssh-keygen -t rsa -b 4096 # RSA, when a server is too old for ed25519
ssh-keygen -p -f ~/.ssh/id_ed25519 # change the passphrase
ssh-keygen -y -f ~/.ssh/id_ed25519 # print the public half of a private key
ssh-keygen -l -f ~/.ssh/id_ed25519.pub # fingerprint of a key
ssh-keygen -R host # forget a host's key in known_hosts
ssh-copy-id user@host # install the default key on a server
ssh-copy-id -i ~/.ssh/id_work.pub user@host # install one specific key
ssh -i ~/.ssh/id_work user@host # use a key without touching any config
Permissions are enforced:
sshsilently ignores a private key that anyone else can read. Keep~/.sshat700and private keys at600(chmod 600 ~/.ssh/id_ed25519). Protect the key with a passphrase and load it into an agent rather than leaving it unencrypted.
~/.ssh/config
The place to turn a long command line into a name you can type.
Host web # `ssh web` connects as deploy@203.0.113.10 on port 2222
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_work
Host *.internal.example.com
ProxyJump bastion
Host legacy
HostKeyAlgorithms +ssh-rsa # for old servers only
PubkeyAcceptedAlgorithms +ssh-rsa
Host * # global defaults: keep this block LAST
ServerAliveInterval 30 # ping every 30 s, so idle NAT and firewall sessions survive
ServerAliveCountMax 3
AddKeysToAgent yes # load a key into the agent on first use
ControlMaster auto # reuse one TCP connection for every later session
ControlPath ~/.ssh/cm/%C
ControlPersist 10m
mkdir -p ~/.ssh/cm # ControlPath does not create the directory
ssh -G web # print the configuration and options actually used
ssh -F ~/.ssh/otherconf host # use an alternative config file
ssh -o StrictHostKeyChecking=accept-new host # accept an unknown host key once
ssh -O check host # is a shared connection open?
ssh -O exit host # close it, and every session using it
For each option, the first value found wins. Put the specific
Hostblocks at the top of the file and the catch-allHost *at the bottom, or the defaults will shadow the specifics.IdentityFileis the exception: every entry is tried in order.
Connection sharing (the
ControlMasterblock) makes the second and later connections to a host start instantly, because they reuse the first, already-authenticated one; laterscp,rsyncandgitcalls to that host skip the handshake too.
Copying files
scp file1.txt user@host:/tmp/ # upload
scp user@host:/tmp/file1.txt . # download to the current directory
scp -r dir/ user@host:/tmp/ # a whole directory
scp -P 2222 file1.txt user@host:/tmp/ # note: -P (capital) is scp's port flag
scp 'user@host:/var/log/*.log' . # quote the remote glob
rsync -avz --progress dir/ user@host:/tmp/ # only what changed, resumable
sftp user@host # interactive: ls, get, put, bye
scphas spoken SFTP since OpenSSH 9.0, and it reads the same~/.ssh/configblocks:scp web:file1.txt .works onceHost webexists. For large or repeated transfers preferrsync, which transfers only the differences and can resume — it must be installed on both machines.
Port forwarding
ssh -L 8080:localhost:80 user@host # local 8080 → port 80, as seen from the server
ssh -L 5432:db.internal:5432 user@host # reach a host only the server can reach
ssh -R 9000:localhost:3000 user@host # the server's 9000 → your local 3000
ssh -D 1080 user@host # SOCKS5 proxy on localhost:1080
ssh -N -f -L 8080:localhost:80 user@host # tunnel only, in the background
ssh -O exit user@host # close the backgrounded tunnel
| Flag | Listens on | Traffic goes to |
|---|---|---|
-L 8080:host:80 |
your machine, port 8080 | host, as resolved from the server
|
-R 9000:host:3000 |
the server, port 9000 | host, as resolved from your machine
|
-D 1080 |
your machine, port 1080 | whatever the SOCKS client asks for |
The same forwardings in config, for a tunnel you start by name:
Host dbtunnel
HostName db.example.com
LocalForward 5432 localhost:5432 # 5432 as seen from the server
SessionType none # no shell, just the tunnel (OpenSSH 8.4+)
-Llistens on your machine,-Ron the server's. Both bind to loopback by default, so only that machine can reach the port — exposing it to a network needs an explicit bind address (-L 0.0.0.0:8080:localhost:80), and ports below 1024 require root.
Agent
eval "$(ssh-agent -s)" # start an agent in this shell
ssh-add ~/.ssh/id_ed25519 # load a key; asks for the passphrase once
ssh-add -t 8h ~/.ssh/id_ed25519 # ... dropping it again after 8 hours
ssh-add -l # fingerprints of the loaded keys
ssh-add -L # full public keys
ssh-add -d ~/.ssh/id_ed25519 # remove one key
ssh-add -D # remove every key
ssh-agent -k # kill this agent
ssh -A user@host # forward the agent to the server
ssh-add -c ~/.ssh/id_ed25519 # ...or make every use of a key prompt for confirmation
Avoid
ssh -Aon hosts you do not fully trust: anyone able to read the agent socket there (root, mostly) can authenticate as you for as long as the session lasts, without ever seeing the key. Prefer-J(ProxyJump), where authentication stays end-to-end.AddKeysToAgent yesin~/.ssh/configloads keys into the agent on first use.
Troubleshooting
| Symptom | Fix |
|---|---|
Permission denied (publickey) |
the key is not installed: ssh-copy-id user@host; check chmod 600 ~/.ssh/id_ed25519; ssh -v lists the keys the client offers
|
Too many authentication failures |
the agent offers more keys than the server allows attempts: add IdentitiesOnly yes to that host's block
|
REMOTE HOST IDENTIFICATION HAS CHANGED! |
the server's key really did change — confirm with its admin before running ssh-keygen -R host
|
Connection refused |
wrong port, sshd down, or a firewall: test with nc -vz host 22
|
Connection timed out |
nothing answers at all: a firewall is dropping the packets, or the host is unreachable |
| the session freezes after an idle spell | ServerAliveInterval 30 in the config file
|
| which key, which options, which file? | ssh -G host prints the effective configuration, ssh -vvv host everything the client tries
|
For more
man ssh,man ssh_config,man ssh-keygen,man sshd_config— the built-in manuals, always installed- ssh(1) — every client flag, escape sequence and environment variable
- ssh_config(5) — every client option, including the forwarding and multiplexing ones
- ssh-keygen(1) — certificates, FIDO keys, key format conversion
- sshd_config(5) — the server side:
authorized_keys,PermitRootLogin,PasswordAuthentication - OpenSSH — release notes and security advisories
- tldr — community-maintained short examples
- explainshell.com — annotate any command line