A good ~/.ssh/config does more than save you from typing long commands. It can prevent your SSH agent from being exposed to untrusted machines, make host identity verification explicit, keep dead connections from hanging forever, and make authentication behaviour predictable. But not every SSH option belongs in a global "security" block. Some are genuinely security-critical, some improve robustness, and others are useful only when your particular network or workflow calls for them. This article focuses on nine directives where that distinction matters.

The Problem

SSH already ships with sensible defaults. The danger is not that a completely empty configuration is automatically insecure; the danger is that a few seemingly harmless convenience settings can change the security boundary of a connection.

Consider a developer who regularly connects to a production server, a staging machine, and a jump host. Their SSH agent holds several private keys, the connections sometimes pass through unreliable networks, and the infrastructure occasionally gets rebuilt.

First, what is the "SSH agent"?

The SSH agent is a small background program that keeps your decrypted private keys in memory, so you type each key's passphrase once instead of on every connection. You can load more than one key into it — say, a personal key, a work key, and a bastion key — and it holds all of them at the same time, ready to sign on your behalf. That is what "the agent holds several keys" means throughout this article. You can list whatever your agent currently holds with ssh-add -l. Keep this picture in mind: it is exactly what makes the first setting below so consequential.

bash — what looks like a normal connection
alice@laptop:~$ ssh production The authenticity of host 'production' can't be established...

The important questions are not just "can I connect?" but:

  • Can a compromised remote host use my SSH agent to authenticate somewhere else?
  • Will SSH silently accept a new or changed host key?
  • Where are trusted host keys stored, and are they isolated between environments?
  • What happens when the network disappears for ten minutes?
  • Will an unreachable host block a script for several minutes?
  • Am I changing compression or authentication behaviour for a good reason, or simply because a blog told me to?

These are different problems, and they deserve different levels of configuration.

SSH CLIENT HARDENING SECURITY-CRITICAL ForwardAgent StrictHostKeyChecking Protect trust boundaries ROBUSTNESS UserKnownHostsFile ServerAlive* ConnectTimeout Make failure predictable SITUATIONAL Compression LogLevel PreferredAuthentications Use when the situation warrants it Not every useful SSH option should become a global hardening rule.
The nine directives fall into three different categories: protect trust boundaries, improve failure behaviour, or solve a specific operational problem.

Security-Critical Settings

These are the settings where a careless value can directly weaken a security boundary. They deserve deliberate configuration rather than blind copying.

1. ForwardAgent — don't give the remote host your agent Critical

DEFAULT no — the SSH authentication agent is not forwarded to the remote host.

ForwardAgent controls whether your local SSH agent becomes available through the SSH connection. With ForwardAgent yes, a special channel — a Unix-domain socket — is opened on the remote host that pipes signing requests back to the agent running on your own machine.

Here is the part worth slowing down on, because it is easy to assume forwarding is harmless when it is not. Agent forwarding does not copy your private keys to the remote host — the key files never leave your laptop. So far, so safe. But the forwarded socket is a live line back to your agent, and your agent will happily sign whatever authentication challenges come down that line. While your session is open, anyone on the remote host who can reach that socket — in practice the root user, or an attacker who has already compromised the machine — can send their own signing requests to your agent.

They cannot read your keys. They do not need to. They can ask your agent to prove your identity to other servers, which means they can log in as you anywhere those keys are trusted, for as long as your session lasts. And this is where the earlier detail matters: because the agent holds all your loaded keys at once, a single compromised host does not merely expose the one key you used to reach it — it exposes your entire keyring. If your agent is holding your production key, your bastion key, and your personal key, an attacker on one forwarded host can attempt to use all three. OpenSSH's own manual states the warning plainly: agent forwarding should be enabled with caution.

