Simple Web Server Availability Monitoring with cron, bash, and wget - jrowberg.io
Home Programming Simple Web Server Availability Monitoring with cron, bash, and wget

Simple Web Server Availability Monitoring with cron, bash, and wget

7 comments

I recently needed a really, really simple remote monitoring agent to keep track of server availability. Actually, I didn’t even need a record of uptime or anything; I merely needed to be alerted if any of a few web servers had gone down unexpectedly. So, I wrote one.

I know there are lots of other solutions for this problem, many of which are free. You can do this with Nagios, for example. But I don’t have an existing Nagios installation already setup, and I didn’t want to deploy monstrous beast of a platform of which I needed only 0.1% of the capabilities. There are also many web-based services that will do this for you, but most of these have a pretty large “check” interval, or else they cost money. I was feeling both cheap and picky, and I knew everything I needed to do could be accomplished using cron, bash, and wget. It works great, so I wanted to share what I came up with.

Here is a basic feature list:

  • Requires bash, wget, and working “mail” command (sendmail, exim, postfix, etc.)
  • Monitors any HTTP or HTTPS URL, checking for “200” status returned
  • Checks request fulfillment time for slow responses, not just UP/DOWN
  • Sends email notification on state change (UP, SLOW, or DOWN)
  • Customizable To/From notification settings
  • Customizable SLOW latency threshold
  • Tracks previous state change and last update to avoid multiple notifications
  • Uses single text file for data storage, no DB necessary

It’s pretty basic, but it works. Just save this script somewhere, modify the HOSTS and other settings to taste, and add a cron job definition to run it every five minutes or so. No Nagios or 3rd party solutions required. If this is what you’re looking for, then…awesome!

Sample cron job definition

*/5 * * * * root /home/username/sitemonitor.sh

sitemonitor.sh script source

#!/bin/bash

# Simple HTTP/S availability notifications script v0.1
# March 20, 2012 by Jeff Rowberg - http://www.sectorfej.net

HOSTS=( \
    "http://www.yahoo.com" \
    "https://www.google.com" \
    "http://www.amazon.com" \
    )

NOTIFY_FROM_EMAIL="Site Monitoring <monitoring@example.com>"
NOTIFY_TO_EMAIL="Server Admin <admin@example.com>"

STATUS_FILE="/home/username/sitemonitor.status"

SLOW_THRESHOLD=10
OK_STATUSES=( "200" )

################################################################
#        NO MORE USERMOD STUFF BELOW THIS, MOST LIKELY         #
################################################################

