ssh

Log into and run commands on a remote machine securely

Updated 2026-08-12

ssh opens an encrypted connection to another machine and gives you a shell on it, or runs a single command there and streams the output back. It carries logins to servers, it is what scp and rsync run over, it is what Git's git@github.com: URLs use, and every "tunnel a port through a jump host" trick is built on it.

An encrypted pipe with a shell on the end

ssh user@host does three things in order: negotiate an encrypted channel, prove your identity to the server (and, less obviously, prove the server's identity to you), then attach that channel to either an interactive shell or a single remote command:

ssh user@deb1.example.com            # interactive login shell
ssh user@deb1.example.com uptime     # run one command, print its output, exit

Everything else ssh does, port forwarding, agent forwarding, X11 forwarding, is that same encrypted channel carrying something other than a shell. Once you see forwarding as "another use of the tunnel" rather than a separate feature, the -L/-R/-D flags stop looking arbitrary.

Two ways in: passwords and keys

Password authentication asks the remote machine to check a secret you type. Key-based authentication asks it to check that you hold the private half of a key pair whose public half is already listed in ~/.ssh/authorized_keys on the server; nothing secret ever crosses the wire. ssh-keygen -t ed25519 generates a pair (Ed25519 is the current recommended type: smaller and faster to verify than RSA, with no key-size decision to get wrong), and ssh-copy-id installs the public half on a server you can still log into by password.

Keys can be encrypted at rest with a passphrase, which is what ssh-agent is for: unlock a key once per session, hand it to the agent, and every subsequent ssh or scp call asks the agent for a signature instead of prompting you again. If a private key's permissions are too open (readable by group or other), ssh refuses to use it outright rather than risk a key that anyone else on the box could read.

A connection that drops takes everything running under it with it, which is worth arranging for before it happens rather than after: keep a program running after you log out.

The config file: stop retyping flags

~/.ssh/config maps a short alias to a real hostname, user, port, and key, so ssh deb1 can mean ssh -p 2222 -i ~/.ssh/id_ed25519 user@deb1.example.com. Entries are Host blocks read top to bottom; the first matching value for a given setting wins, which is why host-specific blocks belong above a catch-all Host * block, not below it. ssh -G <host> prints the fully resolved configuration for a host without connecting, which is the fastest way to check why ssh is using the port, key, or jump host it's using.

Host keys prove the server is the one you saw last time

Password or key checks prove who you are; a host key check proves who the server is. The first time you connect to a new host, ssh shows a fingerprint and asks you to confirm it, then remembers it (hashed, by default) in ~/.ssh/known_hosts. Every later connection compares the server's key against that saved copy and refuses to continue if it's changed, which is exactly what happens if someone is intercepting the connection, or, far more often in practice, if a server was rebuilt or a hosting provider recycled an IP address. ssh-keygen -R <host> removes a stale entry after you've confirmed the change is legitimate.

Forwarding sends something other than a shell down the tunnel

-L local:host:remote opens a port on your machine that tunnels to a port reachable from the server's side, useful for reaching a database that only listens on a remote machine's loopback interface. ss -ltn on the server shows such a service bound to 127.0.0.1 rather than to 0.0.0.0. -R runs the same idea backwards, exposing a port on your machine to the server. -D turns ssh into a SOCKS proxy, routing arbitrary traffic through the connection without picking a single destination port up front. All three need the connection to stay open, so they're normally combined with -N (no remote command) or -f (background after connecting).

Reusing one connection for everything

Every fresh ssh connection repeats the full handshake, which is the noticeable delay before a prompt or scp transfer starts. ControlMaster/ControlPath/ControlPersist in the config file let a second ssh (or scp, or rsync -e ssh) to the same host reuse an already-open connection instead of negotiating a new one, cutting a login from a full round trip to almost instant.

Reading a failure

ssh exits 255 for a connection-level failure (unresolvable host, refused port, rejected key, timeout) and passes through whatever the remote command exited with otherwise, so a script checking ssh host cmd's exit code has to know which kind of failure it's looking at. See Exit codes and error handling for handling that distinction. When the reason for a 255 isn't obvious, -v (repeat up to -vvv) prints the handshake step by step, showing exactly where it stalled.

Sample files used on this page

Every example below was run against these files. Recreate them to follow along.

~/.ssh/config the host block the -G and connection-sharing examples resolve against

Host deb1
    HostName deb1.example.com
    User user
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

~/.ssh/known_hosts two saved host keys, for the lookup and removal examples - host keys are public, so nothing secret is shown

|1|54lfM/zb+rQclYh4kZPZwbDAJXY=|0nF4Tq2N1xiwJDS540Be7ylsvKU= ssh-ed25519
deb1.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOo
19 outputs, collapsed by default

Connecting and running commands

The two shapes every ssh invocation takes: an interactive shell, or one command with its output streamed back.

Log into a remote machine

ssh user@deb1.example.com

Opens an interactive shell on the remote machine. Requires a real, reachable host; the address here is a placeholder.

Run a single command and exit

ssh user@deb1.example.com uptime

Anything after the host is executed remotely instead of starting a shell; ssh prints its output and exits with the remote command's exit status.

Connect on a non-default port

ssh -p 2222 user@deb1.example.com

-p overrides the default port 22. Common on boxes that move sshd off the default port to cut down on automated scan noise.

Check the installed ssh version

ssh -V

Prints the OpenSSH and OpenSSL versions in use, useful when a flag doesn't behave as documented and you suspect an older build. Note that both are printed on standard error, so ssh -V | grep OpenSSL finds nothing and ssh -V 2>&1 | grep OpenSSL works.

Show output

Your output will differ: the two version numbers and the OpenSSL release date are whatever your Debian ships

OpenSSH_10.0p2 Debian-7+deb13u4, OpenSSL 3.5.6 7 Apr 2026

Run a command that needs a real shell (quoting)

ssh user@deb1.example.com "cd /var/log && ls -la"

Quote multi-part commands as one string; otherwise ssh only sends the first word remotely and the rest gets interpreted locally as ssh's own arguments.

Pipe local input into a remote command

cat report.csv | ssh user@deb1.example.com "cat >> /var/log/reports.log"

Stdin is forwarded over the same connection, so a local pipeline can feed a remote command directly without an intermediate file.

Force a pseudo-terminal for a command that expects one

ssh -t user@deb1.example.com "sudo systemctl status ssh"

ssh only allocates a pseudo-terminal automatically for an interactive login. A command that needs one anyway, such as sudo prompting for a password or an interactive top, needs -t stated explicitly when run as a one-off.

Key-based authentication

Generating a key pair, installing the public half on a server, and letting an agent hold the unlocked key for the session.

Generate an Ed25519 key pair

ssh-keygen -t ed25519 -C "user@deb1" -f ./id_ed25519_test -N "" | head -3

-t ed25519 picks the current recommended key type (smaller and faster to verify than RSA). -C sets a comment, conventionally an identifying label, stored alongside the public key. -f names the output file and -N "" sets an empty passphrase; run without them, ssh-keygen prompts for both. It also prints the new key's fingerprint and a randomart image, which differ for every key generated.

Show output
Generating public/private ed25519 key pair.
./id_ed25519_test already exists.
Overwrite (y/n)?

View the public key

cat ~/.ssh/id_ed25519.pub

The public key is the line you share: it's safe to post anywhere, paste into a server's authorized_keys, or hand to a Git hosting provider.

Show output
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE/UVquqwrnITn1udSbK8/Vp2jejSDOUnYfQPsPIcM/T user@deb1

Show a key's fingerprint

ssh-keygen -lf ~/.ssh/id_ed25519.pub

Prints a short hash of the key instead of the full public key text, handy for visually comparing two keys without pasting the whole thing.

Show output
256 SHA256:EpmQiJDQS3h8FvlWEXRLoxPebOnp/F5nMGf59V9hYEs user@deb1 (ED25519)

Generate an RSA key at a specific size

ssh-keygen -t rsa -b 4096 -C "user@deb1"

RSA is still widely supported by older servers and appliances that don't understand Ed25519. -b 4096 sets the bit length; RSA's default of 3072 is acceptable, but 4096 is the common convention for a long-lived key.

Copy a public key to a server's authorized_keys

ssh-copy-id user@deb1.example.com

Appends your default public key to the remote account's ~/.ssh/authorized_keys, creating the file and directory with correct permissions if they don't exist. Needs a working login (password or an already-trusted key) to install the new one. Requires a real reachable host to verify.

Copy a specific, non-default key

ssh-copy-id -i ~/.ssh/id_ed25519_work.pub user@deb1.example.com

-i points at a .pub file explicitly, for accounts where you keep separate keys per server or per role rather than one default key for everything.

Start ssh-agent and add a key

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

ssh-agent -s starts the agent and prints shell variables that later ssh calls in the same session use to find it; eval applies them. ssh-add unlocks the key (prompting for its passphrase once) and hands it to the agent.

Show output
Agent pid 2961
Identity added: id_ed25519 (user@deb1)

List keys currently held by the agent

ssh-add -l

Confirms which keys the agent will offer, without exposing the private key material itself.

Show output
256 SHA256:EpmQiJDQS3h8FvlWEXRLoxPebOnp/F5nMGf59V9hYEs user@deb1 (ED25519)

See the error when no agent is running

ssh-add -l

Without SSH_AUTH_SOCK set (no agent started, or the variable lost in a new shell), ssh-add can't reach an agent at all.

Show output
Could not open a connection to your authentication agent.

Remove all keys from the agent

eval "$(ssh-agent -s)" >/dev/null
ssh-add ~/.ssh/id_ed25519 2>/dev/null
ssh-add -D

Clears every identity the agent is holding, without stopping the agent process itself. Useful before switching to a different set of keys mid-session.

Show output
All identities removed.

Change a key's passphrase

ssh-keygen -p -f ~/.ssh/id_ed25519

-p re-encrypts an existing private key with a new passphrase in place, without generating a new key pair or touching the public key or any authorized_keys entry that already references it.

Refuse a private key with overly open permissions

ssh-keygen -y -f ./id_ed25519_test

A private key readable by group or other (here deliberately set to 644) is rejected outright rather than used. chmod 600 on the key file is the fix.

Show output
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for './id_ed25519_test' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "./id_ed25519_test": bad permissions

The ~/.ssh/config file

Aliasing a host, its port, user, and key so a full connection collapses to a short name.

Write a basic host alias

Host deb1
    HostName deb1.example.com
    User user
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Saved in ~/.ssh/config. After this, ssh deb1 expands to the full connection details; scp and rsync -e ssh honour the same file.

Set defaults that apply to every host

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

A catch-all Host * block sets fallback values. Order matters: the first matching value for a setting wins, so host-specific blocks must come above a catch-all, not below it.

Show the fully resolved config for a host

ssh -G deb1

Prints every setting ssh would actually use for deb1 after merging all matching Host blocks, without opening a connection. The fastest way to check why ssh is using a particular port, key, or jump host.

Show output
Pseudo-terminal will not be allocated because stdin is not a terminal.
host deb1
user user
hostname deb1.example.com
port 2222
addressfamily any
batchmode no
canonicalizefallbacklocal yes
canonicalizehostname false
checkhostip no
compression no
controlmaster false
enablesshkeysign no
clearallforwardings no
exitonforwardfailure no
fingerprinthash SHA256
forwardx11 no
forwardx11trusted yes
gatewayports no
gssapiauthentication yes
gssapikeyexchange no
gssapidelegatecredentials no
gssapitrustdns no
gssapirenewalforcesrekey no
gssapikexalgorithms gss-group14-sha256-,gss-group16-sha512-,gss-nistp256-sha256-,gss-curve25519-sha256-,gss-group14-sha1-,gss-gex-sha1-
hashknownhosts yes
hostbasedauthentication no
identitiesonly no
kbdinteractiveauthentication yes
nohostauthenticationforlocalhost no
passwordauthentication yes
permitlocalcommand no
proxyusefdpass no
pubkeyauthentication true
requesttty auto
sessiontype default
stdinnull no
forkafterauthentication no
streamlocalbindunlink no
stricthostkeychecking ask
tcpkeepalive yes
tunnel false
verifyhostkeydns false
visualhostkey no
updatehostkeys true
enableescapecommandline no
canonicalizemaxdots 1
connectionattempts 1
forwardx11timeout 1200
numberofpasswordprompts 3
serveralivecountmax 3
serveraliveinterval 0
requiredrsasize 1024
obscurekeystroketiming yes
ciphers chacha20-poly1305@openssh.com,aes128-gcm@openssh.com,aes256-gcm@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr
hostkeyalgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,rsa-sha2-512,rsa-sha2-256
hostbasedacceptedalgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,rsa-sha2-512,rsa-sha2-256
kexalgorithms mlkem768x25519-sha256,sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256
casignaturealgorithms ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,rsa-sha2-512,rsa-sha2-256
loglevel INFO
macs umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1
securitykeyprovider internal
pubkeyacceptedalgorithms ssh-ed25519-cert-v01@openssh.com,ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,sk-ecdsa-sha2-nistp256-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,sk-ssh-ed25519@openssh.com,sk-ecdsa-sha2-nistp256@openssh.com,rsa-sha2-512,rsa-sha2-256
xauthlocation /usr/bin/xauth
identityfile ~/.ssh/id_ed25519
canonicaldomains none
globalknownhostsfile /etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2
userknownhostsfile /home/user/.ssh/known_hosts /home/user/.ssh/known_hosts2
sendenv LANG
sendenv LC_*
sendenv COLORTERM
sendenv NO_COLOR
logverbose none
channeltimeout none
permitremoteopen any
addkeystoagent false
forwardagent no
connecttimeout none
tunneldevice any:any
canonicalizePermittedcnames none
controlpersist no
escapechar ~
ipqos ef cs1
rekeylimit 0 0
streamlocalbindmask 0177
syslogfacility USER

Use an alternate config file

ssh -F ~/.ssh/config-work deb1

-F points ssh at a different config file entirely, for keeping personal and work host lists separate.

Route through a jump host

Host internal
    HostName 10.0.0.5
    User user
    ProxyJump deb1.example.com

ProxyJump (or ssh -J deb1.example.com internal) connects through an intermediate host first, useful for machines only reachable from inside a private network. Confirmed by resolving with ssh -G internal, which reports proxyjump deb1.example.com.

Disable host key checking for one throwaway host

Host scratch-vm
    HostName 192.0.2.50
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null

Skips host key verification and avoids polluting your real known_hosts, appropriate for a disposable VM you rebuild constantly. Never do this for a host that holds anything real: it removes ssh's protection against a spoofed or swapped server.

Set a default identity file for every host

Host *
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

IdentitiesOnly yes restricts ssh to the keys explicitly listed instead of also offering every key an agent happens to be holding, avoiding a host locking you out after too many rejected keys.

Send environment variables to the remote session

Host deb1
    SendEnv LANG LC_*

Forwards local environment variables matching the pattern, if the server's sshd_config has a matching AcceptEnv entry; most servers don't accept arbitrary variables by default.

Host keys and known_hosts

How ssh verifies the server is who it claims to be, and what to do when that check legitimately fails.

See the confirmation prompt for a first-time host

ssh -o StrictHostKeyChecking=ask user@newhost.example.com

The default behaviour: on a host ssh has never seen, it prints the key's fingerprint and asks for confirmation before continuing. Requires a real, unrecognised host to trigger; this is the default even without the explicit flag.

Accept a new host key automatically

ssh -o StrictHostKeyChecking=no user@deb1.example.com

Skips the confirmation prompt and accepts whatever key the server presents, adding it straight to known_hosts. Convenient for scripted, one-off connections to freshly provisioned hosts; never use it for a host whose identity matters, since it also silently accepts a key that's changed for the wrong reasons.

Query known_hosts for a saved entry

ssh-keygen -F github.com

Looks up a host's saved key without connecting anywhere. known_hosts entries are hashed by default (HashKnownHosts yes on Debian), so the file itself doesn't reveal which hosts you've connected to at a glance; -F decodes the lookup.

Show output
# Host github.com found: line 1
|1|54lfM/zb+rQclYh4kZPZwbDAJXY=|0nF4Tq2N1xiwJDS540Be7ylsvKU= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl

Remove a stale host key

ssh-keygen -R deb1.example.com

Deletes a host's entry from known_hosts after you've confirmed a key change is legitimate (server rebuilt, hosting provider recycled the IP), so the next connection re-prompts instead of refusing outright. Keeps a .old backup of the file it edited.

Show output
# Host deb1.example.com found: line 2
/home/user/.ssh/known_hosts updated.
Original contents retained as /home/user/.ssh/known_hosts.old

Pre-populate known_hosts without logging in

ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts

Fetches a host's public key directly, the same way provisioning scripts avoid an interactive first-connection prompt. Only trustworthy over a connection you already trust (a private network, a host you just built yourself): it has no way to verify the key it receives, so it's exactly as blind as StrictHostKeyChecking=no for that one lookup.

Show output
# github.com:22 SSH-2.0-dad0df6
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl

See what a changed host key looks like

ssh user@deb1.example.com

When a saved key no longer matches, ssh refuses the connection outright with a loud warning and exits without prompting, unlike the first-connection case. Genuine output from a real mismatch depends on the specific host; treat the warning as a stop sign, not something to route around with -o StrictHostKeyChecking=no out of habit.

Copying files over ssh

scp and rsync both reuse an ssh connection rather than needing a separate file-transfer server. See the full recipe for more: Copy files between two machines over SSH.

Copy a file to a remote host

scp report.pdf user@deb1.example.com:/home/user/backups/

Uses the same authentication and host key checking as ssh. The trailing slash means "into this directory," keeping the original filename.

Copy a file from a remote host

scp user@deb1.example.com:/var/log/syslog ./syslog-backup

Reversing the source and destination pulls a file down instead of pushing one up.

Sync a tree with rsync over ssh

rsync -avz -e ssh project/ deb1:/home/user/project/

-e ssh is usually redundant (ssh is rsync's default remote shell) but useful for pinning a specific port or key: -e "ssh -p 2222". rsync only transfers what changed, unlike scp's full re-copy.

Copy directly between two remote hosts

scp user@deb1.example.com:/data/export.csv user@deb2.example.com:/data/

By default the data still routes through your local machine unless the server supports -3 mode; without a real pair of reachable hosts this can't be verified here, but the syntax is standard scp usage.

Port forwarding

Using the encrypted connection to carry traffic other than a shell.

Forward a local port to a service reachable from the remote side

ssh -L 8080:localhost:80 user@deb1.example.com

-L local_port:target_host:target_port opens localhost:8080 on your machine and tunnels connections to it through to target_host:target_port as seen from the remote server, useful for reaching a database or admin panel that only listens on the server's own loopback interface. Needs a real reachable target to demonstrate; the tunnel itself carries no output of its own.

Forward a local port without opening a shell

ssh -N -L 8080:localhost:80 user@deb1.example.com

-N tells ssh not to run any remote command at all, just hold the tunnel open. Combine with -f to background it after the connection is established.

Expose a local service on the remote host

ssh -R 9000:localhost:3000 user@deb1.example.com

The reverse of -L: a connection to port 9000 on the remote host tunnels back to port 3000 on your machine. Useful for letting a colleague, or a remote webhook, reach something only running on your laptop. The remote sshd needs GatewayPorts configured to expose this beyond the server's own loopback.

Run ssh as a SOCKS proxy

ssh -D 1080 user@deb1.example.com

-D port turns ssh into a dynamic SOCKS proxy on that local port; pointing a browser or another tool's proxy setting at localhost:1080 routes its traffic through the remote host, without picking a single destination port up front the way -L requires.

Background a forwarding-only connection

ssh -fN -L 5432:localhost:5432 user@deb1.example.com

-f backgrounds ssh right after authentication instead of holding the foreground, combined with -N for a tunnel with no shell attached. The classic incantation for "forward this one database port and get out of the way."

Multiplexing and performance

Reusing one authenticated connection instead of repeating the handshake for every ssh, scp, or rsync call to the same host.

Enable connection sharing for a host

Host deb1
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

The first connection to deb1 becomes the "master" and opens a control socket at ControlPath; later connections to the same host reuse it instead of renegotiating. ControlPersist 600 keeps the master alive for 10 minutes after the last session closes, ready for the next one. Confirmed the paths resolve correctly with ssh -G deb1, which reports the expanded controlpath.

Show output
controlmaster auto
controlpath /home/user/.ssh/sockets/user@deb1.example.com-22
controlpersist 600

Create the socket directory multiplexing needs

mkdir -p ~/.ssh/sockets

ControlPath doesn't create its parent directory automatically; the first connection attempt fails with a cryptic error if ~/.ssh/sockets doesn't already exist.

Check whether a master connection is active

ssh -O check deb1

-O check asks an existing control socket whether it's alive without opening a new connection. Needs a real multiplexed session already running to return anything meaningful.

Close a multiplexed master connection

ssh -O exit deb1

-O exit tells the control master to shut down, closing every session still riding on it. Useful after changing a key or a config value, which a live multiplexed connection would otherwise carry on using.

Agent and X11 forwarding

Extending the local session's identity or display to the remote end.

Forward the local agent to a remote host

ssh -A user@deb1.example.com

Lets commands run on the remote host authenticate onward using keys held by your local agent, without copying any private key to the server, useful for hopping from one server to a second one behind it. Only forward an agent to hosts you trust: anyone with root on the remote machine while the forwarding is active can request signatures from your agent.

Forward agent for one command only

ssh -A user@deb1.example.com "git pull"

Limits the exposure window from the previous example to the lifetime of a single remote command instead of an open-ended interactive session.

Forward a graphical application's display

ssh -X user@deb1.example.com

-X forwards X11 so a GUI application started remotely displays on your local screen. Requires a local X server and an X11-enabled sshd on the remote end to show anything; the connection itself is the only part verifiable without both.

Trust the forwarded display fully

ssh -Y user@deb1.example.com

-Y is -X without the extra security restrictions X11SecurityExtension normally applies. Only worth the trade-off for a specific application that breaks under the restricted mode.

Debugging a connection

Working out why ssh isn't behaving the way the config file suggests it should.

Get a step-by-step trace of the connection

ssh -v user@deb1.example.com

-v prints each stage as it happens: config file lines read, key exchange, which keys were offered and in what order, and where the process stopped. The first thing to try when a connection fails for a reason that isn't obvious.

Show output
debug1: OpenSSH_10.0p2 Debian-7+deb13u4, OpenSSL 3.5.6 7 Apr 2026
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: include /etc/ssh/ssh_config.d/*.conf matched no files
debug1: /etc/ssh/ssh_config line 21: Applying options for *
debug1: Connecting to deb1.example.com port 22.

Increase verbosity further

ssh -vvv user@deb1.example.com

Each extra v (up to three) adds more detail, down to individual protocol messages. -vvv is usually more than you need; start with -v and escalate only if that doesn't show the problem.

See why a host is unreachable at all

ssh -o BatchMode=yes -o ConnectTimeout=3 nonexistent.invalid echo hi

BatchMode=yes stops ssh from ever prompting (for a password or a host key confirmation), so a script gets a clean failure instead of hanging. This is a real, unresolvable hostname, so the error and exit code below are genuine.

Show output
ssh: Could not resolve hostname nonexistent.invalid: Name or service not known

Check the exit code after a batch-mode failure

ssh -o BatchMode=yes -o ConnectTimeout=3 nonexistent.invalid echo hi
echo "exit=$?"

255 specifically means ssh itself failed to connect or authenticate, distinct from any exit code the remote command might have returned had it actually run.

Show output
ssh: Could not resolve hostname nonexistent.invalid: Name or service not known
exit=255

Test key-based auth against a known real host

ssh -o BatchMode=yes -o StrictHostKeyChecking=no -T git@github.com

GitHub's ssh endpoint replies with an identity check even without a matching key, so it's a genuinely useful way to confirm ssh can reach some real server and negotiate a protocol, isolating whether a problem is network-level or specific to your own target host. -T disables the pseudo-terminal since no shell is expected.

Show output
git@github.com: Permission denied (publickey).

Scripting and hardening patterns

ssh calls that show up inside larger scripts, and config choices that reduce risk on a shared or internet-facing key.

Run a remote command and capture its output

result=$(ssh user@deb1.example.com "df -h /")

Standard command substitution works over ssh exactly as it does locally, since the remote command's stdout is just ssh's own stdout.

Check whether a host is reachable before proceeding

if ssh -o BatchMode=yes -o ConnectTimeout=3 user@deb1.example.com true; then
  echo "reachable"
fi

true as the remote command does nothing but succeed, so the whole check reduces to "did ssh manage to connect and authenticate," reported as a plain exit code ready for an if.

Run the same command across several hosts

for host in deb1 deb2 deb3; do
  echo "== $host =="
  ssh -o BatchMode=yes "$host" 'uptime'
done

A simple fan-out loop. Each iteration is a fresh connection unless multiplexing is configured for these hosts; for more than a handful of hosts, a proper tool (Ansible, pssh) tracks failures per host far better than a bare loop.

List the key exchange and cipher algorithms this build supports

ssh -Q cipher

-Q queries ssh's own compiled-in capabilities, here symmetric ciphers offered during negotiation. Also accepts key, mac, kex, and others.

Show output
3des-cbc
aes128-cbc
aes192-cbc
aes256-cbc
aes128-ctr
aes192-ctr
aes256-ctr
aes128-gcm@openssh.com
aes256-gcm@openssh.com
chacha20-poly1305@openssh.com

Restrict a shared account key to one command

command="/usr/local/bin/backup.sh",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA... user@deb1

A command= prefix in authorized_keys forces that key to run only the named command, ignoring whatever the connecting client actually asked for, and the trailing options strip out forwarding capabilities the key doesn't need. Standard practice for a key handed to a backup script or CI system instead of a person.

Turn off password authentication server-side

# /etc/ssh/sshd_config
PasswordAuthentication no

Forces every login through a key, closing off online password-guessing entirely. Requires root on the server and a restart of sshd (systemctl reload ssh) to take effect; confirm at least one working key-based login first, or a mistake here locks out password access with no way back in except console access.