SSH Config File Mastery
Configure ~/.ssh/config for efficient multi-host management — host aliases, per-host keys, jump/bastion hosts, connection multiplexing, Match blocks for conditional logic, and wildcard patterns for fleet-scale SSH management.
Content
Overview
The SSH config file (~/.ssh/config) is the central control point for all SSH client behavior. It eliminates repetitive command-line flags, enforces per-host security policies, routes connections through bastion hosts, and multiplexes connections for performance. A well-structured config file turns ssh -i ~/.ssh/work_key -p 2222 -J bastion deploy@prod-app-01.internal.example.com into ssh prod-app.
How Matching Works
Understanding SSH's matching rules is essential before writing any config. SSH processes the config file top-to-bottom and collects settings from every Host or Match block that matches. For any given option, the first matching value wins. Later blocks can only add options that haven't been set yet — they cannot override earlier values.
This means: put specific hosts at the top, wildcard patterns in the middle, and your global Host * defaults at the bottom.
Host Configuration
Basic Host Aliases
IdentitiesOnly yes is critical. Without it, SSH sends every key loaded in your agent to the server, one by one. If you have five keys loaded and the server has MaxAuthTries 3, you'll get locked out before the correct key is even tried. IdentitiesOnly restricts the connection to only the specified IdentityFile.
Multiple GitHub/GitLab Accounts
A common challenge is using multiple Git hosting accounts from the same machine. The trick is using a different Host alias that still resolves to the same HostName:
Then clone with the alias: git clone github-work:company/repo.git. Git uses the Host alias to look up SSH settings, so each account gets its own key automatically.
Jump Hosts and Bastion Access
ProxyJump is the modern way to route SSH through an intermediate host. It's simpler and more secure than the older ProxyCommand approach because traffic is encrypted end-to-end — the bastion host never sees your private key or the decrypted session.
For multi-hop chains (rare but sometimes necessary in segmented networks):
ProxyJump keeps your private keys on your local machine at all times. Unlike agent forwarding, the bastion host never has access to your key material. Prefer ProxyJump over ForwardAgent yes whenever possible.
Connection Multiplexing
Multiplexing reuses a single TCP connection for multiple SSH sessions to the same host. The first connection creates a master socket; subsequent connections piggyback on it, skipping TCP handshake and authentication entirely.
ControlMaster auto creates a master connection if one doesn't exist or reuses an existing one. ControlPersist 600 keeps the master alive for 10 minutes after the last session disconnects, so reconnecting within that window is instant.
The ControlPath must include %r (remote user), %h (host), and %p (port) to avoid collisions between different connections. Create the socket directory:
Performance impact: On a typical SSH connection, the TCP handshake + key exchange + authentication takes 200-500ms. Multiplexed connections skip all of that. This is especially noticeable when running git pull or scp repeatedly against the same host — each operation reuses the existing connection.
When to disable multiplexing: On shared jump hosts (other users could access your socket), when running long-lived tunnels (a stuck master socket blocks all connections), or when debugging connection issues (multiplexing masks connection setup problems).
Match Blocks for Conditional Logic
Match blocks apply settings conditionally based on hostname, user, network, or the result of an external command. They're powerful for handling environments where your SSH behavior needs to change based on context.
The exec keyword runs a local command; if it returns exit code 0, the Match block applies. This lets you adapt SSH behavior based on which network you're on, whether you're in a container, or any other condition you can test with a command.
Wildcard Patterns for Fleet Management
When managing dozens or hundreds of servers, wildcard patterns prevent config file explosion:
The AWS SSM pattern is especially useful — it uses AWS Systems Manager as a transport layer, so instances don't need public IPs or open SSH ports. The Host i-* pattern matches EC2 instance IDs directly.
Security Defaults
Place these at the bottom of your config as global defaults:
HashKnownHosts yes is an often-missed security measure. If your ~/.ssh/known_hosts file is exfiltrated, hashed entries prevent an attacker from learning what servers you connect to. The tradeoff is that you lose tab-completion for hostnames from known_hosts.
File Permissions
SSH refuses to use the config file if permissions are too open:
Common Mistakes
- -Missing IdentitiesOnly: SSH tries all agent keys, hits MaxAuthTries before the right key
- -Wildcards before specific hosts: First match wins — specific hosts must come first
- -ForwardAgent on untrusted hosts: Anyone with root on that host can use your agent keys
- -Missing socket directory: ControlMaster fails silently if
~/.ssh/sockets/doesn't exist - -Wrong config permissions: SSH ignores the config file if it's group/world-readable
- -ControlMaster on shared bastions: Other users can hijack your multiplexed sessions
- -Using ProxyCommand when ProxyJump works: ProxyJump is simpler and chains more cleanly
- -StrictHostKeyChecking set to no globally: Disables MITM protection for all connections
FAQ
Discussion
Loading comments...