Build a Reliable SSH Client Configuration

An SSH client configuration is a small policy file, not a collection of shortcuts. A predictable configuration should identify the intended host, use a named account, select the right key deliberately, verify the host key, and make failure visible. The examples below assume a private management network or an already-approved access path. They do not create a public SSH listener, a firewall rule, or a port forward.

Keep a recovery session open. Test a new client rule in a second terminal while an already-working session remains available. Never solve a connection error by disabling host-key checking or by copying a private key to a server.

Know which setting wins

OpenSSH reads command-line options, the per-user file ~/.ssh/config, and the system-wide client file (normally /etc/ssh/ssh_config). For a particular option, the first value obtained wins. In practice, a command-line option is the most specific override, a matching block in the user file is considered before the system file, and a value placed earlier in a matching file can beat a later value. Do not assume that the last visually matching block wins.

Use a narrow Host pattern and put general defaults near the bottom. A pattern such as Host * can set safe defaults, but a later-looking specific block cannot necessarily replace an option already obtained from that wildcard. Use ssh -G to inspect the effective result instead of reasoning from the file alone.

# Show the effective configuration for the alias, without connecting.
ssh -G production-app | grep -E '^(hostname|port|user|identityfile|identitiesonly|forwardagent|canonicalizehostname) '

# Show how a one-off command-line override changes the result.
ssh -G -o IdentitiesOnly=yes production-app | grep -E '^(user|identityfile|identitiesonly) '

ssh -G expands the configuration for a destination and prints it without opening a session. Treat the output as diagnostic data: it can include local paths and policy values, so do not paste it into a public issue without reviewing it.

Create aliases that state intent

Make the alias different from a DNS name so it is difficult to confuse a maintenance target with an arbitrary host. Store the file with mode 0600; the directory should normally be 0700. The example uses a private address that is already reachable through the site’s approved network, not a public address.

# ~/.ssh/config
Host app-prod
    HostName 192.168.60.10
    User deploy
    Port 22
    IdentityFile ~/.ssh/id_ed25519_app_prod
    IdentitiesOnly yes
    ForwardAgent no
    ServerAliveInterval 30
    ServerAliveCountMax 3

# Conservative defaults for every other destination.
Host *
    IdentitiesOnly yes
    ForwardAgent no
    AddKeysToAgent no
    PasswordAuthentication no

HostName is the network destination while Host is the local alias. IdentitiesOnly yes tells the client to offer configured identity files rather than trying every key an agent happens to hold. This avoids authentication failures caused by too many offers and reduces accidental use of the wrong credential. PasswordAuthentication no is a client preference, not a server-side guarantee; the server must enforce its own authentication policy.

Do not put private key material, passphrases, or one-time tokens in this file. Use a passphrase-protected key and an operating-system credential store or a carefully scoped agent where policy permits.

Choose keys and host keys deliberately

  • Use a separate key for a separate administrative role or trust boundary. Restrict its file permissions and back it up only through an approved encrypted process.
  • On a first connection, compare the server’s fingerprint with an out-of-band record from the administrator or host console. Accepting a prompt without verification does not authenticate the server.
  • After a host rebuild, investigate a changed key and use ssh-keygen -F app-prod and the documented host-replacement process. Do not reflexively remove a warning with ssh-keygen -R.
  • Keep StrictHostKeyChecking yes for production aliases when the team maintains a known-hosts process. A deliberate enrollment step is safer than silently trusting a changed key.
# Inspect the recorded key without contacting the server.
ssh-keygen -F app-prod

# Verify a key file supplied through the approved host inventory.
ssh-keygen -lf /path/to/approved-host-key.pub

If a host is addressed by an alias, ensure the inventory records both the alias and the real name or address. Avoid StrictHostKeyChecking=no and UserKnownHostsFile=/dev/null in production: they suppress evidence of interception or accidental misrouting.

Use Match for exceptions, not surprises

Match applies conditionally to the local client configuration. Its scope continues until the next Host or Match line, and a later option still cannot undo a value already selected earlier. Keep exceptions explicit and test their effective output.

Host app-prod
    HostName 192.168.60.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519_app_prod
    IdentitiesOnly yes
    ForwardAgent no

# A separate alias makes the maintenance exception visible.
Host app-maintenance
    HostName 192.168.60.10
    User admin
    IdentityFile ~/.ssh/id_ed25519_admin
    IdentitiesOnly yes
    RequestTTY yes
    ForwardAgent no

# Match examples should be placed after ordinary Host blocks.
Match host app-maintenance exec "test -f $HOME/.ssh/maintenance-approved"
    ServerAliveInterval 15

Be cautious with exec conditions: they run a local command and should not depend on untrusted, interpolated input. In many environments, a separate alias is clearer than a clever conditional.

Make one controlled connection

# Check the selected policy, then connect with the alias.
ssh -G app-prod | grep -E '^(hostname|user|port|identityfile|identitiesonly|forwardagent) '
ssh -vv app-prod

Use verbose output for diagnosis, then remove it from routine command transcripts. It can reveal usernames, hostnames, paths, and negotiated algorithms. A successful key exchange does not prove that the remote account has the intended authorization; confirm the account and role on the server.

For a temporary diagnostic override, prefer a visible -o option over editing the shared file:

ssh -o ConnectTimeout=10 -o IdentitiesOnly=yes app-prod

Do not use -o StrictHostKeyChecking=no, broad wildcard identities, or a public jump host as a shortcut around a failed trust decision. Fix name resolution, routing, the host inventory, or server authorization at its actual boundary.

Review and maintain the policy

  1. Record each alias’s owner, destination, account, key owner, and recovery path in the team’s protected inventory.
  2. Run ssh -G after changing an include, alias, key, or Match rule. Check that forwarding is disabled unless an article or change record explicitly requires it.
  3. Remove retired aliases and rotate keys through the server-side authorization process. Do not leave old keys in agents or authorized_keys.
  4. Test from a second terminal before replacing a known-good configuration. Keep a console or documented alternate administrator path for the remote host.

Continue with Install and Harden an OpenSSH Server to apply the corresponding server policy.

Sequence navigation

Previous: Use ssh-agent and Hardware-Backed Keys · Next: Install and Harden an OpenSSH Server

References