YOUR LAPTOP ssh-agent holds: 🔑 production key 🔑 bastion key 🔑 personal key forwarded socket signing requests REMOTE HOST compromised / untrusted attacker with root reaches the socket and asks your agent to sign for them logs in as you other servers The key files never leave your laptop — but the attacker can still use them while your session is open.
Agent forwarding never copies your keys. It opens a line back to your agent — and whoever controls the remote host can use that line to authenticate as you elsewhere.
✗ RISKY — GLOBAL FORWARDING
Host * ForwardAgent yes
✓ RECOMMENDED — OFF BY DEFAULT
Host * ForwardAgent no

If you genuinely need agent forwarding for a trusted bastion, scope it to that one host:

~/.ssh/config
Host trusted-bastion HostName bastion.example.com ForwardAgent yes Host * ForwardAgent no
Better alternative: ProxyJump

If your only reason for agent forwarding is reaching a private server through a bastion, consider ProxyJump instead. The SSH client can use the bastion as a transport hop without exposing your authentication agent to it. Agent forwarding should be a deliberate requirement, not the default way to implement a jump host. Mozilla's OpenSSH guidance specifically recommends ProxyJump as a safer alternative.

2. StrictHostKeyChecking — decide how much you trust host identity Critical

DEFAULT ask — new host keys require confirmation; changed host keys are rejected.

SSH does not authenticate the server merely because the connection is encrypted. It also needs to know which server is on the other end. Host keys stored in known_hosts provide that continuity of identity.

StrictHostKeyChecking controls what happens when the host key is unknown or has changed. This is one of the most consequential settings in a client configuration because accepting a changed key without investigation can turn an identity warning into a silent trust decision. Modern OpenSSH supports four practical values: yes, accept-new, ask, and no/off.

The values, from safest to most permissive

Value New host Changed host key Risk Typical use
yes Reject Reject Lowest Production, controlled infrastructure, pre-provisioned hosts
accept-new Accept automatically Reject Low Ephemeral infrastructure where first-seen keys are acceptable
ask Ask the user Reject Low / Medium Normal interactive use; OpenSSH default
no / off Accept automatically May accept High Rare compatibility cases; generally avoid

The important distinction between accept-new and no is what happens after the first connection. accept-new automatically records an unknown host key but still refuses a changed key. That makes it useful for environments where hosts are frequently created but identity changes should remain exceptional.

HIGH RISK

StrictHostKeyChecking no. A changed host key can be accepted instead of forcing investigation.

INTERACTIVE DEFAULT

ask is a reasonable general-purpose choice when humans can verify new hosts.

For a hardened workstation configuration, yes is the strongest choice — but only if you have a reliable process for provisioning host keys. Otherwise, you are likely to train users to bypass the warning instead of investigating it.

The warning you should never blindly "fix"

If SSH tells you that a known host key has changed, do not immediately delete the old entry and reconnect. A legitimate server rebuild is one explanation; DNS changes, a wrong target, or an active man-in-the-middle attack are others. Verify the new fingerprint through a trusted channel before replacing the old key.

Robustness Settings

These directives are less about cryptographic strength and more about making SSH behave predictably when networks fail, machines disappear, or several environments need separate trust databases. They are especially valuable for automation, laptops, VPNs, cloud infrastructure, and unreliable networks.

3. UserKnownHostsFile — control where host trust is stored Recommended

DEFAULT ~/.ssh/known_hosts (and, depending on OpenSSH version, ~/.ssh/known_hosts2).

UserKnownHostsFile changes the file or files used as the user's host-key database. This becomes particularly useful when you need different trust domains: for example, a temporary lab environment, a CI job, or an isolated fleet whose host keys should not be mixed with your everyday SSH history.

~/.ssh/config — separate trust database
Host lab-* UserKnownHostsFile ~/.ssh/known_hosts_lab

This is not a replacement for StrictHostKeyChecking. Think of the two directives as answering different questions: StrictHostKeyChecking decides how SSH reacts to host identity, while UserKnownHostsFile decides where that identity information is kept.

Useful for ephemeral infrastructure