# thanks to stackoverflow!
# stackoverflow.com/questions/3685970/bash-check-if-an-array-contains-a-value
function contains() {
    local n=$#
    local value=${!n}
    for ((i=1; i < $#; i++)) {
        if [ "${!i}" == "${value}" ]; then
            echo "y"
            return 0
        fi
    }
    echo "n"
    return 1
}

rm -f /tmp/sitemonitor.status.tmp
for HOST in "${HOSTS[@]}"
do
    START=$(date +%s)
    RESPONSE=`wget $HOST --no-check-certificate -S -q -O - 2>&1 | \
                  awk '/^  HTTP/{print \$2}'`
    END=$(date +%s)
    DIFF=$(( $END - $START ))
    if [ -z "$RESPONSE" ]; then
        RESPONSE="0"
    fi
    if [ $(contains "${OK_STATUSES[@]}" "$RESPONSE") == "y" ]; then
        if [ "$DIFF" -lt "$SLOW_THRESHOLD" ]; then
            STATUS="UP"
        else
            STATUS="SLOW"
        fi
    else
        STATUS="DOWN"
    fi
    touch $STATUS_FILE
    STATUS_LINE=`grep $HOST $STATUS_FILE`
    STATUS_PARTS=($(echo $STATUS_LINE | tr " " "\n"))
    CHANGED=${STATUS_PARTS[2]}
    if [ "$STATUS" != "${STATUS_PARTS[5]}" ]; then
        #if [ -e "${STATUS_PARTS[5]}" ] || [ "$STATUS" != "UP" ]; then
            if [ -z "${STATUS_PARTS[5]}" ]; then
                STATUS_PARTS[5]="No record"
            fi
            TIME=`date -d @$END`
            echo "Time: $TIME" > /tmp/sitemonitor.email.tmp
            echo "Host: $HOST" >> /tmp/sitemonitor.email.tmp
            echo "Status: $STATUS" >> /tmp/sitemonitor.email.tmp
            echo "Latency: $DIFF sec" >> /tmp/sitemonitor.email.tmp
            echo "Previous status: ${STATUS_PARTS[5]}" >> /tmp/sitemonitor.email.tmp
            if [ -z "${STATUS_PARTS[2]}" ]; then
                TIME="No record"
            else
                TIME=`date -d @${STATUS_PARTS[2]}`
            fi
            echo "Previous change: $TIME" >> /tmp/sitemonitor.email.tmp
            `mail -a "From: $NOTIFY_FROM_EMAIL" \
                  -s "SiteMonitor Notification: $HOST is $STATUS" \
                  "$NOTIFY_TO_EMAIL" < /tmp/sitemonitor.email.tmp`
            rm -f /tmp/sitemonitor.email.tmp
        #else
             # first report, but host is up, so no need to notify
        #fi
        CHANGED="$END"
    fi
    echo $HOST $RESPONSE $CHANGED $END $DIFF $STATUS >> /tmp/sitemonitor.status.tmp
done

mv /tmp/sitemonitor.status.tmp $STATUS_FILE

Sample notification email

Time: Fri Mar 23 17:20:02 UTC 2012
Host: http://www.example.com
Status: UP
Latency: 0 sec
Previous status: DOWN
Previous change: Fri Mar 23 15:05:20 UTC 2012

Pretty simple stuff, but it totally gets the job done, and you can have it check on any interval and as many servers as you want! That’s hard to beat at this level of simplicity and freedom.

You may also like

7 comments

Ali November 23, 2012 - 5:39 am

i am going to try this its look great.I will update after applying this.

Reply
Ali November 23, 2012 - 8:08 am

Its not look like your output……….unable to understand this.

[root@ads6 scripts]# cat /home/ali/sitemonitor.status
http://www.yahoo.com 0 1353670449 1353675899 0 DOWN
https://www.google.com 0 1353670449 1353675899 0 DOWN
http://www.amazon.com 0 1353670450 1353675900 1 DOWN

Reply
Tom Schaefer June 28, 2013 - 10:34 pm

Thanks for writing this. In case anyone else is trying to do this from a Mac, the date command is slightly different when seeding it with an epoch.

I had to change the script to this: TIME=`date -r “$END” “+%Y-%m-%d %H:%M:%S”` and this further down: TIME=`date -r ${STATUS_PARTS[2]} “+%Y-%m-%d %H:%M:%S”`

Also, the Mac version of mail does not like the -a option so remove that.

Maybe it will save someone else searching for the differences.

Thanks again for sharing the script.

Regards,

Tom Schaefer

Reply
Vladimir July 5, 2013 - 5:37 am

String with “mail” command has been changed and it work. My OS is Ubuntu 13.04
`mail -r “$NOTIFY_FROM_EMAIL”
-s “SiteMonitor Notification: $HOST is $STATUS”
“$NOTIFY_TO_EMAIL” < /tmp/sitemonitor.email.tmp`

Reply
Clint July 22, 2014 - 8:58 pm

Thanks, this saved me some time! I have made various improvements, including a much more precise latency measurement (from seconds to milliseconds), adapting to redirects, and added a bit more detail to the log.

Regarding redirects, the script as was would only use the first HTTP response found. Of course if you get a 301 then you want the final result instead. A little awk snippet simply grabs the last line of the HTTP results.


#!/bin/bash

HOSTS=(
"http://www.yahoo.com"
"http://google.com"
"http://www.amazon.com"
)

NOTIFY_FROM_EMAIL="Site Monitoring "
NOTIFY_TO_EMAIL="Server Admin "

STATUS_FILE="/home/user/sitemonitor.status"

SLOW_THRESHOLD=600 #milliseconds
OK_STATUSES=( "200" )

function contains() {
local n=$#
local value=${!n}
for ((i=1; i &1 |
awk '/^ HTTP/{print $2}' | awk 'END{print}'`
LATENCY=`ping -c 1 pctlc.com | awk '/^rtt /{print $4}' | awk -F/ '{print int($2)}'`
if [ -z "$RESPONSE" ]; then
RESPONSE="0"
fi
if [ $(contains "${OK_STATUSES[@]}" "$RESPONSE") == "y" ]; then
if [ "$LATENCY" -lt "$SLOW_THRESHOLD" ]; then
STATUS="UP"
else
STATUS="SLOW"
fi
else
STATUS="DOWN"
fi
touch $STATUS_FILE
STATUS_LINE=`grep $HOST $STATUS_FILE`
STATUS_PARTS=($(echo $STATUS_LINE | tr " " "n"))
CHANGED=${STATUS_PARTS[2]}
if [ "$STATUS" != "${STATUS_PARTS[5]}" ]; then
if [ -z "${STATUS_PARTS[5]}" ]; then
STATUS_PARTS[5]="No record"
fi
echo "Time: $TIME" > /tmp/sitemonitor.email.tmp
echo "Host: $HOST" >> /tmp/sitemonitor.email.tmp
echo "Status: $STATUS" >> /tmp/sitemonitor.email.tmp
echo "Response: $RESPONSE" >> /tmp/sitemonitor.email.tmp
echo "Latency: $LATENCY ms" >> /tmp/sitemonitor.email.tmp
echo "Previous status: ${STATUS_PARTS[5]}" >> /tmp/sitemonitor.email.tmp
if [ -z "${STATUS_PARTS[2]}" ]; then
CHANGE="No record"
else
CHANGE=`date -d @${STATUS_PARTS[2]}`
fi
echo "Previous change: $CHANGE" >> /tmp/sitemonitor.email.tmp
`mail -a "From: $NOTIFY_FROM_EMAIL"
-s "SiteMonitor Notification: $HOST is $STATUS"
"$NOTIFY_TO_EMAIL" > /tmp/sitemonitor.status.tmp
done

mv /tmp/sitemonitor.status.tmp $STATUS_FILE

I would like to adapt the “last changed” feature to better suit my purposes, or at least fix it from showing Wed Dec 31 18:00:22 CST 1969 like it does now at first run (though that may be from a change I made, don’t really remember at this point).

Reply
Mike May 15, 2015 - 8:52 pm

I am getting same error like Ali. When I use just wget and website from command, it works. I was wondering what I need to modify from my linux.

Any information will be appreciated.

Thanks,
Mike

Reply
Roman August 21, 2015 - 9:23 am

If the RESPONSE get two Elements like this “302 200” than modified
this part
if [ $(contains “${OK_STATUSES[@]}” “$RESPONSE”) == “y” ]; then
to this
RESPONSE=($(echo $RESPONSE | tr ” ” “n”))
if [ $(contains “${OK_STATUSES[@]}” “${RESPONSE[@]}”) == “y” ]; then

Reply

Leave a Comment