Home / Alt manpages / ssh(1)

  • ssh(1)
  • User command
  • linux

Use SSH Safely: Host Keys, Config and Remote Commands

You will connect to a remote Linux host, run one command without opening a shell, and save a small per-host configuration that makes later connections repeatable. The examples use the OpenSSH 9.6p1 client installed by Ubuntu's openssh-client package. Allow 10 to 20 minutes if you already have a server account and an authentication method.

Before you start

You need the remote host name, a remote account name, and either a password, a key already accepted by the server, or another method enabled by that server. Replace every value in angle brackets before pressing Enter. You do not need root on the client for any step here. A remote command can still change data on the server, so read it before running it.

1. Check the client and make a first connection

Confirm which client is being used, then connect with the smallest useful command:

$ command -v ssh
$ ssh -V
$ ssh <remote-user>@<remote-host>

The local client reports a version similar to this on the machine used for this guide:

/usr/bin/ssh
OpenSSH_9.6p1 Ubuntu-3ubuntu13.19, OpenSSL 3.0.13 30 Jan 2024

On a first connection, SSH shows the server host-key fingerprint. Compare it with a fingerprint supplied through a trusted channel by the server operator, then accept it only if it matches. The accepted key is recorded in ~/.ssh/known_hosts. A changed key is not a routine prompt to bypass: stop and investigate DNS, the server rebuild, or a possible interception. The default StrictHostKeyChecking ask accepts a new key only after confirmation and refuses a changed key.

Checkpoint

You have an interactive shell and the host has a corresponding entry in ~/.ssh/known_hosts. Type exit to return to the local shell.

2. Run a remote command and verify its exit status

Put the command after the destination when you want a non-interactive session. SSH sends the command to the remote host instead of starting a login shell:

$ ssh <remote-user>@<remote-host> 'uname -srm'
Linux 6.8.0-xx-generic x86_64
$ ssh <remote-user>@<remote-host> 'test -r /etc/os-release'
$ printf 'remote status: %s\n' "$?"
remote status: 0

The second command prints nothing when the file is readable, but its status is still useful in a script. SSH exits with the remote command's status, or with 255 when the SSH operation itself fails. Quote a command containing shell operators so that the remote shell, rather than your local shell, interprets them:

$ ssh <remote-user>@<remote-host> 'printf "host=%s\n" "$HOSTNAME"; id -un'

3. Make the host easy to recognise

SSH reads the per-user configuration from ~/.ssh/config. Create the directory and file only if they do not exist. The file contains ordinary user configuration, so do not make it writable by other users:

$ mkdir -p ~/.ssh
$ chmod 700 ~/.ssh
$ touch ~/.ssh/config
$ chmod 600 ~/.ssh/config

Add a short alias with the real host, account, key and a modest dead-connection policy:

Host <short-name>
    HostName <remote-host>
    User <remote-user>
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

Use it like this:

$ ssh -G <short-name> | grep -E '^(user|hostname|port|identityfile|serveralive) '
user <remote-user>
hostname <remote-host>
port 22
identityfile ~/.ssh/id_ed25519
serveralivecountmax 3
serveraliveinterval 30
$ ssh <short-name>

ssh -G prints the evaluated configuration and exits without connecting. This is the quickest way to catch a misspelled alias or an unexpectedly inherited setting. SSH uses the first value obtained for most parameters, so put a specific Host block before a broad Host * block. The IdentitiesOnly line prevents unrelated keys offered by an agent from being tried for this host; it does not create or install a key.

4. Use a jump host when the target is private

If the target is reachable only through a bastion, use ProxyJump. The client makes an SSH connection to the jump host and forwards a connection to the final host:

$ ssh -J <jump-user>@<jump-host> <remote-user>@<private-host>
$ ssh -J <jump-user>@<jump-host> <remote-user>@<private-host> 'hostname'

For repeated use, put ProxyJump <jump-user>@<jump-host> inside the target's Host block. Configuration for the destination does not generally apply to the jump host, so give the jump host its own block if it needs a different key or port.

5. Add forwarding only for a named purpose

Local forwarding makes a local listening port reach a service from the remote side. For example, this maps local port 15432 to a database listening on the target host's loopback address:

$ ssh -N -L 127.0.0.1:15432:127.0.0.1:5432 <short-name>

-N requests no remote command, leaving the connection for forwarding. Keep the explicit 127.0.0.1 bind: an empty address or a wildcard can expose the listening port to other interfaces. Stop this foreground tunnel with Ctrl-C. Do not use -g, GatewayPorts, or a wildcard bind unless you have deliberately designed the access boundary.

Agent forwarding deserves the same caution. -A lets a remote process use your local agent through a forwarded socket. It cannot copy the private key, but a sufficiently privileged remote user can ask the agent to authenticate. Leave it off unless the trust boundary is clear; a jump host with -J is often the better fit.

6. Diagnose without changing state

Use verbose output for a connection failure and keep it local unless you need to share it:

$ ssh -vv <short-name>

To inspect supported algorithm query names without connecting:

$ ssh -Q help
cipher
cipher-auth
compression
kex
key
key-cert
key-plain
key-sig
mac

For a host-key warning, do not delete known_hosts wholesale. Confirm the new fingerprint first. If the host was intentionally rebuilt, remove only the affected entry with ssh-keygen -R <remote-host>, then reconnect and verify the replacement key. That command changes local state and cannot recover an old entry unless you have a backup, so record the old line before removing it if an audit trail matters.

Done means

  • You verified the installed client and know whether a connection is interactive or command-only.
  • You checked the first host-key fingerprint through a trusted channel.
  • Your alias resolves as expected with ssh -G.
  • Private keys and ~/.ssh/config are not readable or writable by other users.
  • Any tunnel is bound to the intended interface, and agent forwarding is enabled only for a deliberate trust boundary.