A disposable environment can have its own known-hosts file without polluting the main ~/.ssh/known_hosts. This makes teardown and recreation much cleaner — but it does not make it safe to blindly accept changed keys.

4. ServerAliveInterval + ServerAliveCountMax — detect dead sessions Recommended

DEFAULT ServerAliveInterval 0 and ServerAliveCountMax 3.

These two directives work together.

ServerAliveInterval tells the client to send an encrypted keepalive request after a specified number of seconds with no data received from the server. ServerAliveCountMax determines how many unanswered probes are tolerated before SSH disconnects. The default interval is 0, meaning no SSH-level keepalive messages are sent; the default count is 3.

~/.ssh/config
Host * ServerAliveInterval 30 ServerAliveCountMax 3

With that configuration, SSH sends a probe after 30 seconds of inactivity and disconnects after three unanswered probes — roughly 90 seconds of unresponsiveness.

These are different from TCP keepalives. SSH-level server-alive messages travel through the encrypted SSH channel, whereas TCP keepalive operates at the transport layer. The SSH mechanism is therefore useful when you want the SSH client itself to know that the encrypted session has stopped responding.

Why this matters on laptops

Wi-Fi changes, VPNs, sleep/wake cycles, NAT timeouts, and disappearing network routes can leave an SSH process waiting on a connection that is no longer useful. Server-alive settings turn that ambiguous state into a deterministic disconnect.

5. ConnectTimeout — don't wait forever to connect Recommended

DEFAULT The system's normal TCP connection timeout; SSH does not impose a short application-level timeout by default.

ConnectTimeout limits how long SSH waits while establishing the connection. In current OpenSSH, the timeout also covers the initial SSH protocol handshake and key exchange.

~/.ssh/config
Host * ConnectTimeout 10

Ten seconds is a useful starting point for interactive systems, although the right value depends on your network. A very aggressive timeout can make high-latency links unreliable; an excessively long timeout makes automation painfully slow when a host is down.

Situational Settings

These directives are useful, sometimes very useful, but they should not automatically be promoted into a universal security policy. Their correct values depend on the workload, network, debugging needs, and authentication architecture.

6. Compression — useful on slow links, unnecessary on fast ones Situational

DEFAULT no — SSH does not request compression by default.

Compression yes asks SSH to compress the data stream before encryption. It can reduce bandwidth consumption and improve interactive performance over slow links, particularly for compressible text-heavy traffic.

~/.ssh/config — a slow-link profile
Host remote-slow-link Compression yes

On a fast modern connection, compression can simply consume CPU without producing a meaningful bandwidth benefit. Already-compressed data — images, archives, videos, many database dumps — generally gains little.

Don't treat compression as a security setting

Compression is primarily a performance decision. Whether it is appropriate depends on the traffic and environment. Keep it disabled globally unless you have a concrete reason to enable it.

7. LogLevel — useful when diagnosing SSH, noisy when overused Situational

DEFAULT INFO.

LogLevel controls how much diagnostic information the SSH client emits. Common values include QUIET, FATAL, ERROR, INFO, VERBOSE, and the increasingly detailed DEBUG1 through DEBUG3.

bash — diagnose a connection
alice@laptop:~$ ssh -vvv production OpenSSH_... debug1: Reading configuration data ... debug1: Connecting to production ...

For everyday use, INFO is usually the right balance. For troubleshooting, temporarily use -v, -vv, or -vvv on the command line instead of permanently increasing the global log level.

Why not DEBUG3 globally?

Because diagnostic output is for humans investigating a problem. Making every normal SSH invocation maximally verbose creates noise and can expose operational details in logs, terminal recordings, or automation output. Keep the normal configuration quiet and increase verbosity only when needed.

8. PreferredAuthentications — control authentication order Situational

DEFAULT A client-defined preference order; modern OpenSSH normally prioritizes methods such as gssapi-with-mic, hostbased, publickey, keyboard-interactive, and password depending on the build and enabled methods.

PreferredAuthentications controls the order in which SSH tries authentication methods. It does not itself enable an authentication method that the server does not permit.

