Cheatsheets:LinuxCLI/ssh: Difference between revisions

From Wikibase
Jump to navigation Jump to search
Remove stray placeholder line in the Agent section. AI-assisted (RonzzWikiCowriter) (via update-page on MediaWiki MCP Server)
No edit summary
 
(2 intermediate revisions by one other user not shown)
Line 1: Line 1:
{{DesktopLinux}}
{{DesktopLinux}}


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 <syntaxhighlight lang="bash" inline>sshd</syntaxhighlight>.
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 <syntaxhighlight lang="bash" inline>man ssh</syntaxhighlight>, <syntaxhighlight lang="bash" inline>man ssh_config</syntaxhighlight> or [https://tldr.sh tldr ssh] for short examples.
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.


== Basic example: set up key login ==
== 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 half, one key per line, in <syntaxhighlight lang="text" inline>~/.ssh/authorized_keys</syntaxhighlight>.
<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 host                              # same username as your local one
ssh user@host                        # log in as user on host
ssh user@host                        # log in as another user
ssh -p 2222 user@host                # non-standard port
ssh -p 2222 user@host                # non-standard port
ssh user@host uptime                  # run one command instead of a shell
ssh -i ~/.ssh/my_key user@host        # use my_key as private key
ssh user@host 'df -h; free -h'       # several commands: quote them for the remote shell
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 -J bastion user@host              # jump through bastion (ProxyJump)
ssh user@host 'command 1; command 2' # several commands: quote and separate commands by ;
ssh -J user@bastion:2222 user@host    # jump host with its own user and port
ssh -t user@host tmux attach          # -t: force a TTY, for interactive programs. Opposite is -T
ssh -o ProxyCommand="ssh -W %h:%p bastion" user@host   # same, on OpenSSH < 7.3
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>


{| class="wikitable"
<blockquote>
! Flag !! Effect
*: 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>
| <syntaxhighlight lang="bash" inline>-p port</syntaxhighlight> || connect to a port other than 22
|-
| <syntaxhighlight lang="bash" inline>-i key</syntaxhighlight> || authenticate with a specific private key
|-
| <syntaxhighlight lang="bash" inline>-t</syntaxhighlight> / <syntaxhighlight lang="bash" inline>-T</syntaxhighlight> || force / disable pseudo-terminal allocation
|-
| <syntaxhighlight lang="bash" inline>-N</syntaxhighlight> || no remote command: forward ports only
|-
| <syntaxhighlight lang="bash" inline>-f</syntaxhighlight> || go to the background just before running the command
|-
| <syntaxhighlight lang="bash" inline>-J host</syntaxhighlight> || connect via a jump host; comma-separated for several
|-
| <syntaxhighlight lang="bash" inline>-L</syntaxhighlight> / <syntaxhighlight lang="bash" inline>-R</syntaxhighlight> / <syntaxhighlight lang="bash" inline>-D</syntaxhighlight> || port forwarding (see below)
|-
| <syntaxhighlight lang="bash" inline>-A</syntaxhighlight> || forward your local ssh-agent to the server (see the caution below)
|-
| <syntaxhighlight lang="bash" inline>-o OPT=value</syntaxhighlight> || set any ssh_config option on the command line
|}
 
Escape sequences are recognised only at the start of a line, inside an interactive session:
 
{| class="wikitable"
! Escape !! Action
|-
| <syntaxhighlight lang="text" inline>~.</syntaxhighlight> || disconnect
|-
| <syntaxhighlight lang="text" inline>~^Z</syntaxhighlight> || suspend ssh, back to the local shell
|-
| <syntaxhighlight lang="text" inline>~#</syntaxhighlight> || list the forwarded connections
|-
| <syntaxhighlight lang="text" inline>~C</syntaxhighlight> || command line: add <syntaxhighlight lang="bash" inline>-L</syntaxhighlight>, <syntaxhighlight lang="bash" inline>-R</syntaxhighlight> or <syntaxhighlight lang="bash" inline>-D</syntaxhighlight> to the live session
|-
| <syntaxhighlight lang="text" inline>~?</syntaxhighlight> || list all escapes
|}


== 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, when a server is too old for ed25519
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
ssh -i ~/.ssh/id_work user@host              # use a key without touching any config
</syntaxhighlight>
</syntaxhighlight>


<blockquote>
<blockquote>
'''Permissions are enforced:''' <syntaxhighlight lang="bash" inline>ssh</syntaxhighlight> silently ignores a private key that anyone else can read. Keep <syntaxhighlight lang="text" inline>~/.ssh</syntaxhighlight> at <syntaxhighlight lang="text" inline>700</syntaxhighlight> and private keys at <syntaxhighlight lang="text" inline>600</syntaxhighlight> (<syntaxhighlight lang="bash" inline>chmod 600 ~/.ssh/id_ed25519</syntaxhighlight>). Protect the key with a passphrase and load it into an [[#Agent|agent]] rather than leaving it unencrypted.
'''<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 ==


The place to turn a long command line into a name you can type.
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 web                      # `ssh web` connects as deploy@203.0.113.10 on port 2222
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 bastion
     ProxyJump mySSHRemote


Host legacy
Host myLegacyRemote
     HostKeyAlgorithms +ssh-rsa          # for old servers only
     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/%C
     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 -G web                      # print the configuration and options actually used
ssh -F ~/.ssh/otherconf host    # use an alternative config file
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 check host              # is a shared connection open?
ssh -O exit host                # close it, and every session using it
ssh -O exit host                # close shared connection, and every session using it
</syntaxhighlight>
</syntaxhighlight>


<blockquote>
<blockquote>
'''For each option, the first value found wins.''' Put the specific <syntaxhighlight lang="text" inline>Host</syntaxhighlight> blocks at the top of the file and the catch-all <syntaxhighlight lang="text" inline>Host *</syntaxhighlight> at the bottom, or the defaults will shadow the specifics. <syntaxhighlight lang="text" inline>IdentityFile</syntaxhighlight> is the exception: every entry is tried in order.
'''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>
'''Connection sharing''' (the <syntaxhighlight lang="text" inline>ControlMaster</syntaxhighlight> block) makes the second and later connections to a host start instantly, because they reuse the first, already-authenticated one; later <syntaxhighlight lang="bash" inline>scp</syntaxhighlight>, <syntaxhighlight lang="bash" inline>rsync</syntaxhighlight> and <syntaxhighlight lang="bash" inline>git</syntaxhighlight> calls to that host skip the handshake too.
<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/file1.txt .        # download to the current directory
scp -P 2222 file1.txt user@host:/tmp/ # note: scp's port flag is -P, NOT -p
scp -r dir/ user@host:/tmp/            # a whole directory
scp user@host:~/file1.txt /tmp/       # download a file
scp -P 2222 file1.txt user@host:/tmp/ # note: -P (capital) is scp's port flag
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 the remote glob
scp -r dir/ user@host:~/folder1        # download a whole directory
rsync -avz --progress dir/ user@host:/tmp/  # only what changed, resumable
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>
'''<syntaxhighlight lang="bash" inline>scp</syntaxhighlight> has spoken SFTP since OpenSSH 9.0''', and it reads the same <syntaxhighlight lang="text" inline>~/.ssh/config</syntaxhighlight> blocks: <syntaxhighlight lang="bash" inline>scp web:file1.txt .</syntaxhighlight> works once <syntaxhighlight lang="text" inline>Host web</syntaxhighlight> exists. For large or repeated transfers prefer <syntaxhighlight lang="bash" inline>rsync</syntaxhighlight>, which transfers only the differences and can resume — it must be installed on both machines.
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 → port 80, as seen from the server
ssh -L 8080:localhost:80 user@host        # local 8080 forwards to host port 80
ssh -L 5432:db.internal:5432 user@host    # reach a host only the server can reach
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 → your local 3000
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, in the background
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>
{| class="wikitable"
! Flag !! Listens on !! Traffic goes to
|-
| <syntaxhighlight lang="bash" inline>-L 8080:host:80</syntaxhighlight> || your machine, port 8080 || <syntaxhighlight lang="text" inline>host</syntaxhighlight>, as resolved from the server
|-
| <syntaxhighlight lang="bash" inline>-R 9000:host:3000</syntaxhighlight> || the server, port 9000 || <syntaxhighlight lang="text" inline>host</syntaxhighlight>, as resolved from your machine
|-
| <syntaxhighlight lang="bash" inline>-D 1080</syntaxhighlight> || your machine, port 1080 || whatever the SOCKS client asks for
|}


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  # 5432 as seen from the server
     LocalForward 5432 localhost:5432  # -L 8080:localhost:80 by default for `ssh dbtunnel`
     SessionType none                  # no shell, just the tunnel (OpenSSH 8.4+)
     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>
'''<syntaxhighlight lang="bash" inline>-L</syntaxhighlight> listens on your machine, <syntaxhighlight lang="bash" inline>-R</syntaxhighlight> on 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 (<syntaxhighlight lang="bash" inline>-L 0.0.0.0:8080:localhost:80</syntaxhighlight>), and ports below 1024 require root.
Inside an interactive session, escape sequences are recognised only at the start of a line.
</blockquote>
</blockquote>


== Agent ==
<blockquote>
<code>~C</code> is disabled by default. To enable, add to `~/.ssh/config`:


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="text" copy>
eval "$(ssh-agent -s)"            # start an agent in this shell
Host *
ssh-add ~/.ssh/id_ed25519        # load a key; asks for the passphrase once
   EnableEscapeCommandline yes
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
</syntaxhighlight>
 
<syntaxhighlight lang="bash" copy>
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
</syntaxhighlight>
</syntaxhighlight>
<blockquote>
'''Avoid <syntaxhighlight lang="bash" inline>ssh -A</syntaxhighlight> on 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 <syntaxhighlight lang="bash" inline>-J</syntaxhighlight> (ProxyJump), where authentication stays end-to-end. <syntaxhighlight lang="text" inline>AddKeysToAgent yes</syntaxhighlight> in <syntaxhighlight lang="text" inline>~/.ssh/config</syntaxhighlight> loads keys into the agent on first use.
</blockquote>
</blockquote>


Line 209: Line 194:
! Symptom !! Fix
! Symptom !! Fix
|-
|-
| <syntaxhighlight lang="text" inline>Permission denied (publickey)</syntaxhighlight> || the key is not installed: <syntaxhighlight lang="bash" inline>ssh-copy-id user@host</syntaxhighlight>; check <syntaxhighlight lang="bash" inline>chmod 600 ~/.ssh/id_ed25519</syntaxhighlight>; <syntaxhighlight lang="bash" inline>ssh -v</syntaxhighlight> lists the keys the client offers
| <code>Permission denied (publickey)</code> || Incorrect key or key-host/user mismatch: run <code>ssh -vvv user@host</code> for more details
|-
| <syntaxhighlight lang="text" inline>Too many authentication failures</syntaxhighlight> || the agent offers more keys than the server allows attempts: add <syntaxhighlight lang="text" inline>IdentitiesOnly yes</syntaxhighlight> to that host's block
|-
|-
| <syntaxhighlight lang="text" inline>REMOTE HOST IDENTIFICATION HAS CHANGED!</syntaxhighlight> || the server's key really did change — confirm with its admin before running <syntaxhighlight lang="bash" inline>ssh-keygen -R host</syntaxhighlight>
| <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
|-
|-
| <syntaxhighlight lang="text" inline>Connection refused</syntaxhighlight> || wrong port, <syntaxhighlight lang="bash" inline>sshd</syntaxhighlight> down, or a firewall: test with <syntaxhighlight lang="bash" inline>nc -vz host 22</syntaxhighlight>
| <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
|-
|-
| <syntaxhighlight lang="text" inline>Connection timed out</syntaxhighlight> || nothing answers at all: a firewall is dropping the packets, or the host is unreachable
| <code>Connection refused</code> || wrong port, <code>sshd</code> down, or a firewall: test with <code>nc -vz host 22</code>
|-
|-
| the session freezes after an idle spell || <syntaxhighlight lang="text" inline>ServerAliveInterval 30</syntaxhighlight> in the config file
| <code>Connection timed out</code> || nothing answers at all: a firewall is dropping the packets, or the host is unreachable
|-
|-
| which key, which options, which file? || <syntaxhighlight lang="bash" inline>ssh -G host</syntaxhighlight> prints the effective configuration, <syntaxhighlight lang="bash" inline>ssh -vvv host</syntaxhighlight> everything the client tries
| 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 ==


* <syntaxhighlight lang="bash" inline>man ssh</syntaxhighlight>, <syntaxhighlight lang="bash" inline>man ssh_config</syntaxhighlight>, <syntaxhighlight lang="bash" inline>man ssh-keygen</syntaxhighlight>, <syntaxhighlight lang="bash" inline>man sshd_config</syntaxhighlight> — the built-in manuals, always installed
* [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: <syntaxhighlight lang="text" inline>authorized_keys</syntaxhighlight>, <syntaxhighlight lang="text" inline>PermitRootLogin</syntaxhighlight>, <syntaxhighlight lang="text" inline>PasswordAuthentication</syntaxhighlight>
* [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://tldr.sh tldr] — community-maintained short examples
* [https://explainshell.com explainshell.com] — annotate any command line
* [https://explainshell.com explainshell.com] — annotate any command line
Wiki: wikibase.ronzz.org

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

ssh enforces key permissions: It will not accept a private key where any other user has read permission. Keep ~/.ssh at 700 and private keys at 600 (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 yes in ~/.ssh/config loads 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 rsync must be installed both on your local machine and the remote server to work.

Prefer rsync for 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.

~C is 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