You type this 10 times a day:
ssh -i ~/.ssh/keys/prod -p 2222 admin@prod-server.example.comStop. There’s a better way.
Your ~/.ssh/config file can turn that into:
ssh prodThat’s it. One word. Everything else is configuration.
Basic Syntax
Host prod HostName prod-server.example.com User admin Port 2222 IdentityFile ~/.ssh/keys/prodNow:
$ ssh prod# Expands to: ssh -i ~/.ssh/keys/prod -p 2222 admin@prod-server.example.comMagic. (It’s not magic. It’s just config.)
Common Options
Host myserver HostName actual-hostname.com User myusername Port 2222 IdentityFile ~/.ssh/id_ed25519 IdentityFile ~/.ssh/id_rsa_backup
# Compression (helps over slow links) Compression yes
# Keep connection alive (no timeout) ServerAliveInterval 60 ServerAliveCountMax 10
# Disable password auth (force key-only) PasswordAuthentication no
# Enable agent forwarding (careful with security!) ForwardAgent yes
# Local port forwarding LocalForward 8080 localhost:8080
# X11 forwarding (for GUI apps) ForwardX11 yes
# Use a specific SSH config per-host ConfigFile ~/.ssh/configs/prod.confMultiple Keys Per Host
If a host accepts multiple keys, list them:
Host github.com HostName github.com User git IdentityFile ~/.ssh/github_rsa IdentityFile ~/.ssh/github_ed25519 IdentityFile ~/.ssh/personal_keySSH tries them in order. First one that works wins.
Wildcards (Pattern Matching)
Match groups of hosts:
Host prod-* User admin Port 2222 ForwardAgent no
Host dev-* User developer PasswordAuthentication yes
Host *.example.com User myuser IdentityFile ~/.ssh/id_ed25519Now:
$ ssh prod-web-01 # Uses admin, port 2222$ ssh dev-local # Uses developer, password auth$ ssh test.example.com # Uses myuser, ed25519 keyProxyJump: Jump Hosts (The Best Feature)
You have a jump host (bastion) you route through:
# Without config:ssh -J jumphost user@internal-server
# Or the old way (painful):ssh user@jumphost# Then from jumphost:ssh user@internal-serverWith config:
Host jumphost HostName jump.example.com User admin IdentityFile ~/.ssh/jump_key
Host internal HostName internal-server.local User appuser IdentityFile ~/.ssh/app_key ProxyJump jumphostNow:
$ ssh internal# SSH tunnels through jumphost automaticallyChain multiple jumps:
Host jump1 HostName jump1.example.com
Host jump2 ProxyJump jump1 HostName jump2.internal.local
Host final ProxyJump jump2 HostName final-server.local$ ssh final# Routes: local -> jump1 -> jump2 -> final-serverPractical Example: Multi-Environment Setup
# Production (strict security)Host prod-* HostName %h.prod.internal User deploy IdentityFile ~/.ssh/prod_rsa PasswordAuthentication no ForwardAgent no StrictHostKeyChecking accept-new
# Development (relaxed)Host dev-* HostName %h.dev.local User dev IdentityFile ~/.ssh/dev_rsa PasswordAuthentication yes
# BastionHost bastion HostName bastion.example.com User admin Port 2222
# Internal servers via bastionHost *.internal ProxyJump bastion User appuser IdentityFile ~/.ssh/internal_key
# Quick test serverHost test HostName 192.168.1.100 User root StrictHostKeyChecking noNow you can:
$ ssh prod-web-01 # Direct to prod$ ssh dev-api # Direct to dev$ ssh internal-db-01 # Routes through bastion$ ssh test # Local testingDebugging: See What’s Happening
$ ssh -G prod# Shows resolved config for host 'prod'Or verbose mode:
$ ssh -v prod# Shows connection detailsVery verbose:
$ ssh -vv prodInclude Other Config Files
For teams or complex setups:
# Global defaultsHost * ServerAliveInterval 60
# Include team configsInclude ~/.ssh/configs/team/*.confInclude ~/.ssh/configs/production.confThen manage separate files:
~/.ssh/configs/ production.conf staging.conf personal.confSCP and Rsync
Aliases work everywhere SSH is used:
# Copy file via SSH alias$ scp myfile prod:~
# Rsync via SSH alias$ rsync -av data/ prod:~/backup/Gotchas
%h expands to the Host name you use. So Host prod-* with HostName %h.internal means ssh prod-web-01 becomes prod-web-01.internal.
Order matters. First matching Host block wins. Put specific hosts before wildcards:
Host prod-db-01 # Specific config for this one Port 3333
Host prod-* # Generic config for all prod-* Port 2222StrictHostKeyChecking=accept-new is safer than =no. It accepts new hosts once, then locks them.
Permissions
Keep it readable but not world-visible:
chmod 600 ~/.ssh/configIf group or others can read it, SSH will ignore it for security.
Bottom Line
~/.ssh/config is your friend. Takes 5 minutes to set up, saves hours of typing. Most teams already need jump hosts and multiple environments—might as well make it painless.
ProxyJump Chains
If you need to hop through multiple bastions:
Host internal-db HostName 10.0.1.50 User ubuntu ProxyJump bastion1,bastion2This chains through bastion1 then bastion2 to reach internal-db. Each jump is a separate SSH connection. The syntax works in OpenSSH 7.3+, which is anything modern.
Check your version: ssh -V.