This distinction matters. If your environment is explicitly designed around public-key authentication, you may prefer to make that preference obvious:

~/.ssh/config — key-first authentication
Host production PreferredAuthentications publickey,keyboard-interactive,password

But don't confuse ordering with disabling. If the goal is to prevent password authentication entirely, that is primarily a server-side policy such as PasswordAuthentication no and, depending on the environment, appropriate keyboard-interactive/PAM configuration. Mozilla's OpenSSH guidance recommends public-key-only authentication for suitable deployments.

The practical rule

Use PreferredAuthentications when you need to control which method gets tried first. Use server-side authentication policy when you need to control which methods are actually allowed.

A Practical Risk Classification

The easiest way to remember these directives is not by memorizing their names, but by asking what kind of problem they solve.

LEVEL 1 — SECURITY-CRITICAL

ForwardAgent
Keep disabled unless the destination is explicitly trusted.

StrictHostKeyChecking
Never weaken host identity checks merely to remove a warning.

LEVEL 3 — SITUATIONAL

Compression
Performance decision.

LogLevel
Diagnostic decision.

PreferredAuthentications
Authentication-order decision.

A Real-World Comparison

Consider two developers, Alice and Bob. Both connect from laptops to production infrastructure. Both use an SSH agent and both occasionally work from unreliable networks.

Their shared environment

Alice and Bob both have a production server, a staging server, and a bastion host. Their agent contains multiple keys. Production host keys are expected to remain stable; staging infrastructure is recreated more frequently.

Bob wants SSH to "just work", so he disables anything that interrupts him:

✗ BOB — TOO PERMISSIVE
Host * ForwardAgent yes StrictHostKeyChecking no ConnectTimeout 0 Compression yes
✓ ALICE — DELIBERATE
Host * ForwardAgent no StrictHostKeyChecking yes ServerAliveInterval 30 ServerAliveCountMax 3 ConnectTimeout 10

Bob's configuration is superficially convenient. Alice's configuration is predictable.

Bob's configuration ForwardAgent yes Remote host can access forwarded agent StrictHostKeyChecking no Changed host keys can be accepted ConnectTimeout 0 Unreachable hosts may take a long time Compression yes Enabled everywhere without a measured need Convenient — but trust boundaries are weak Alice's configuration ForwardAgent no Agent stays local unless explicitly needed StrictHostKeyChecking yes Unexpected identity changes stop the connection ServerAliveInterval 30 Dead sessions become detectable ConnectTimeout 10 Unreachable hosts fail predictably Predictable — security and operations align
Hardening is not about changing every SSH option. It is about making the important trust and failure decisions explicit.

Defaults at a Glance

Directive Default Recommended baseline Purpose
ForwardAgent no no Controls whether the local authentication agent is exposed through the connection
StrictHostKeyChecking ask yes or accept-new Controls how unknown and changed host keys are handled
UserKnownHostsFile ~/.ssh/known_hosts Keep default unless isolation is useful Chooses the user's host-key database
ServerAliveInterval 0 30 Sends encrypted keepalive probes after inactivity
ServerAliveCountMax 3 3 Controls how many unanswered probes are tolerated
ConnectTimeout System-dependent 10 Limits connection and initial handshake time
Compression no no Compresses SSH traffic; primarily a performance choice
LogLevel INFO INFO Controls client diagnostic verbosity
PreferredAuthentications Client-defined order Usually leave default Controls authentication-method preference order

A Hardened Configuration

Here is a practical hardened baseline. It deliberately does not turn every situational option into a global rule. The global block protects the most important boundaries and improves connection behaviour; host-specific blocks handle exceptions.

