17

I have a script which creates a virtual machine and gives me back an IP address. Then I would like to do something like this:

waitforssh 192.168.2.38 && ssh 192.168.2.38

And it will wait for the machine to be up and ssh to be responding, then ssh into it.

waitforssh is the command I need to find.

Would nmap, netcat, fping or ping do the job? I tried netcat but it gives up in just a couple of seconds if the host is unreachable.

It needs to handle the fact that the machine itself is booting and might take some time to respond to network packets.

7 Answers 7

23

I don't have a host that I can ssh to and control whether it's up or not, but this should work:

while ! ssh <ip>
do
    echo "Trying again..."
done

Or a shell script that is more explicit:

#!/bin/sh

ssh $1
while test $? -gt 0
do
   sleep 5 # highly recommended - if it's in your local network, it can try an awful lot pretty quick...
   echo "Trying again..."
   ssh $1
done

Save it as (say) waitforssh.sh and then call it with sh waitforssh.sh 192.168.2.38

2
  • This works pretty good! The sleep 5 won't be necessary as this is all local machines. Thanks!
    – Weboide
    Jun 19, 2010 at 19:43
  • 1
    Beware IDS/IPS (Intrusion Detection/Prevention Systems) that may see this as a SSH scan or attack.
    – Jason
    Jun 20, 2010 at 1:55
5

This is a 'ping_ssh' script I'm using. It handles timeout cases, fast success, and won't prompt for passwords or be fooled by the port being open but not responding as with 'nc' based solutions. This combines several answers found on various stackoverflow related sites.

#!/usr/bin/bash
HOST=$1
PORT=$2
#HOST="localhost"
#PORT=8022
if [ -z "$1" ]
  then
    echo "Missing argument for host."
    exit 1 
fi

if [ -z "$2" ]
  then
    echo "Missing argument for port."
    exit 1 
fi
echo "polling to see that host is up and ready"
RESULT=1 # 0 upon success
TIMEOUT=30 # number of iterations (5 minutes?)
while :; do 
    echo "waiting for server ping ..."
    # https://serverfault.com/questions/152795/linux-command-to-wait-for-a-ssh-server-to-be-up
    # https://unix.stackexchange.com/questions/6809/how-can-i-check-that-a-remote-computer-is-online-for-ssh-script-access
    # https://stackoverflow.com/questions/1405324/how-to-create-a-bash-script-to-check-the-ssh-connection
    status=$(ssh -o BatchMode=yes -o ConnectTimeout=5 ${HOST} -p ${PORT} echo ok 2>&1)
    RESULT=$?
    if [ $RESULT -eq 0 ]; then
        # this is not really expected unless a key lets you log in
        echo "connected ok"
        break
    fi
    if [ $RESULT -eq 255 ]; then
        # connection refused also gets you here
        if [[ $status == *"Permission denied"* ]] ; then
            # permission denied indicates the ssh link is okay
            echo "server response found"
            break
        fi
    fi
    TIMEOUT=$((TIMEOUT-1))
    if [ $TIMEOUT -eq 0 ]; then
        echo "timed out"
        # error for jenkins to see
        exit 1 
    fi
    sleep 10
done
3
  • I added my PEM file using -i and my user name to the SSH command and this script works. Apr 7, 2020 at 21:19
  • 2
    I found this helpful. Added -o StrictHostKeyChecking=no to the ssh call for status
    – Jazzepi
    Jul 27, 2020 at 18:27
  • 1
    If you add StrictHostKeyChecking=no it solves the problem of failure when the host key has changed, but will automagically add the new host key. An alternate is to test for "verification failed" as well as "Permission denied", either means the SSH host is up, which is the point.
    – stux
    Mar 19 at 2:29
4

Something simple like this does the job, waiting 5 seconds between attempts and discarding STDERR

until ssh <host> 2> /dev/null
  do
    sleep 5
  done
2

The ssh command can be given a command to perform on the remote machine as the last paramater. So call ssh $MACHINE echo in a loop. On success it returns 0 in $?, on failure 255. You must of course use paswordless authentication with certificates.

2

Well, I'm not sure what you mean by to be up, but what about:

$ ping host.com | grep --line-buffered "bytes from" | head -1 && ssh host.com

First command ping | ... | head -1 waits for server to sent single ping reply and exists. Then ssh comes into play. Be aware that grep can buffer output, so this is what --line-buffered is for.

You can wrap this command with bash function to use it exactly the way you've described.

$ waitfor() { ping $1 | grep --line-buffered "bytes from" | head -1 }
$ waitfor server.com && ssh server.com
1
  • 2
    This will wait for the server but not the port May 21, 2017 at 0:24
2
function hurryup () { 
    until ssh -o ConnectTimeout=2 "$1"@"$2"
        do sleep 1
    done
}
hurryup root "10.10.0.3"

-o ConnectTimeout=2 is a slightly hacky way of getting around not responding to network packets, reporting ssh: connect to host 10.10.0.3 port 22: Operation timed out until it's responsive.

Then, when the host responding to network packets, the 1 second sleep with happen in-between ssh: connect to host 10.10.0.3 port 22: Connection refused as we wait for ssh to come up.

2

This uses socat to connect and wait for the server to say something, which will be the SSH version info:

while [ -z "$( socat -T2 stdout tcp:127.0.0.1:22,connect-timeout=2,readbytes=1 2>/dev/null )" ]
do
    echo "."
    sleep 1
done

Adjust host, timeouts, and sleep interval as desired.

You might not need -T2, which sets a timeout for inactivity after the TCP connection is already established, but in my case I was connecting to a QEMU virtual machine, and QEMU opens forwarded ports before the virtual OS is genuinely listening. It's harmless, in any case.

1
  • IMO the best solution if you do not need to go through jump hosts
    – mishmashru
    Mar 10 at 15:37

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .