ssh_config is a power tool. I think few people realize that, because more often than not I see people remembering IP addresses by heart, using command-line ssh options such as -A repeatedly, and relying on their shell history to find out what options or IP addresses they usually reach out to.

Relying on history is absolutely fine, even a productivity boost, but depending on your history for configuration options seems like a bad idea to me. And I don't have the patience for learning to recognize IP addresses by heart.

Instead I use ssh_config and I think you should too. Not only will it make you faster at using ssh and its related tools like scp, you will also:

  • have a much more readable shell history (for example ssh work-laptop instead of ssh 10.0.0.13)
  • be able to chain node jumps

For more information, see man ssh_config.

Adding hosts

To start out, here's a simple config entry for my home router:

# ~/.ssh/config

Host router
  Hostname 10.77.77.1
  User johanan

This lets me type ssh router instead of ssh johanan@10.77.77.1. Not a huge improvement in terms of number of characters, but it brings two improvements:

  1. it's easier to remember
  2. it's easier to understand when looking at my shell history

Conditional IP addresses

There are a number of devices I need to access over SSH on a regular basis, both privately and at work. Sometimes I'm on the same network and sometimes I'm not. When I'm on the same LAN I want a direct connection to avoid latency, and when I'm not I want to connect over a jump host / VPN. To do this I maintain a single Host entry per device, but with conditional rules for how to access them depending on my current network connection.

Using my home router as an example again, we can improve its config entry like so:

# ~/.ssh/config

Match originalhost router exec "[ $(nmcli -g NAME c show --active | grep -xE 'wg0') ]"
  Hostname 10.66.66.1
Host router
  Hostname 10.77.77.1
  User johanan

This line, Match originalhost $HOST exec "$SHELL_COMMAND", will execute $SHELL_COMMAND when connecting to $HOST. If the command is successful the configuration entries under Match will be applied on top of the configuration options for $HOST.

In this example, that means that ssh router will connect to 10.66.66.1 if the interface wg0 is currently active (i.e. I'm connected to my home VPN with WireGuard). If the wg0 interface is inactive, ssh router will instead connect to 10.77.77.1.

If you're on Mac, here is an alternative command instead of nmcli ..., as suggested on Stack Overflow. This will however only show connected WiFi networks, not wired.

/Sy*/L*/Priv*/Apple8*/V*/C*/R*/airport -I | awk '/ SSID:/ {print $2}'

For more information on using match rules, see man ssh_config & search for Match.

Key forwarding

Instead of using ssh -A, we can configure the same behaviour by using ForwardAgent true in the Host entry.

Note: be careful with ForwardAgent - you must trust the devices you SSH to with this option. From man ssh_config:

Agent forwarding should be enabled with caution. Users with the ability to bypass file permissions on the remote host (for the agent's Unix-domain socket) can access the local agent through the forwarded connection. An attacker cannot obtain key material from the agent, however they can perform operations on the keys that enable them to authenticate using the identities loaded into the agent.

My home router entry as an example again:

# ~/.ssh/config

Match originalhost router exec "[ $(nmcli -g NAME c show --active | grep -xE 'wg0') ]"
  Hostname 10.66.66.1
Host router
  Hostname 10.77.77.1
  ForwardAgent true
  User johanan

Chaining node jumps

Consider the following scenario. You have your local laptop with an SSH key. At work you have a build server of some kind, without an SSH key and you need to access a Git repository and build stuff there. The build server is not publicly exposed and you are not connected to the same LAN. To reach it you have to go through a jump host.

If you wanted to do some development work on the build server manually, you'd have to do something like this:

ssh -A foobar@123.123.123.24 # ssh to jump server (-A needed in order to forward the keys again to the build server)
ssh -A foobaz@124.124.124.25 # ssh to build server (-A needed to access the Git repositories)

With a reasonable sshconfig, you just have to do

ssh build-server

And this benefit extends to other SSH tools like SCP as well.

A sample config for the above scenario:

# ~/.ssh/config
Host build-server
  User foobaz
  ProxyJump jumphost
  ForwardAgent yes
  Hostname 124.124.124.25

Host jumphost
  User foobar
  ForwardAgent yes
  Hostname 123.123.123.24

Note that this means that you trust the jump host, since you are forwarding your SSH key to it. If you don't trust your jump host, you should add the SSH key to the build server so that you don't have to forward it.

Wildcards


Host usw???
    Hostname %h.skogsbrus.xyz

A configuration like above would let you SSH to e.g. the names usw001 & usw757 which would resolve to usw001.skogsbrus.xyz and usw757.skogsbrus.xyz.