DEV Community

Enterno
Enterno

Posted on

Website Uptime Monitoring: From a 20-Line Bash Script to Production-Ready Alerts

Every team eventually asks the same question at the worst possible time: "Wait — how long has the site been down?" Usually the answer is "since a customer emailed us," which is not a monitoring strategy.

You don't need a heavyweight observability stack to answer that question. You need something that checks your endpoints on a schedule and shouts when they break. Let's build that from scratch, make it robust enough to trust, and then be honest about where a hand-rolled script stops being enough.

The 20-line version

At its core, uptime monitoring is one question asked on a loop: did the endpoint respond the way I expected? curl answers it and cron provides the loop.

#!/usr/bin/env bash
# uptime-check.sh — minimal uptime monitor
set -euo pipefail

URL="https://example.com"
TIMEOUT=10

code=$(curl --silent --show-error --location \
            --max-time "$TIMEOUT" \
            --output /dev/null \
            --write-out '%{http_code}' \
            "$URL" || echo "000")

if [[ "$code" =~ ^(2|3)[0-9][0-9]$ ]]; then
  echo "UP   $URL ($code)"
else
  echo "DOWN $URL ($code)" >&2
  # trigger an alert here
fi
Enter fullscreen mode Exit fullscreen mode

Wire it to cron and you have a monitor:

* * * * * /opt/monitoring/uptime-check.sh >> /var/log/uptime.log 2>&1
Enter fullscreen mode Exit fullscreen mode

That's a genuine, working uptime check. But "working in the happy path" and "trustworthy at 3 a.m." are two very different bars.

Making it resistant to false positives

The fastest way to kill a monitoring system is alert fatigue. If it cries wolf on every transient blip, people mute it — and then it's worse than having nothing, because you think you're covered.

Three fixes matter most:

1. Retry before you alert. A single failed request is noise; three failures in a row is a signal. Never page a human on one bad sample.

check() {
  curl --silent --location --max-time "$TIMEOUT" \
       --output /dev/null --write-out '%{http_code}' "$1" || echo "000"
}

fails=0
for _ in 1 2 3; do
  code=$(check "$URL")
  [[ "$code" =~ ^(2|3)[0-9][0-9]$ ]] && { fails=0; break; }
  ((fails++)); sleep 5
done

(( fails >= 3 )) && echo "DOWN $URL ($code)" >&2
Enter fullscreen mode Exit fullscreen mode

2. Validate the algorithm and the destination, not just "did it connect." A 200 OK that serves an error page is still down for your users. If you can, assert on a known string in the body or a health endpoint that touches the database, not just the front door.

3. Set explicit timeouts. Without --max-time, a hung connection hangs your whole check. A monitor that blocks is a monitor that lies.

Don't forget the certificate

Expired TLS certificates are self-inflicted outages, and they're completely predictable — which makes getting caught by one especially painful. Add a days-until-expiry check while you're here:

host="example.com"
expiry=$(echo | openssl s_client -servername "$host" -connect "$host:443" 2>/dev/null \
         | openssl x509 -noout -enddate | cut -d= -f2)

days_left=$(( ( $(date -d "$expiry" +%s) - $(date +%s) ) / 86400 ))

(( days_left < 14 )) && echo "SSL $host expires in $days_left days" >&2
Enter fullscreen mode Exit fullscreen mode

Fourteen days is a sane warning window — enough lead time to renew without a fire drill.

Where the DIY script quietly breaks down

The script above is honestly fine for a personal project or an internal tool. The problems show up as you scale, and most of them are structural — no amount of extra bash fixes them:

  • One vantage point. Your cron job runs from one machine. If that box (or its network path) has the problem, you'll get false alarms; if the outage is regional, you'll miss real ones. Real availability needs checks from multiple locations.
  • Who watches the watcher? If the server running your monitor goes down, your monitoring goes down with it — silently. The thing most likely to fail is the thing you're least likely to be alerted about.
  • No history. "Is it up right now" is easy. "What was our uptime last month," "when did response time start creeping up," "show the customer an SLA report" — none of that lives in a log file you'll actually parse.
  • Alert routing and dedup. Real incidents need escalation, on-call schedules, and grouping so one outage doesn't fire 200 identical messages. That's a system, not a >> curl to a webhook.
  • Status communication. When you are down, users want a status page, not silence. Building and hosting one is its own project.

When to stop scripting and use a hosted monitor

The honest rule of thumb: keep the script while monitoring is a convenience; move to a dedicated monitor once uptime is a promise you make to someone else. The moment there's a customer, an SLA, or an on-call rotation, the properties above stop being nice-to-haves.

A hosted uptime monitor exists precisely to own the parts that are tedious to build well: checks from independent regions, retry/dedup logic, incident history, alert routing across email/Telegram/Slack/webhooks, SSL-expiry tracking, and public status pages. If you'd rather not run and babysit that infrastructure yourself, enterno.io is a straightforward option that covers HTTP/SSL/ping/DNS checks, multi-region monitoring, and status pages out of the box — the same checklist we just built by hand, minus the maintenance.

Whichever way you go, the underlying discipline is identical, so it's worth internalizing:

TL;DR checklist

  • ✅ Check on a schedule (cron or hosted).
  • ✅ Retry N times before alerting — kill false positives.
  • ✅ Assert on real behavior (health endpoint / body content), not just a 200.
  • ✅ Always set explicit timeouts.
  • ✅ Monitor TLS-certificate expiry (warn ~14 days out).
  • ✅ Check from more than one location once real users depend on you.
  • ✅ Make sure something other than the failing server is doing the watching.
  • ✅ Keep history and a status page once uptime becomes a commitment.

Start with the 20 lines. Graduate when the stakes — not the ego — say it's time.

What does your team use for uptime monitoring — a hand-rolled script, a hosted service, or a full observability stack? Curious where people draw the DIY line.

Top comments (0)