Vivian Voss

SSH Config: The File Nobody Reads

ssh unix devops

The Unix Way ■ Episode 07

You type ssh -i ~/.ssh/prod_key -p 2222 deploy@192.168.50.12 fourteen times a day. You have done this for years. You have committed the flags to muscle memory the way pianists commit scales. It works. It is also entirely unnecessary.

There is a plain text file that reduces this to ssh prod. It has existed since 1999. ~/.ssh/config. SSH reads it automatically on every connection. No flag, no import, no reload. One file. No GUI. No tool. No subscription.

The Basics

Host prod
  HostName 192.168.50.12
  User deploy
  Port 2222
  IdentityFile ~/.ssh/prod_key

Now: ssh prod. That is it. scp prod:logs.tar.gz . works too. rsync, git pull, VS Code Remote: they all read this file. You configure once, everything else follows.

Wildcards make it better:

Host dev-*
  User developer
  IdentityFile ~/.ssh/dev_key

Every host matching dev-* inherits the same user and key. dev-api, dev-frontend, dev-db: all covered. One pattern, not twelve entries.

The Multiplexer

Host *
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%r@%h-%p
  ControlPersist 600

Your first connection opens TCP, negotiates TLS, authenticates. Every subsequent connection to the same host reuses that socket. No handshake. No key exchange. Instant.

Ten terminals to the same server: one connection. The other nine are free. Rather changes the economics of SSH, that.

ControlMaster Multiplexing terminal 1 terminal 2 terminal 3 ... terminal 10 Unix socket ControlPath 1 TCP server sshd

The Jump

Host internal
  HostName 10.0.0.50
  ProxyJump bastion
  User admin

One line: ProxyJump bastion. SSH connects to the bastion first, then tunnels through to the internal host. No agent forwarding, which exposes your private keys on the bastion. No two-step manual process. One command: ssh internal.

Before OpenSSH 7.3 (2016), this required ProxyCommand with netcat. Now it is one word.

ProxyJump: One Command, Two Hops public network private network client ssh internal bastion no keys here 10.0.0.50 internal

The Keep-Alive

Host *
  ServerAliveInterval 60
  ServerAliveCountMax 3

Your connection drops after five idle minutes. Not because the server closed it: because a NAT gateway, a firewall, or a load balancer decided the session was stale. This sends a heartbeat every 60 seconds. Three missed heartbeats: disconnect. No more frozen terminals where you stare at a cursor that will never move again.

The Security Defaults

The first comment material from the LinkedIn post is worth including here, because it turns a convenience file into a security posture.

Host *
  IdentitiesOnly yes
  AddKeysToAgent yes
  HashKnownHosts yes
  StrictHostKeyChecking ask
  VisualHostKey yes

IdentitiesOnly tells SSH to try only the key you specified, not every key loaded in the agent. When you have five keys and the server allows three attempts before locking you out, this matters. AddKeysToAgent loads the key into ssh-agent after first use, so you do not run ssh-add after every reboot. VisualHostKey prints a randomart fingerprint on every connection. You will notice when a host key changes. Rather more reliable than reading hex strings at eight in the morning.

The Complete Config

For the impatient, the entire recommended baseline in one block:

# Global defaults
Host *
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%r@%h-%p
  ControlPersist 600
  ServerAliveInterval 60
  ServerAliveCountMax 3
  IdentitiesOnly yes
  AddKeysToAgent yes
  HashKnownHosts yes
  StrictHostKeyChecking ask
  VisualHostKey yes

# Production
Host prod
  HostName 192.168.50.12
  User deploy
  Port 2222
  IdentityFile ~/.ssh/prod_key

# Development wildcard
Host dev-*
  User developer
  IdentityFile ~/.ssh/dev_key

# Internal via bastion
Host internal
  HostName 10.0.0.50
  ProxyJump bastion
  User admin

Before first use:

mkdir -p ~/.ssh/sockets && chmod 700 ~/.ssh

That is the entire setup. No package manager involved.

What ~/.ssh/config Replaces connection managers credential GUIs bastion scripts SSH wrapper tools 80-char muscle memory ~/.ssh/config plain text, since 1999, 0 dependencies

The Point

~/.ssh/config is a plain text file. It works on FreeBSD, Linux, macOS, and anything running OpenSSH. It has not changed its syntax in 25 years. It requires no package, no daemon, no update cycle.

One file replaces connection managers, credential GUIs, bastion scripts, SSH wrapper tools, and the muscle memory of typing the same 80-character command fourteen times a day.

The full reference is one command away: man ssh_config. Then open the file. You will wonder why you waited.