If you’re still typing ssh -i ~/.ssh/id_prod -p 2222 [email protected] every time you connect to a server, the Linux SSH config file is about to change your life. I’m not exaggerating. The file lives at ~/.ssh/config, and once you’ve set it up, that mile-long command becomes ssh prod. That’s it. That’s the whole pitch.

I’ve been managing a six-node homelab for years, plus a handful of cloud droplets, plus client servers. Without ~/.ssh/config, my terminal history would look like a ransom note made from IP addresses. With it, I have aliases, automatic key selection, jump host routing, and connection multiplexing โ all from one tidy text file that OpenSSH reads every time I run ssh, scp, rsync, or Ansible.
By the end of this guide, you’ll know how to write Host blocks, use ProxyJump to reach servers behind a bastion, and speed up Ansible runs with ControlMaster. There’s also one debugging command that 90% of tutorials skip โ I’ll cover it in the troubleshooting section, and it’ll save you hours.
Quick answer: The Linux SSH config file is ~/.ssh/config. Create it, set permissions to chmod 600, and add Host blocks like this: Host myserver / HostName 1.2.3.4 / User alexa / IdentityFile ~/.ssh/id_ed25519. Then connect with ssh myserver.
Get a VPS from as low as $11/year! WOW!
What Is the SSH Config File (And Why You’re Missing Out)
The SSH config file is a plain text file that tells your SSH client how to connect to remote hosts. Instead of memorizing IPs, ports, and key paths, you define them once under a friendly alias and forget the details forever.
It’s read every time you run any tool built on OpenSSH. That includes scp command, rsync, sftp, and Ansible. They all inherit your aliases automatically. No extra setup, no plugin gymnastics.
User Config vs System-Wide Config
There are two SSH client config files on a Linux system:
- ~/.ssh/config โ your personal user config. This is what you’ll edit 99% of the time.
- /etc/ssh/ssh_config โ the system-wide client config that applies to every user on the machine.
Important: don’t confuse /etc/ssh/ssh_config (client) with /etc/ssh/sshd_config (server daemon). They’re different files. If you’re tightening up the server side, that’s a separate job โ see my guide on secure SSH server configuration for that.
When Does SSH Read the Config File?
OpenSSH walks through configuration sources in this priority order:
- Command-line flags (highest priority)
~/.ssh/config(your personal config)/etc/ssh/ssh_config(system-wide config)
The first match for any given directive wins. So if you set Port 2222 in your user config and pass -p 22 on the command line, the flag wins. Knowing this order saves serious head-scratching later.
I learned this the hard way back when I was building out my homelab โ Proxmox, Pi-hole, Gitea, Nextcloud, all on different VLANs, all with different ports. I tried managing it with shell aliases. It was awful. The day ~/.ssh/config finally clicked, my mental overhead got cut in half.
Creating the SSH Config File
The file doesn’t exist by default on most distros. Create it and lock down the permissions in two commands:
touch ~/.ssh/config
chmod 600 ~/.ssh/config
The ~/.ssh/ directory itself should be chmod 700. If you’ve never set up SSH before, you’ll also want to generate an SSH key first โ a config file pointing at keys you don’t have isn’t going to do much.
Creating the File and Setting Permissions
Permissions matter more than you’d think. SSH is paranoid by design. If your config file is world-readable, SSH may silently ignore it or refuse to load certain directives. I’ve debugged this on a fresh VM more times than I’d like to admit.
Permissions checklist: ~/.ssh/ directory โ 700, ~/.ssh/config โ 600, private keys (id_ed25519, etc.) โ 600. If you copy your config between machines, re-check permissions on the new host. Need a refresher? See Linux file permissions.
SSH Config File Syntax: Host Blocks and Directives
The syntax is dead simple. Each Host keyword starts a new block. Everything indented below applies to that host pattern. Order matters โ the first matching block wins for each directive.
Host Blocks โ The Core Building Block
Here’s the smallest useful Host block:
Host prod
HostName 203.0.113.42
User deploy
Port 2222
IdentityFile ~/.ssh/id_prod
Now ssh prod connects you as [email protected] on port 2222 using the id_prod key. That single block replaces a 50-character command.
Global Defaults with Host *
The Host * pattern matches every host. It’s perfect for sane defaults:
Host *
ServerAliveInterval 120
ServerAliveCountMax 3
AddKeysToAgent yes
HashKnownHosts yes
Critical placement rule: put Host * at the bottom. SSH uses first-match-wins for most directives, so any specific host blocks must appear above the wildcard block.
Most-Used Directives Reference
| Directive | What It Does |
|---|---|
HostName |
The actual IP or FQDN to connect to |
User |
Login username on the remote host |
Port |
SSH port (default 22) |
IdentityFile |
Path to the private key |
IdentitiesOnly |
yes = only use the key you specified |
ServerAliveInterval |
Send keepalive every N seconds |
ServerAliveCountMax |
Max missed keepalives before disconnect |
ForwardAgent |
Forward your SSH agent (use carefully) |
ProxyJump |
Connect via a jump/bastion host |
Compression |
Compress traffic (good for slow links) |
For the full directive list โ and there are dozens more โ the official ssh_config man page is the canonical reference. I keep it bookmarked.
Host patterns also support wildcards. Host dev-* matches dev-web, dev-db, and dev-cache. Useful for applying common settings to a group of servers without copy-pasting.
Practical SSH Config Examples for Real-World Use
Theory is fine. Real configs are better. Here’s a setup pulled almost verbatim from my actual homelab.
Single Server Alias
The classic before-and-after:
# Before
ssh -i ~/.ssh/id_prod -p 2222 [email protected]
# After ~/.ssh/config
Host prod
HostName 203.0.113.42
User deploy
Port 2222
IdentityFile ~/.ssh/id_prod
# Now
ssh prod
Managing a Multi-Server Homelab
This is the config that runs my homelab. Names changed, IPs sanitized, but the structure is real:
Host proxmox
HostName 10.0.0.10
User root
IdentityFile ~/.ssh/id_homelab
Host pihole
HostName 10.0.0.20
User alexa
IdentityFile ~/.ssh/id_homelab
Host gitea
HostName 10.0.0.30
User git
Port 2222
IdentityFile ~/.ssh/id_homelab
Host nextcloud
HostName 10.0.0.40
User alexa
IdentityFile ~/.ssh/id_homelab
The first time this clicked for me was setting up five DigitalOcean droplets in one weekend. I’d been typing full IPs all morning, got fed up, sat down with a fresh cup of coffee, and rewrote my workflow with aliases. By the end of the day I was connecting to anything by name. Felt like the system finally trusted me to be lazy in the right way.
This same config makes managing remote storage trivial โ even spinning up a quick NFS server on a homelab node becomes ssh proxmox instead of digging up an IP.
Different Keys for Different Hosts
Don’t use the same key everywhere. I keep work, personal, and homelab keys separated:
Host work-*
IdentityFile ~/.ssh/id_work
IdentitiesOnly yes
Host homelab-*
IdentityFile ~/.ssh/id_homelab
IdentitiesOnly yes
Host github.com
IdentityFile ~/.ssh/id_personal
IdentitiesOnly yes
IdentitiesOnly yes is the unsung hero here. Without it, SSH offers every key in your agent to every server. With it, only the key you named gets sent. Cleaner logs, less risk of accidentally identifying yourself across contexts.
Preventing Dropped Connections with ServerAliveInterval
If your SSH sessions die after a few minutes of idle time, drop this in a Host * block:
Host *
ServerAliveInterval 120
ServerAliveCountMax 3
Your client now sends a tiny keepalive every 120 seconds. If three in a row fail, it disconnects gracefully instead of hanging forever. NAT routers and corporate firewalls love to kill idle connections โ this fixes it.
Advanced Techniques: ProxyJump and ControlMaster
This is where the SSH config file goes from convenient to indispensable.
ProxyJump for Bastion Hosts and Private Networks
ProxyJump lets you reach a private server through a public bastion host with one command. It was introduced in OpenSSH 7.3 (back in 2016) and replaces the older, clunkier ProxyCommand.
“ProxyJump is the easiest and recommended way to jump between hosts because it ensures that traffic passing through the intermediate hosts is always encrypted end-to-end.” โ Teleport Engineering Team
Here’s the setup. Define your bastion first, then reference it on private hosts:
Host bastion
HostName bastion.example.com
User alexa
IdentityFile ~/.ssh/id_ed25519
Host db-internal
HostName 192.168.10.50
User alexa
ProxyJump bastion
IdentityFile ~/.ssh/id_ed25519
Now ssh db-internal automatically routes through bastion. End-to-end encryption is preserved โ the bastion sees encrypted traffic only.
Need to chain through two jump hosts? Use a comma:
ProxyJump bastion1,bastion2
For deeper bastion patterns โ multi-hop, dynamic targets, the works โ the OpenSSH jump host cookbook is excellent. And if you also need port forwarding tunnels through that bastion, my SSH tunneling guide picks up where this one leaves off.
ControlMaster: Connection Multiplexing for Speed
This one quietly changed how I do everything. ControlMaster lets new SSH sessions reuse an existing connection to the same host. The first connection authenticates normally; every subsequent one reuses the open socket. The result is huge speedups for tools that open many SSH connections โ Ansible playbooks, recursive rsync, fast scp transfers.
The setup needs a sockets directory:
mkdir -p ~/.ssh/sockets
chmod 700 ~/.ssh/sockets
Then in your config:
Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 10m
ControlPersist 10m keeps the master connection open for 10 minutes after the last session closes. The next ssh within that window connects instantly.
SSH Config File Security Tips
Convenience without security is just a footgun. A few rules I live by:
- chmod 600 always. Re-check after copying configs between machines. SSH ignores configs that are too permissive.
- Use Ed25519 keys. They’re compact, fast, and resistant to side-channel attacks. RSA still works but Ed25519 is the modern default.
- Don’t put ForwardAgent yes in Host *. Only enable agent forwarding on hosts you fully trust. A compromised host can use your forwarded agent to hop to your other servers.
- Never store passphrases in the config. Use ssh-agent with
AddKeysToAgent yesinstead. - Set IdentitiesOnly yes when you specify an IdentityFile. Stops SSH from offering every key in your agent.
Client config is only half the story. Pair it with server-side hardening โ strong sshd_config, key-only auth, and fail2ban to block brute force attacks. Defense in depth.
Troubleshooting SSH Config Issues
When things break โ and they will โ these are the commands I reach for first.
ssh -v for verbose output
Run ssh -v hostname to see which config file and Host block SSH is applying. Add more vs for more detail: -vv, then -vvv for the full debugging firehose.
ssh -G for the resolved config
Here’s the command most tutorials skip โ and it’s the single most useful debugging tool I know:
ssh -G prod
ssh -G hostname prints the final, fully-resolved config that SSH would apply for that host. Every directive, every wildcard match, all merged. Whenever a connection acts weird, run ssh -G first. Half the time the answer jumps right out.
Common mistakes that bite everyone
- Permissions too open. Not 600 = SSH ignores the file. Run
ls -l ~/.ssh/configto verify. - Host * placed first. Specific hosts must come BEFORE the wildcard block. First match wins.
- Hostname vs HostName. Some implementations are case-sensitive. Stick to
HostNameas documented. - Wrong key offered. Add
IdentitiesOnly yesto your Host block.
If your connections feel sluggish even after fixing the config, that’s a separate rabbit hole โ I’ve got a full breakdown in troubleshooting slow SSH connections.
Wrapping Up
The SSH config file is one of those small Linux features that quietly transforms how you work. Five minutes of setup, and you’re managing dozens of servers like they’re a single fleet. Aliases, key routing, jump hosts, multiplexing โ all from a plain text file with permissions locked down at 600.
Start small. Add one Host block for the server you connect to most often. Then another. Within a week, you’ll wonder how you lived without it. BTW, I use Arch.
If you found this useful, you might also enjoy my walkthroughs on SSH tunneling for port forwarding, Ansible on Linux to automate config across all those new aliases, and the secure SSH server configuration guide to lock down the other side of the connection. Happy SSH-ing.




