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.
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.
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.
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
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.
If you genuinely need agent forwarding for a trusted bastion, scope it to that one host:
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
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.
RECOMMENDED
StrictHostKeyChecking yes for known infrastructure, or accept-new where first-seen hosts are expected.
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.
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
~/.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.
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.
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
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.
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.
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
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.
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
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.
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.
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
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.
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.
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
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:
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.
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 2 — ROBUSTNESS
UserKnownHostsFile
Isolate trust databases when appropriate.
ServerAlive*
Make dead sessions detectable.
ConnectTimeout
Prevent unreachable hosts from hanging indefinitely.
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.
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's configuration is superficially convenient. Alice's configuration is predictable.
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.
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.
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:
- Keep
ForwardAgent noglobally. Enable it only for explicitly trusted hosts that genuinely need it. - Don't weaken
StrictHostKeyCheckingto make warnings disappear. Useyesfor controlled infrastructure,accept-newfor suitable ephemeral environments, or leave the interactive defaultask. - Use
UserKnownHostsFilewhen you need separate trust domains. It controls the database; it does not replace host-key verification. - Use
ServerAliveIntervalandServerAliveCountMaxtogether. They turn dead connections into detectable failures. - Set a reasonable
ConnectTimeout. Especially for automation and laptops that regularly move between networks. - Leave
Compression,LogLevel, andPreferredAuthenticationssituational. 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
-
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 - ssh(1) — OpenBSD manual pages — General SSH client behaviour, host-key verification, the agent-forwarding warning, and diagnostic options. man.openbsd.org/ssh.1
- 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
-
Mozilla InfoSec — OpenSSH Guidelines — Practical OpenSSH hardening guidance, including warnings about agent forwarding and the use of
ProxyJumpas a safer alternative. infosec.mozilla.org/guidelines/openssh - 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