Cheatsheets:LinuxCLI/ssh: Difference between revisions
Remove stray MCP-footer line (Wiki: wikibase.ronzz.org) |
No edit summary |
||
| Line 1: | Line 1: | ||
{{DesktopLinux}} | {{DesktopLinux}} | ||
Common OpenSSH client ops: 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 <code>sshd</code>. | |||
New to SSH? Every command has a built-in manual — try < | New to SSH? Every command has a built-in manual — try <code>man ssh</code>, <code>man ssh_config</code> or [https://tldr.sh tldr ssh] for short examples. | ||
== | == Set up key login == | ||
<syntaxhighlight lang="bash" copy> | <syntaxhighlight lang="bash" copy> | ||
ssh user@host # first connection: type the password | ssh user@host # first connection: type the password, or for most cloud providers, via the web console shell | ||
ssh-keygen -t ed25519 -C "user@laptop" # create ~/.ssh/id_ed25519 and its .pub | 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-copy-id user@host # install the public half on the server | ||
| Line 14: | Line 14: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
The private key never leaves your machine; the server only stores the public | <blockquote> | ||
user: your username on the remote machine | |||
host: IP address or URL of the remote machine | |||
</blockquote> | |||
The private key never leaves your machine; the server only needs to stores the public key, one key per line, in <code>~/.ssh/authorized_keys</code>. | |||
== Connecting == | == Connecting == | ||
<syntaxhighlight lang="bash" copy> | <syntaxhighlight lang="bash" copy> | ||
ssh user@host # log in as user on host | |||
ssh user@host # log in as | |||
ssh -p 2222 user@host # non-standard port | ssh -p 2222 user@host # non-standard port | ||
ssh user@host | ssh -i ~/.ssh/my_key user@host # use my_key as private key | ||
ssh user@host ' | ssh user@host command # run one command instead of spawning a shell | ||
ssh -t user@host tmux attach # -t: force a TTY, for interactive programs | ssh user@host -N # no remote command: forward ports only | ||
ssh user@host 'command 1; command 2' # several commands: quote and separate commands by ; | |||
ssh -J | ssh -t user@host tmux attach # -t: force a TTY, for interactive programs. Opposite is -T | ||
ssh -o | ssh -J user1@proxy:2222 user2@host # -J*: proxy jump, connects to host as user2, via the intermediate (proxy) machine on port 2222 | ||
ssh -o StrictHostKeyChecking=accept-new user@host # accept an unknown host key once | |||
ssh -G host # print effective configurations for a given host | |||
ssh -v user@host # debug the connection; -vv and -vvv go further | ssh -v user@host # debug the connection; -vv and -vvv go further | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<blockquote> | |||
*: This may be useful when you need to connect to a remote DB, etc. behind a firewall that is only accessible via machines on the local network. You first connect to a proxy machine on that network which accepts ssh from other networks, then ssh to the final destination via the proxy. | |||
</blockquote> | |||
== Keys == | == Keys == | ||
| Line 74: | Line 46: | ||
ssh-keygen -t ed25519 # new key in ~/.ssh/id_ed25519 (+ .pub) | 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 ed25519 -f ~/.ssh/id_work -C "deploy@laptop" # a named key, with a comment | ||
ssh-keygen -t rsa -b 4096 # RSA | ssh-keygen -t rsa -b 4096 # use RSA instead of ed25519 for legacy compatibility | ||
ssh-keygen -p -f ~/.ssh/id_ed25519 # change the passphrase | 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 -y -f ~/.ssh/id_ed25519 # print the public half of a private key | ||
| Line 81: | Line 53: | ||
ssh-copy-id user@host # install the default key on a server | 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-copy-id -i ~/.ssh/id_work.pub user@host # install one specific key | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<blockquote> | <blockquote> | ||
'''<code>ssh</code> enforces key permissions:''' It will not accept a private key where any other user has read permission. Keep <code>~/.ssh</code> at <code>700</code> and private keys at <code>600</code> (<code>chmod 600 ~/.ssh/id_ed25519</code>). | |||
</blockquote> | |||
<blockquote> | |||
Good security practice: Always protect your private keys with a passphrase. | |||
</blockquote> | </blockquote> | ||
== ~/.ssh/config == | == ~/.ssh/config == | ||
For frequently accessed machines, set up a connection profile in `~/.ssh/config` on your local machine. | |||
<syntaxhighlight lang="text" copy> | <syntaxhighlight lang="text" copy> | ||
Host | Host mySSHRemote | ||
HostName 203.0.113.10 | HostName 203.0.113.10 | ||
User deploy | User deploy | ||
| Line 100: | Line 75: | ||
Host *.internal.example.com | Host *.internal.example.com | ||
ProxyJump | ProxyJump mySSHRemote | ||
Host | Host myLegacyRemote | ||
HostKeyAlgorithms +ssh-rsa # for old servers | HostKeyAlgorithms +ssh-rsa # for old servers | ||
PubkeyAcceptedAlgorithms +ssh-rsa | PubkeyAcceptedAlgorithms +ssh-rsa | ||
| Line 110: | Line 85: | ||
ServerAliveCountMax 3 | ServerAliveCountMax 3 | ||
AddKeysToAgent yes # load a key into the agent on first use | AddKeysToAgent yes # load a key into the agent on first use | ||
ControlMaster auto # reuse one TCP connection for every later session | ControlMaster auto # reuse one TCP connection for every later session to the | ||
ControlPath ~/.ssh/cm | ControlPath ~/.ssh/cm-%r@%h:%p # save the TCP connections to the specified path, with automatic naming. %r: remote user, %h: host name %p: port | ||
ControlPersist 10m | ControlPersist 10m # cache TCP connection for 10 min after disconnection | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<syntaxhighlight lang="bash" copy> | <syntaxhighlight lang="bash" copy> | ||
mkdir -p ~/.ssh/cm # ControlPath does not create the directory | mkdir -p ~/.ssh/cm # ControlPath does not create the directory | ||
ssh -F ~/.ssh/otherconf host # use an alternative config file | ssh -F ~/.ssh/otherconf host # use an alternative config file | ||
ssh -O check host # is a shared connection open? | ssh -O check host # is a shared connection open? | ||
ssh -O exit host # close | ssh -O exit host # close shared connection, and every session using it | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<blockquote> | <blockquote> | ||
'''For each option, the first value found wins.''' | '''For each option, the first value found wins.''' Hence the catch-all <code>Host *</code> should always be at the bottom. | ||
</blockquote> | </blockquote> | ||
== Encrypted ssh keys: <code>ssh-agent</code> == | |||
If you use encrypted ssh keys, <code>ssh-agent</code> allow you to cache those keys unencrypted in RAM over one login session on local machine, so you do not have to provide the passphrase on each use. | |||
<syntaxhighlight lang="bash" copy> | |||
eval "$(ssh-agent -s)" # start an agent in this shell | |||
ssh-add ~/.ssh/id_ed25519 # load a key. If key encryped, asks for the passphrase once | |||
ssh-add -t 8h ~/.ssh/id_ed25519 # -t: automatically drop the key after 8 hours | |||
ssh-add -l # print fingerprints of the loaded keys | |||
ssh-add -L # print full public keys of the loaded keys | |||
ssh-add -d ~/.ssh/id_ed25519 # remove one key | |||
ssh-add -D # remove every key | |||
ssh-agent -k # kill this agent | |||
</syntaxhighlight> | |||
<blockquote> | <blockquote> | ||
<code>AddKeysToAgent yes</code> in <code>~/.ssh/config</code> loads keys into the agent on first use. | |||
</blockquote> | </blockquote> | ||
| Line 136: | Line 124: | ||
<syntaxhighlight lang="bash" copy> | <syntaxhighlight lang="bash" copy> | ||
scp file1.txt user@host:/tmp/ # upload | scp file1.txt user@host:/tmp/ # upload | ||
scp user@host:/tmp/ | scp -P 2222 file1.txt user@host:/tmp/ # note: scp's port flag is -P, NOT -p | ||
scp | scp user@host:~/file1.txt /tmp/ # download a file | ||
scp 'user@host:/var/log/*.log' . # download multiple files matching the glob: remember to quote remote path when * is used | |||
scp 'user@host:/var/log/*.log' . # quote | scp -r dir/ user@host:~/folder1 # download a whole directory | ||
rsync -avz --progress dir/ user@host:/tmp/ # | rsync -avz --progress dir/ user@host:/tmp/ # sync changes | ||
sftp user@host # interactive: ls, get, put, bye | sftp user@host # interactive: ls, get, put, bye | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<blockquote> | <blockquote> | ||
Note <code>rsync</code> must be installed both on your local machine and the remote server to work. | |||
</blockquote> | |||
<blockquote> | |||
Prefer <code>rsync</code> for large or repeated transfers, as it transfers only the differences and can resume when transfer is interrupted. | |||
</blockquote> | </blockquote> | ||
| Line 151: | Line 143: | ||
<syntaxhighlight lang="bash" copy> | <syntaxhighlight lang="bash" copy> | ||
ssh -L 8080:localhost:80 user@host # local 8080 | ssh -L 8080:localhost:80 user@host # local 8080 forwards to host port 80 | ||
ssh -L 5432:db.internal:5432 user@host # | ssh -L 5432:db.internal:5432 user@host # local 5432 forwards to host, which then forwards to db.internal 5432 (proxy connect) | ||
ssh -R 9000:localhost:3000 user@host # the server's 9000 | ssh -R 9000:localhost:3000 user@host # reverse forward: the server's 9000 forwards to your local 3000 | ||
ssh -D 1080 user@host # SOCKS5 proxy on localhost:1080 | ssh -D 1080 user@host # make host accessible as a SOCKS5 proxy on localhost:1080 | ||
ssh -N -f -L 8080:localhost:80 user@host # tunnel only, | ssh -N -f -L 8080:localhost:80 user@host # background tunnel only, no interactive shell | ||
ssh -O exit user@host # close the backgrounded tunnel | ssh -O exit user@host # close the backgrounded tunnel | ||
</syntaxhighlight> | </syntaxhighlight> | ||
The same forwardings in config, for a tunnel you start by name: | The same forwardings in config, for a tunnel you start by name: | ||
| Line 174: | Line 156: | ||
Host dbtunnel | Host dbtunnel | ||
HostName db.example.com | HostName db.example.com | ||
LocalForward 5432 localhost:5432 # | LocalForward 5432 localhost:5432 # -L 8080:localhost:80 by default for `ssh dbtunnel` | ||
SessionType none # | SessionType none # background tunnel only | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Escape sequences == | |||
{| class="wikitable" | |||
! Escape !! Action | |||
|- | |||
| <code>~.</code> || disconnect | |||
|- | |||
| <code>~^Z</code> || suspend ssh, back to the local shell | |||
|- | |||
| <code>~#</code> || list the forwarded connections | |||
|- | |||
| <code>~C</code> || open ssh command line: add <code>-L</code>, <code>-R</code> or <code>-D</code> to the live session | |||
|- | |||
| <code>~?</code> || list all escapes | |||
|} | |||
<blockquote> | <blockquote> | ||
Inside an interactive session, escape sequences are recognised only at the start of a line. | |||
</blockquote> | </blockquote> | ||
<blockquote> | |||
<code>~C</code> is disabled by default. To enable, add to `~/.ssh/config`: | |||
<syntaxhighlight lang=" | <syntaxhighlight lang="text" copy> | ||
Host * | |||
EnableEscapeCommandline yes | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</blockquote> | </blockquote> | ||
| Line 209: | Line 194: | ||
! Symptom !! Fix | ! Symptom !! Fix | ||
|- | |- | ||
| < | | <code>Permission denied (publickey)</code> || Incorrect key or key-host/user mismatch: run <code>ssh -vvv user@host</code> for more details | ||
|- | |- | ||
| < | | <code>Too many authentication failures</code> || the agent offers more keys than the server allows attempts: add <code>IdentitiesOnly yes</code> to that host's block to only use keys explicitely configured for that host | ||
|- | |- | ||
| < | | <code>REMOTE HOST IDENTIFICATION HAS CHANGED!</code> || the server's key changed: this could either be a server-side reset or a man-in-the-middle attack: confirm with the server's admin whether a legitimate reset took place before running <code>ssh-keygen -R host:port</code> to accept the change | ||
|- | |- | ||
| < | | <code>Connection refused</code> || wrong port, <code>sshd</code> down, or a firewall: test with <code>nc -vz host 22</code> | ||
|- | |- | ||
| <code>Connection timed out</code> || nothing answers at all: a firewall is dropping the packets, or the host is unreachable | |||
|- | |- | ||
| | | the session freezes after some idle || consider adding <code>ServerAliveInterval 60</code> in the config file to keep the session alive by sending keep-alive signals every 60s | ||
|} | |} | ||
== For more == | == For more == | ||
* [https://www.man7.org/linux/man-pages/man1/ssh.1.html ssh(1)] — every client flag, escape sequence and environment variable | * [https://www.man7.org/linux/man-pages/man1/ssh.1.html ssh(1)] — every client flag, escape sequence and environment variable | ||
* [https://www.man7.org/linux/man-pages/man5/ssh_config.5.html ssh_config(5)] — every client option, including the forwarding and multiplexing ones | * [https://www.man7.org/linux/man-pages/man5/ssh_config.5.html ssh_config(5)] — every client option, including the forwarding and multiplexing ones | ||
* [https://www.man7.org/linux/man-pages/man1/ssh-keygen.1.html ssh-keygen(1)] — certificates, FIDO keys, key format conversion | * [https://www.man7.org/linux/man-pages/man1/ssh-keygen.1.html ssh-keygen(1)] — certificates, FIDO keys, key format conversion | ||
* [https://www.man7.org/linux/man-pages/man5/sshd_config.5.html sshd_config(5)] — the server side: < | * [https://www.man7.org/linux/man-pages/man5/sshd_config.5.html sshd_config(5)] — the server side: <code>authorized_keys</code>, <code>PermitRootLogin</code>, <code>PasswordAuthentication</code> | ||
* [https://www.openssh.com/ OpenSSH] — release notes and security advisories | * [https://www.openssh.com/ OpenSSH] — release notes and security advisories | ||
* [https://explainshell.com explainshell.com] — annotate any command line | * [https://explainshell.com explainshell.com] — annotate any command line | ||
Latest revision as of 08:02, 30 September 2026
Quick reference for smart people — part of our Linux cheatsheets collection.
Common OpenSSH client ops: 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.
Set up key login
ssh user@host # first connection: type the password, or for most cloud providers, via the web console shell
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
user: your username on the remote machine host: IP address or URL of the remote machine
The private key never leaves your machine; the server only needs to stores the public key, one key per line, in ~/.ssh/authorized_keys.
Connecting
ssh user@host # log in as user on host
ssh -p 2222 user@host # non-standard port
ssh -i ~/.ssh/my_key user@host # use my_key as private key
ssh user@host command # run one command instead of spawning a shell
ssh user@host -N # no remote command: forward ports only
ssh user@host 'command 1; command 2' # several commands: quote and separate commands by ;
ssh -t user@host tmux attach # -t: force a TTY, for interactive programs. Opposite is -T
ssh -J user1@proxy:2222 user2@host # -J*: proxy jump, connects to host as user2, via the intermediate (proxy) machine on port 2222
ssh -o StrictHostKeyChecking=accept-new user@host # accept an unknown host key once
ssh -G host # print effective configurations for a given host
ssh -v user@host # debug the connection; -vv and -vvv go further
- This may be useful when you need to connect to a remote DB, etc. behind a firewall that is only accessible via machines on the local network. You first connect to a proxy machine on that network which accepts ssh from other networks, then ssh to the final destination via the proxy.
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 # use RSA instead of ed25519 for legacy compatibility
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
sshenforces key permissions: It will not accept a private key where any other user has read permission. Keep~/.sshat700and private keys at600(chmod 600 ~/.ssh/id_ed25519).
Good security practice: Always protect your private keys with a passphrase.
~/.ssh/config
For frequently accessed machines, set up a connection profile in `~/.ssh/config` on your local machine.
Host mySSHRemote
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_work
Host *.internal.example.com
ProxyJump mySSHRemote
Host myLegacyRemote
HostKeyAlgorithms +ssh-rsa # for old servers
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 to the
ControlPath ~/.ssh/cm-%r@%h:%p # save the TCP connections to the specified path, with automatic naming. %r: remote user, %h: host name %p: port
ControlPersist 10m # cache TCP connection for 10 min after disconnection
mkdir -p ~/.ssh/cm # ControlPath does not create the directory
ssh -F ~/.ssh/otherconf host # use an alternative config file
ssh -O check host # is a shared connection open?
ssh -O exit host # close shared connection, and every session using it
For each option, the first value found wins. Hence the catch-all
Host *should always be at the bottom.
Encrypted ssh keys: ssh-agent
If you use encrypted ssh keys, ssh-agent allow you to cache those keys unencrypted in RAM over one login session on local machine, so you do not have to provide the passphrase on each use.
eval "$(ssh-agent -s)" # start an agent in this shell
ssh-add ~/.ssh/id_ed25519 # load a key. If key encryped, asks for the passphrase once
ssh-add -t 8h ~/.ssh/id_ed25519 # -t: automatically drop the key after 8 hours
ssh-add -l # print fingerprints of the loaded keys
ssh-add -L # print full public keys of the loaded keys
ssh-add -d ~/.ssh/id_ed25519 # remove one key
ssh-add -D # remove every key
ssh-agent -k # kill this agent
AddKeysToAgent yesin~/.ssh/configloads keys into the agent on first use.
Copying files
scp file1.txt user@host:/tmp/ # upload
scp -P 2222 file1.txt user@host:/tmp/ # note: scp's port flag is -P, NOT -p
scp user@host:~/file1.txt /tmp/ # download a file
scp 'user@host:/var/log/*.log' . # download multiple files matching the glob: remember to quote remote path when * is used
scp -r dir/ user@host:~/folder1 # download a whole directory
rsync -avz --progress dir/ user@host:/tmp/ # sync changes
sftp user@host # interactive: ls, get, put, bye
Note
rsyncmust be installed both on your local machine and the remote server to work.
Prefer
rsyncfor large or repeated transfers, as it transfers only the differences and can resume when transfer is interrupted.
Port forwarding
ssh -L 8080:localhost:80 user@host # local 8080 forwards to host port 80
ssh -L 5432:db.internal:5432 user@host # local 5432 forwards to host, which then forwards to db.internal 5432 (proxy connect)
ssh -R 9000:localhost:3000 user@host # reverse forward: the server's 9000 forwards to your local 3000
ssh -D 1080 user@host # make host accessible as a SOCKS5 proxy on localhost:1080
ssh -N -f -L 8080:localhost:80 user@host # background tunnel only, no interactive shell
ssh -O exit user@host # close the backgrounded tunnel
The same forwardings in config, for a tunnel you start by name:
Host dbtunnel
HostName db.example.com
LocalForward 5432 localhost:5432 # -L 8080:localhost:80 by default for `ssh dbtunnel`
SessionType none # background tunnel only
Escape sequences
| Escape | Action |
|---|---|
~. |
disconnect |
~^Z |
suspend ssh, back to the local shell |
~# |
list the forwarded connections |
~C |
open ssh command line: add -L, -R or -D to the live session
|
~? |
list all escapes |
Inside an interactive session, escape sequences are recognised only at the start of a line.
~Cis disabled by default. To enable, add to `~/.ssh/config`:Host * EnableEscapeCommandline yes
Troubleshooting
| Symptom | Fix |
|---|---|
Permission denied (publickey) |
Incorrect key or key-host/user mismatch: run ssh -vvv user@host for more details
|
Too many authentication failures |
the agent offers more keys than the server allows attempts: add IdentitiesOnly yes to that host's block to only use keys explicitely configured for that host
|
REMOTE HOST IDENTIFICATION HAS CHANGED! |
the server's key changed: this could either be a server-side reset or a man-in-the-middle attack: confirm with the server's admin whether a legitimate reset took place before running ssh-keygen -R host:port to accept the change
|
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 some idle | consider adding ServerAliveInterval 60 in the config file to keep the session alive by sending keep-alive signals every 60s
|
For more
- 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
- explainshell.com — annotate any command line