SSH Config (~/.ssh/config) is the file that maps hostnames to connection parameters. It is the reason you can type ssh alpha instead of ssh -i ~/.ssh/id_ed25519_orbstack -p 2222 [riclib](/wiki/riclib)@192.168.139.66 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null. It is the difference between using SSH and suffering SSH.
The Format
SSH config is a series of Host blocks, each defining how to connect to a machine:
Host alpha
HostName 192.168.139.66
User riclib
IdentityFile ~/.ssh/id_ed25519
Now ssh alpha works. Three lines replaced twenty flags. The file is plain text. The syntax is Key Value with no equals sign, no quotes (usually), no colons, no YAML, no JSON, no TOML. It predates all of them. It will outlive all of them.
The ProxyJump
The most powerful and most confusing directive is ProxyJump — SSH through an intermediate host:
Host alpha-via-mac
HostName 192.168.139.66
ProxyJump mac
This tells SSH: connect to mac first, then from mac connect to alpha. The intermediate hop is transparent. You type ssh alpha-via-mac and you’re on alpha. The connection bounced through mac. You didn’t notice. SSH didn’t mention it.
ProxyJump is how you reach machines behind NAT without port forwarding. It is also the thing that Tailscale made unnecessary, because Tailscale gives every machine a stable IP that every other machine can reach directly. ProxyJump solves the problem of machines being unreachable. Tailscale solves the problem of machines being unreachable by making them reachable.
THE CONFIG FILE IS A MAP
OF DOORS AND HALLWAYSPROXYJUMP IS THE HALLWAY
THROUGH ANOTHER ROOMTAILSCALE IS THE HALLWAY
THAT GOES DIRECTLY THERETHE BEST HALLWAY
IS THE ONE THAT DOES NOT
REQUIRE A MAP🦎
The Archaeology
Every developer’s SSH config is an archaeological record. The oldest entries are from the first job. The newest entries are from this morning. Between them: machines that no longer exist, jump hosts for companies you left three years ago, port numbers that made sense once, and comments like # TODO: fix this that will never be fixed because the machine they refer to was decommissioned in 2019.
# Old AWS bastion (do we still have this?)
Host bastion-prod
HostName 54.x.x.x
User ubuntu
IdentityFile ~/.ssh/legacy-aws.pem
# I think this was the staging server?
Host staging-maybe
HostName 10.0.3.47
ProxyJump bastion-prod
# NOTE: key might be rotated
These entries are never deleted. Deleting an SSH config entry requires certainty that the machine is gone, and certainty requires checking, and checking requires connecting, and connecting requires the SSH config entry you were about to delete. The circularity is structural.
The Tailscale Simplification
Before Tailscale, a typical SSH config for reaching OrbStack VMs from multiple locations required ProxyJump entries, conditional includes, and the quiet acceptance that the phone couldn’t reach anything.
After Tailscale:
Host alpha
HostName alpha
Or nothing at all, because Tailscale SSH with --ssh handles authentication directly. The SSH config entry becomes optional. The hostname resolves via MagicDNS. The authentication happens via Tailscale’s identity provider. The config file shrinks. The archaeology slows.
The Mosh Complication
SSH config works for SSH. It does not work for Mosh. Mosh uses SSH only for the initial handshake — it reads the SSH config to find the host and authenticate — but then switches to UDP. The ProxyJump directive doesn’t help mosh’s UDP traffic. The carefully constructed hallway of intermediate hosts applies only to the TCP handshake. The actual connection, the part that matters, the part that makes mosh survive cellular handoffs, goes direct.
This means mosh needs direct UDP reachability between client and server. Which means Port Forwarding or Tailscale. Which is another way of saying: SSH config solves half the problem, and the half it doesn’t solve is the important half.
Measured Characteristics
Lines in a typical developer's SSH config: 20-200
Lines that are still relevant: 60%
Lines that refer to machines that no longer exist: 25%
Lines that are comments saying "TODO": 15%
TODO comments that will be addressed: 0%
Time saved by SSH config vs typing flags: ~8 seconds per connection
Connections per day: 10-50
Annual time saved: ~20 hours
Entries made unnecessary by Tailscale: most of the ProxyJump ones
Entries that will be deleted: none (see circularity)
See Also
- Tailscale — Made half the config unnecessary
- Mosh — Uses SSH config for bootstrap but ignores it for the actual connection
- Port Forwarding — What ProxyJump replaced, and what Tailscale replaced both of
- UDP — The reason SSH config doesn’t fully solve the mosh problem
