SSH Config: How To Work Faster With Remote Hosts
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 ofssh 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:
- it's easier to remember
- 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.