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.
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.
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.
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.