Connect to an SSH server until it works
I’ve come across a small annoyance with SSH when waiting for a server to boot. For instance, while starting a Karaf container, rebooting a Linux VM, or waiting for an AWS instance to come online, I don’t like having to continually retry to connect to the server. Instead, I’ve been using a small little Bash function for a while now to continually retry to connect to the server. What’s neat about how this works is that if you get disconnected, it will continue to retry. However, if you disconnect manually, it will stop retrying because SSH exits with a successful status when you disconnect properly. The following function can be used with any implementation of SSH, but I recommend using OpenSSH.
function ssh-auto-retry()
{
false
while [ $? -ne 0 ]; do
ssh "$@" || (sleep 1;false)
done
}
This function can be used to automatically retry an SSH connection as described
above. There is also a related configuration option in OpenSSH that can be used
to retry connections a finite number of times. Set the ConnectionAttempts
option to a number larger than 1 to automatically retry a connection up to that
many times. What’s neat about this option is that you can set it per host. For
example, we can set up automatic connection retries on our local Karaf server
with the following ~/.ssh/config
snippet:
Host karaf
HostName localhost
Port 8101
User karaf
ConnectionAttempts 1000
More info about available OpenSSH options are in the ssh_config(5) man page.
Note that we can refactor the above ssh-auto-retry
function to make a generic
auto-retry
function for retrying shell commands until they succeed. This uses
a one second delay between each attempt, but an exponential backoff decay could
be added using some basic math. First, here’s a simple auto-retry
command:
function auto-retry()
{
false
while [ $? -ne 0 ]; do
"$@" || (sleep 1;false)
done
}
Then, we could easily rewrite ssh-auto-retry
as follows:
function ssh-auto-retry()
{
auto-retry ssh "$@"
}
As for an exponential backoff version, we can reimplement auto-retry
by doubling
the timeout every retry:
function auto-retry()
{
let backoff=1
false
while [ $? -ne 0]; do
"$@" || (sleep $((backoff*=2));false)
done
}
In theory, though, the backoff
variable will eventually overflow, but it would
take many years of sleeping before that would happen!