Andrew Mercer

OpenSSH Client Configuration

Practical ~/.ssh/config patterns and ssh-agent setup. For protocol background and algorithm choices, see openssh-comprehensive-guide.md.


1. Create the config file

touch ~/.ssh/config
chmod 0600 ~/.ssh/config

ssh silently ignores a config file with permissions looser than 600 on some systems, or refuses to use it on others — always set this.

2. Global defaults + per-host blocks

Host * sets defaults; later, more specific Host blocks override individual directives for matching hosts. Order matters — put Host * first, specific hosts after.

Host *
  ForwardAgent no
  ForwardX11 no
  ServerAliveInterval 60
  ServerAliveCountMax 3

Host redhat
  HostName 192.168.0.52
  User amercer
  ForwardAgent yes

Note on ForwardAgent yes: avoid this as a global default. It hands the target host the ability to use your local agent to authenticate onward as you for the life of the connection — meaningful risk if that host isn't fully trusted (e.g. a shared jump box). Scope it per-host only where genuinely needed, or prefer ProxyJump instead — see the comprehensive guide, §5.3.

3. Specify an identity (key) per host

Host some_host
  IdentityFile ~/.ssh/id_ed25519_some_host
  IdentitiesOnly yes

IdentitiesOnly yes is the fix for "Too many authentication failures" — without it, ssh (or a loaded agent) will offer every available key to the server until one works or MaxAuthTries is exhausted.

4. Keepalives (avoid dropped "idle" sessions)

If sessions die with Read from remote host <host>: Connection reset by peer after a period of inactivity — a NAT gateway, firewall, or load balancer is silently dropping the idle TCP connection. Client-side keepalives keep it alive:

# ~/.ssh/config
Host *
  ServerAliveInterval 60
  ServerAliveCountMax 3

Or per-connection: ssh -o ServerAliveInterval=60 hostname.

This sends an encrypted no-op through the channel every 60s if no other traffic has occurred, prompting a response from the server; after ServerAliveCountMax missed responses, the client disconnects. This is a client-driven setting — the corresponding server-side directive is ClientAliveInterval in sshd_config (see openssh-server-hardening.md). You generally only need one side configured; setting both is fine and common.

5. ssh-agent on Windows (Git Bash / Cygwin)

Modern Git for Windows and WSL both ship a usable ssh-agent; the manual bootstrap below is still relevant for older Git Bash/Cygwin installs that don't auto-start one.

# ~/.bash_profile
SSHAGENT=/usr/bin/ssh-agent
SSHAGENTARGS="-s"
if [ -z "$SSH_AUTH_SOCK" -a -x "$SSHAGENT" ]; then
  eval $( $SSHAGENT $SSHAGENTARGS )
  trap "kill $SSH_AGENT_PID" 0
fi
chmod a+x ~/.bash_profile
source ~/.bash_profile
ssh-add ~/.ssh/id_ed25519

Verify loaded keys:

ssh-add -l   # fingerprints
ssh-add -L   # full public keys

Git Bash and Cygwin config differ only in path conventions for IdentityFile:

# Git Bash
Host myhost
  HostName myhost
  User amercer
  IdentityFile /c/Users/amercer/.ssh/id_ed25519

# Cygwin
Host myhost
  HostName myhost
  User amercer
  IdentityFile /home/amercer/.ssh/id_ed25519

The historical examples this section was based on used Protocol 2 and id_rsa — both are obsolete: SSH-1 (Protocol 1) was removed from OpenSSH entirely years ago, so the directive is meaningless now, and ed25519 keys are preferred over RSA. See openssh-key-management.md.

6. Windows domain / Active Directory usernames

If your account is DOMAIN\username rather than a plain Unix username (common when a Linux/BSD server authenticates against AD via SSSD or Winbind):

ssh 'DOMAIN\username@host'

The backslash needs escaping or quoting in most shells (DOMAIN\\username@host also works unquoted in bash).