~/.ssh/config — hardened baseline
# ───────────────────────────────────────────── # Global security and robustness defaults # Specific hosts should appear before this block. # ───────────────────────────────────────────── Host * # Never expose the local agent by default. ForwardAgent no # Refuse unexpected host-key changes. StrictHostKeyChecking yes # Keep idle connections detectable. ServerAliveInterval 30 ServerAliveCountMax 3 # Fail reasonably quickly when a host is unreachable. ConnectTimeout 10 # Keep compression off unless a host needs it. Compression no # Normal diagnostic verbosity. LogLevel INFO # ───────────────────────────────────────────── # Production # ───────────────────────────────────────────── Host production HostName production.example.com User alice IdentityFile ~/.ssh/id_ed25519_production IdentitiesOnly yes # ───────────────────────────────────────────── # Trusted bastion — the one explicit exception # ───────────────────────────────────────────── Host trusted-bastion HostName bastion.example.com User alice IdentityFile ~/.ssh/id_ed25519_bastion IdentitiesOnly yes # If agent forwarding is genuinely required: ForwardAgent yes # ───────────────────────────────────────────── # Slow / high-latency connection # ───────────────────────────────────────────── Host remote-slow-link HostName remote.example.com Compression yes ConnectTimeout 20
One important caveat

StrictHostKeyChecking yes is intentionally strict. Before using this configuration across a fleet, make sure your host-key provisioning process is reliable. For infrastructure where new hosts are expected to appear automatically but changed keys must still be rejected, accept-new may be a better operational fit.

Checking What SSH Actually Uses

Configuration files become difficult to reason about once you have multiple Host blocks, includes, command-line overrides, and wildcard matches. Fortunately, OpenSSH can show the effective configuration before you connect.

bash — inspect effective configuration
alice@laptop:~$ ssh -G production user alice hostname production.example.com forwardagent no stricthostkeychecking yes serveraliveinterval 30 serveralivecountmax 3 connecttimeout 10

This is one of the best debugging tools for SSH configuration. Instead of guessing which Host block won, ask the client what configuration it resolved for the destination.

The Short Version

You don't need dozens of SSH options to build a safer configuration. Start with the trust boundary:

  1. Keep ForwardAgent no globally. Enable it only for explicitly trusted hosts that genuinely need it.
  2. Don't weaken StrictHostKeyChecking to make warnings disappear. Use yes for controlled infrastructure, accept-new for suitable ephemeral environments, or leave the interactive default ask.
  3. Use UserKnownHostsFile when you need separate trust domains. It controls the database; it does not replace host-key verification.
  4. Use ServerAliveInterval and ServerAliveCountMax together. They turn dead connections into detectable failures.
  5. Set a reasonable ConnectTimeout. Especially for automation and laptops that regularly move between networks.
  6. Leave Compression, LogLevel, and PreferredAuthentications situational. They solve performance, diagnostics, and authentication-order problems — not the same security problem.

The best SSH configuration is therefore not the longest one. It is the one that makes the important security decisions explicit, makes network failures predictable, and leaves everything else at a sensible default until there is a reason to change it.

Main References

  1. ssh_config(5) — OpenBSD manual pages — The primary reference for OpenSSH client configuration, including ForwardAgent, StrictHostKeyChecking, UserKnownHostsFile, keepalives, compression, logging, and authentication preferences. man.openbsd.org/ssh_config
  2. ssh(1) — OpenBSD manual pages — General SSH client behaviour, host-key verification, the agent-forwarding warning, and diagnostic options. man.openbsd.org/ssh.1
  3. ssh-agent(1) — OpenBSD manual pages — How the authentication agent holds decrypted keys in memory and answers signing requests. man.openbsd.org/ssh-agent.1
  4. Mozilla InfoSec — OpenSSH Guidelines — Practical OpenSSH hardening guidance, including warnings about agent forwarding and the use of ProxyJump as a safer alternative. infosec.mozilla.org/guidelines/openssh
  5. NISTIR 7966 — Security of Interactive and Automated Access Management Using Secure Shell (SSH); guidance on key management and restricting trust. nvlpubs.nist.gov/nistpubs/ir/2015/NIST.IR.7966.pdf
← Previous article
← Back to all articles