Skip to content

Healthcheck V1 — Core Logic


This second project moves faster than the backup script did — the foundational techniques (quoting, logging, safety patterns, getopts, trap) are already established, so these two chapters apply them to a new domain rather than re-teaching them. This chapter builds v1 of a service health-check script: test whether an HTTP endpoint is healthy, and report the result. Deliberately naive, the same way the backup project started — the next chapter hardens it with timeouts, retries, rate-limiting, and structured logging.

What This Script Needs To Do

Check whether a service, reachable over HTTP, is responding successfully — and report clearly whether it is or isn’t.

Building v1

#!/usr/bin/env bash

url="https://example.com/health"

response=$(curl -s -o /dev/null -w "%{http_code}" "$url")

if [[ "$response" == "200" ]]; then
    echo "OK: $url is healthy"
else
    echo "ALERT: $url returned status $response"
fi

Walking Through The Script

curl -s -o /dev/null -w "%{http_code}" "$url" is a common idiom worth recognizing: -s silences curl’s own progress output, -o /dev/null discards the response body (this check only cares about the status code, not the content), and -w "%{http_code}" writes just the numeric HTTP status code to stdout — which is exactly what response=$(...) captures via command substitution. Everything else is straightforward: quoted variables, a simple [[ ]] comparison, two clear outcomes.

What v1 Gets Right

The fundamentals are already solid, carried over directly from everything this course has covered: quoted variable references, [[ ]] for the comparison, clean two-branch logic. For a service that’s either straightforwardly healthy or straightforwardly not, this genuinely works.

What v1 Deliberately Leaves Out

The roadmap for the next chapter:

  • No timeout. If the service is unreachable in a way that doesn’t fail fast — a network path that silently drops packets rather than actively refusing the connection — curl can hang indefinitely, and so does this entire script. This is the single most important gap, demonstrated concretely in this chapter’s Shell-Safety Considerations section.
  • No retries. A single transient network blip triggers a false alert, even if the service is actually fine a moment later.
  • No rate-limiting on alerts. Run frequently (say, once a minute via cron) during a real outage, this version would print a fresh alert on every single run with no throttling — alert fatigue for whoever’s on the receiving end.
  • No distinction between “curl couldn’t connect at all” and “the service responded, but unhealthily.” A DNS failure or a refused connection leaves response empty (or 000), which this version reports identically to any other non-200 status, even though the underlying problem is quite different.
  • No structured logging — just a bare echo, none of the leveled, timestamped logging the backup project built.

Best Practices

  • Quote every variable and use [[ ]], exactly as established throughout this course — nothing about a health check changes those fundamentals.
  • Capture exactly what you need from a tool’s output, and nothing more — -o /dev/null here discards data the script has no use for, keeping the captured value simple and exactly on-topic.

Shell-Safety Considerations

The missing-timeout problem is worth seeing directly, using a safe, bounded stand-in rather than actually letting a real command hang for however long a genuinely broken network path might take.

The danger, demonstrated safelysleep 10 stands in for an unresponsive service call, run with no limit on how long it’s allowed to take:

timeout 2 sleep 10
echo "Exit status: $?"
Exit status: 124

timeout is a standard Unix utility that runs a command and forcibly kills it if it exceeds the given duration — here, sleep 10 was cut off after 2 seconds instead of running for its full 10, and timeout reports exit status 124 specifically to signal “this was killed for taking too long,” distinct from any exit status the command itself might have produced. Without timeout wrapping it, that sleep 10 would have run for the entire 10 seconds — and in v1’s actual script, a genuinely unresponsive service in place of sleep 10 would hang for however long the operating system’s own connection timeout happens to be, potentially minutes, with the health-check script frozen the entire time, unable to report anything at all.

curl has its own, more specific version of this same protection — --max-time SECONDS — which is the actual fix this script needs:

curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url"

This isn’t folded into v1’s script on purpose — timeouts are explicitly the next chapter’s first order of business, alongside retries and the rest of the hardening this naive version is missing. This section exists to make the danger concrete before that fix arrives, the same way the backup project demonstrated a failure before building the chapter that actually addressed it.

Hands-On: Building And Testing v1

1. Set up a working directory.

mkdir -p ~/shell-course/healthcheck-project
cd ~/shell-course/healthcheck-project

2. Write v1 and test it against a real, healthy endpoint.

cat > healthcheck-v1.sh << 'EOF'
#!/usr/bin/env bash

url="$1"

response=$(curl -s -o /dev/null -w "%{http_code}" "$url")

if [[ "$response" == "200" ]]; then
    echo "OK: $url is healthy"
else
    echo "ALERT: $url returned status $response"
fi
EOF
chmod +x healthcheck-v1.sh

./healthcheck-v1.sh https://example.com

3. Test it against a nonexistent domain, and notice response is empty or 000 rather than a real HTTP status.

./healthcheck-v1.sh https://this-domain-genuinely-does-not-exist.invalid

4. Confirm timeout’s protective effect on a safely bounded stand-in for a hanging request.

echo "Running a 10-second operation with a 2-second limit..."
timeout 2 sleep 10
echo "Exit status: $?"

5. Clean up.

cd ~
rm -rf ~/shell-course/healthcheck-project

You’ve now built a genuinely working health check, seen it fail to distinguish a DNS failure from an unhealthy response, and confirmed — safely and with a bounded example — exactly why the missing timeout is v1’s most serious gap. The next chapter hardens this exact script with --max-time, retries, rate-limited alerting, and the structured logging this version still lacks.

Last updated on