- SSH Config Mastery: One File to Rule Them All Or: How I Learned to Stop Typing IP Addresses and Love the Config File
You’re sitting at your terminal, about to SSH into a server. You type:
ssh -i ~/.ssh/my_special_key_for_production_server_3.pem -p 2222 admin@192.168.47.123
And you think: “There has to be a better way.”
Spoiler alert: There is. It’s called ~/.ssh/config, and it’s about to change your life. Or at least your daily workflow. Which, let’s be honest, is basically the same thing for most of us.
What Is SSH Config?
The phone book for servers you actually want to call
The SSH config file is a plain text file that stores connection settings for your SSH sessions. Instead of remembering IP addresses, ports, usernames, and key files, you define them once and reference them by a friendly name.
Before:
ssh -i ~/.ssh/production_key.pem -p 2222 -o StrictHostKeyChecking=no admin@10.0.0.42
After:
ssh production
That’s it. That’s the tweet.
Where Does It Live?
| OS | Config Location | Default Permissions |
|---|---|---|
| macOS | ~/.ssh/config |
600 (rw——-) |
| Linux | ~/.ssh/config |
600 (rw——-) |
| Windows 11 | C:\Users\[username]\.ssh\config |
User read/write |
Pro tip: If the file doesn’t exist, create it. SSH won’t complain – it’ll just work without custom settings like a sad, unconfigured robot.
Setting Up SSH Config on macOS
The land of “it just works” (most of the time)
Step 1: Create or Edit the Config File
mkdir -p ~/.ssh
chmod 700 ~/.ssh
touch ~/.ssh/config
chmod 600 ~/.ssh/config
Step 2: Add Your First Host Entry
Open the file with your favorite editor:
nano ~/.ssh/config
Add a host entry:
Host devserver
HostName 192.168.1.100
User developer
Port 22
IdentityFile ~/.ssh/dev_key
Step 3: Connect Like a Boss
ssh devserver
That’s it. No more typing IP addresses like it’s 1995.
Setting Up SSH Config on Linux
Same procedure as every year, James
Good news: It’s identical to macOS. Linux and macOS both use OpenSSH, so the config format is the same.
mkdir -p ~/.ssh
chmod 700 ~/.ssh
nano ~/.ssh/config
chmod 600 ~/.ssh/config
The only Linux-specific consideration is SELinux (if you’re on RHEL/CentOS/Fedora):
1. If SELinux is being difficult
restorecon -Rv ~/.ssh
Setting Up SSH Config on Windows 11
Yes, Windows has native SSH now. I know, I was surprised too.
Windows 11 (and Windows 10 1803+) includes OpenSSH as a built-in feature. No more PuTTY! Unless you want PuTTY. Nobody’s judging.
Step 1: Verify OpenSSH Is Installed
Open PowerShell as Administrator:
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Client*'
If it shows State: NotPresent, install it:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Step 2: Create the Config File
1. Create .ssh directory if it doesn't exist
New-Item -ItemType Directory -Path "$env:USERPROFILE\.ssh" -Force
1. Create config file
New-Item -ItemType File -Path "$env:USERPROFILE\.ssh\config" -Force
1. Open in Notepad
notepad "$env:USERPROFILE\.ssh\config"
Step 3: Add Host Entries
Same format as macOS/Linux:
Host devserver
HostName 192.168.1.100
User developer
Port 22
IdentityFile C:\Users\YourName\.ssh\dev_key
Windows-specific gotcha: Use forward slashes OR escaped backslashes in paths:
- ✅
C:/Users/YourName/.ssh/key - ✅
C:\\Users\\YourName\\.ssh\\key - ❌
C:\Users\YourName\.ssh\key(single backslashes = escape sequences = pain)
SSH Config Syntax Deep Dive
Time to learn the ancient runes
Basic Structure
Host [alias]
Option1 value1
Option2 value2
Indentation is optional but highly recommended for your sanity.
Essential Options
| Option | Description | Example |
|---|---|---|
Host |
Alias name for this connection | Host production |
HostName |
Actual IP or FQDN | HostName 10.0.0.42 |
User |
Login username | User admin |
Port |
SSH port (default: 22) | Port 2222 |
IdentityFile |
Path to private key | IdentityFile ~/.ssh/my_key |
IdentitiesOnly |
Only use specified key | IdentitiesOnly yes |
ForwardAgent |
Forward SSH agent | ForwardAgent yes |
ProxyJump |
Jump through bastion host | ProxyJump bastion |
LocalForward |
Local port forwarding | LocalForward 8080 localhost:80 |
RemoteForward |
Remote port forwarding | RemoteForward 9090 localhost:3000 |
ServerAliveInterval |
Keep connection alive | ServerAliveInterval 60 |
ServerAliveCountMax |
Max keepalive attempts | ServerAliveCountMax 3 |
StrictHostKeyChecking |
Host key verification | StrictHostKeyChecking accept-new |
UserKnownHostsFile |
Known hosts file location | UserKnownHostsFile ~/.ssh/known_hosts |
Compression |
Enable compression | Compression yes |
AddKeysToAgent |
Auto-add keys to agent | AddKeysToAgent yes |
Real-World Config Examples
Because examples are worth a thousand man pages
Example 1: Simple Server Entry
Host webserver
HostName 192.168.1.50
User deploy
IdentityFile ~/.ssh/webserver_key
Usage: ssh webserver
Example 2: Jump Host / Bastion Setup
1. The bastion (jump host)
Host bastion
HostName bastion.company.com
User admin
IdentityFile ~/.ssh/bastion_key
1. Internal server accessible only through bastion
Host internal-db
HostName 10.0.0.50
User dbadmin
ProxyJump bastion
IdentityFile ~/.ssh/internal_key
Usage: ssh internal-db — SSH automatically jumps through bastion first!
Example 3: Multiple Servers with Wildcards
1. Settings for all production servers
Host prod-*
User deploy
IdentityFile ~/.ssh/production_key
StrictHostKeyChecking yes
Host prod-web
HostName 10.0.1.10
Host prod-api
HostName 10.0.1.11
Host prod-db
HostName 10.0.1.12
Port 2222
Example 4: GitHub/GitLab with Specific Keys
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github_key
IdentitiesOnly yes
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/gitlab_key
IdentitiesOnly yes
Now git clone git@github.com:user/repo.git uses the correct key automatically.
Example 5: Keep Connections Alive
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
This applies to ALL hosts and prevents those annoying “Connection reset by peer” messages.
Example 6: Local Port Forwarding (Access Remote Service Locally)
Host db-tunnel
HostName database.internal
User admin
LocalForward 5432 localhost:5432
IdentityFile ~/.ssh/db_key
Usage: ssh -N db-tunnel — Now connect to localhost:5432 to reach the remote database!
Example 7: Complete Dev Environment Entry
Host homelab
HostName 192.168.50.10
User admin
Port 22
IdentityFile ~/.ssh/homelab_key
ServerAliveInterval 60
ServerAliveCountMax 3
Usage: ssh homelab instead of ssh -i ~/.ssh/homelab_key admin@192.168.50.10
Security Best Practices
Because convenience shouldn’t mean compromised
1. Lock Down File Permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/id_* # Private keys
chmod 644 ~/.ssh/id_*.pub # Public keys
SSH will refuse to use keys with overly permissive permissions. It’s not being difficult—it’s saving you from yourself.
2. Use IdentitiesOnly yes
Without this, SSH tries ALL your keys against every server:
Host secure-server
HostName 10.0.0.1
User admin
IdentityFile ~/.ssh/specific_key
IdentitiesOnly yes # Only try this specific key
This prevents accidentally revealing which keys you have and speeds up connections.
3. Set Default Security Options
Host *
HashKnownHosts yes # Hash hostnames in known_hosts
PasswordAuthentication no # Force key-based auth
StrictHostKeyChecking ask # Verify host keys
4. Separate Keys for Different Purposes
Don’t use one key for everything. Organize by environment/purpose:
~/.ssh/
├── personal_github
├── personal_github.pub
├── work_production
├── work_production.pub
├── work_staging
├── work_staging.pub
└── homelab
└── homelab.pub
If one key is compromised, you only need to rotate that one.
Troubleshooting Common Issues
When SSH says “no” and you don’t know why
Problem: “Permission denied (publickey)”
Cause: Key not found, wrong permissions, or not offered.
Fix:
1. Verbose mode shows what's happening
ssh -vvv hostname
1. Check permissions
ls -la ~/.ssh/
1. Verify key is being offered
ssh-add -l
Problem: “Bad owner or permissions”
Cause: File permissions too open.
Fix:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/private_key
Problem: Config file seems to be ignored
Cause: Syntax error or wrong file location.
Fix:
1. Validate config syntax
ssh -G hostname
1. Check which config file is used
ssh -vvv hostname 2>&1 | grep "Reading configuration"
Problem: “Too many authentication failures”
Cause: SSH trying too many keys before finding the right one.
Fix:
Host problem-server
IdentityFile ~/.ssh/correct_key
IdentitiesOnly yes
Problem: Connection drops after inactivity
Fix:
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
Pro Tips
The good stuff
Tip 1: Use Include for Organization
Split your config into multiple files:
1. ~/.ssh/config
Include config.d/*
Host *
ServerAliveInterval 60
1. ~/.ssh/config.d/work
Host work-*
User corporate-username
IdentityFile ~/.ssh/work_key
1. ~/.ssh/config.d/homelab
Host lab-*
User admin
IdentityFile ~/.ssh/homelab_key
Tip 2: Quick Tunnels with Aliases
Host tunnel-grafana
HostName monitoring.internal
LocalForward 3000 localhost:3000
RequestTTY no
Host tunnel-postgres
HostName db.internal
LocalForward 5432 localhost:5432
RequestTTY no
Usage: ssh -fN tunnel-grafana — Open localhost:3000 in browser!
Tip 3: Match Blocks for Conditional Config
1. Different settings when on VPN
Match host *.internal exec "ping -c1 -W1 vpn-gateway"
ProxyJump none
Match host *.internal
ProxyJump bastion
Tip 4: Use ControlMaster for Connection Reuse
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600
Create the socket directory:
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets
Now multiple SSH sessions to the same host share one connection. Much faster!
Quick Reference Card
╔══════════════════════════════════════════════════════════════════╗
║ SSH CONFIG CHEAT SHEET ║
╠══════════════════════════════════════════════════════════════════╣
║ FILE LOCATIONS ║
║ macOS/Linux: ~/.ssh/config ║
║ Windows 11: C:\Users\[name]\.ssh\config ║
╠══════════════════════════════════════════════════════════════════╣
║ BASIC TEMPLATE ║
║ Host myserver ║
║ HostName 192.168.1.100 ║
║ User admin ║
║ Port 22 ║
║ IdentityFile ~/.ssh/my_key ║
╠══════════════════════════════════════════════════════════════════╣
║ JUMP HOST / BASTION ║
║ Host internal ║
║ HostName 10.0.0.50 ║
║ ProxyJump bastion ║
╠══════════════════════════════════════════════════════════════════╣
║ PORT FORWARDING ║
║ LocalForward 8080 localhost:80 (remote->local) ║
║ RemoteForward 9090 localhost:3000 (local->remote) ║
╠══════════════════════════════════════════════════════════════════╣
║ PERMISSIONS (REQUIRED!) ║
║ ~/.ssh/ 700 (drwx------) ║
║ ~/.ssh/config 600 (-rw-------) ║
║ Private keys 600 (-rw-------) ║
║ Public keys 644 (-rw-r--r--) ║
╠══════════════════════════════════════════════════════════════════╣
║ DEBUGGING ║
║ ssh -vvv hostname Verbose output ║
║ ssh -G hostname Show resolved config ║
║ ssh-add -l List loaded keys ║
╚══════════════════════════════════════════════════════════════════╝
Appendix: Option Reference Table
| Option | Risk Level | Description | When to Use |
|---|---|---|---|
Host |
🟢 Safe | Connection alias | Always |
HostName |
🟢 Safe | Actual server address | Always |
User |
🟢 Safe | Login username | When not default |
Port |
🟢 Safe | SSH port | Non-standard ports |
IdentityFile |
🟢 Safe | Private key path | Key-based auth |
IdentitiesOnly |
🟢 Safe | Only use specified key | Recommended always |
ProxyJump |
🟢 Safe | Bastion/jump host | Private networks |
LocalForward |
🟡 Caution | Local port forward | Tunnel to remote |
RemoteForward |
🟡 Caution | Remote port forward | Expose local service |
ForwardAgent |
🟠 Risk | Forward SSH agent | Only when needed |
StrictHostKeyChecking no |
🔴 Danger | Disable host verification | Never in production |
PasswordAuthentication yes |
🔴 Danger | Allow password auth | Avoid if possible |
Risk Level Legend:
- 🟢 Safe: No security concerns
- 🟡 Caution: Understand before using
- 🟠 Risk: Can expose credentials if misused
- 🔴 Danger: Security risk, avoid if possible
Conclusion: Your SSH Life, Simplified
You now know how to:
- Create and configure
~/.ssh/configon macOS, Linux, and Windows 11 - Define host aliases so you never type IP addresses again
- Set up jump hosts for accessing internal networks
- Configure port forwarding for tunnels and development
- Apply security best practices to keep your keys safe
- Troubleshoot common issues when things go wrong
The best part? This file follows you everywhere. Sync it across machines (minus the private keys!), and you’ll always have your server shortcuts ready.
Fun fact: The average DevOps engineer types SSH commands approximately 47,000 times per year. At 20 characters saved per command, that’s nearly a million characters – or about one medium-sized novel. You’re welcome.
Stay connected, stay curious 🔐
Leave a Reply