Use Bash and curl to make a bounded request, check the response your application actually expects, record evidence, and return a nonzero exit code when the check fails. Run that script from cron or a system timer, and optionally send a success ping to a heartbeat service only after every check passes. This gives you a useful signal from one machine; it does not prove that the site is available from every network or region.
What this monitor can and cannot tell you
A small Bash monitor answers a narrowly defined question such as “Did this URL return an acceptable response, within the time limit, with the expected page marker?” It can detect DNS errors, connection failures, TLS problems, timeouts, unexpected HTTP status codes, and pages that load but no longer contain a stable application marker.
It does not establish global availability. A request from a single host can succeed while users elsewhere experience a routing, DNS, firewall, CDN, or regional problem. If you need multiple vantage points, long-term history, external alert delivery, or protocol-specific probes, compare hosted monitoring services by location coverage, check frequency, HTTP and content checks, TLS reporting, alert destinations, retention, setup effort, and cost.
Define “healthy” before writing the script
Choose acceptance rules that match the site rather than assuming that any network response is good.
#1 Best Overall
- Used Book in Good Condition
- Target: use an HTTPS URL without credentials or secrets in the query string.
- Status: a common policy is a final 2xx response. Some applications intentionally return 3xx, so decide whether redirects should be followed and whether the final destination is allowed.
- Content: check a stable, site-specific marker such as a short heading, product name, or health-text token. Avoid timestamps, rotating offers, personalized text, and other frequently changing content.
- Timing: set both a connection timeout and a total transfer timeout so a broken endpoint cannot occupy the scheduler indefinitely.
- Retries: a small, intentional retry allowance can reduce alerts for transient network failures, but retries also delay detection. Keep the retry count and delay consistent with your schedule.
curl normally does not treat an HTTP 4xx or 5xx response as a command failure. The current curl manual identifies itself as version 8.23.0 and documents --fail, but explicitly checking %{response_code} lets you define an accepted range and still log the returned status. See the curl man page and curl HTTP scripting guide.
A complete Bash monitor
Save this as site-check.sh, then make it executable with chmod 750 site-check.sh. The first argument is the URL. Set EXPECTED_MARKER when a status check alone is not enough.
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
printf 'Usage: %s URL [log_file]n' "$0" >&2
exit 64
}
url="${1:-}"
log_file="${2:-./site-check.log}"
[[ -n "$url" ]] || usage
# Change this to a stable string that proves the expected application loaded.
expected_marker="${EXPECTED_MARKER:-}"
for command in curl date mktemp; do
command -v "$command" >/dev/null 2>&1 || {
printf 'Required command not found: %sn' "$command" >&2
exit 69
}
done
body_file="$(mktemp)"
error_file="$(mktemp)"
trap 'rm -f "$body_file" "$error_file"' EXIT
started_at="$(date -u +%FT%TZ)"
result=""
set +e
result="$(curl --silent --show-error --location \
--connect-timeout 5 --max-time 15 \
--retry 1 --retry-delay 1 \
--output "$body_file" \
--write-out '%{response_code} %{time_total}' \
"$url" 2>"$error_file")"
curl_rc=$?
set -e
status="000"
elapsed="unknown"
if [[ "$result" =~ ^([0-9]{3})[[:space:]]+([0-9.]+)$ ]]; then
status="${BASH_REMATCH[1]}"
elapsed="${BASH_REMATCH[2]}"
fi
fail() {
local reason="$1"
local detail
detail="$(tr 'n' ' ' < "$error_file" | tr -s ' ' | cut -c1-300)"
printf '%s FAIL url=%s status=%s seconds=%s reason=%s detail=%sn' \
"$started_at" "$url" "$status" "$elapsed" "$reason" "$detail" | tee -a "$log_file" >&2
exit 1
}
if (( curl_rc != 0 )); then
fail "curl_exit_$curl_rc"
fi
# This policy accepts only a final 2xx response.
if [[ ! "$status" =~ ^2[0-9][0-9]$ ]]; then
fail "unexpected_http_status"
fi
if [[ -n "$expected_marker" ]] && ! grep -Fq -- "$expected_marker" "$body_file"; then
fail "missing_expected_marker"
fi
printf '%s OK url=%s status=%s seconds=%sn' \
"$started_at" "$url" "$status" "$elapsed" | tee -a "$log_file"
The script writes the response body to a temporary file instead of mixing it with diagnostic output. It records a UTC timestamp, target, status, elapsed time, and a bounded error detail. Temporary files are removed on exit. A curl transport failure, a non-2xx final response, or a missing marker exits with status 1; missing configuration or tools exits with a distinct nonzero status.
Adapt the checks to your application
Redirects
--location follows redirects, and the script evaluates the final response. Keep it only if that is the behavior users should receive. If an HTTP-to-HTTPS redirect or a canonical-host redirect is itself a failure for your test, remove --location and inspect the first response instead. curl also supports redirect limits; add one appropriate to your environment if redirect loops are possible.
Status policies
Replace the 2xx expression with an explicit rule when your endpoint has a different contract. For example, a health endpoint might allow exactly 200, while an intentionally asynchronous endpoint might return 202. Do not add --fail and then assume curl has chosen your policy: --fail changes curl’s exit behavior for HTTP errors, but your monitor still needs to decide which statuses are acceptable.
Content markers
Set the marker without exposing secrets:
EXPECTED_MARKER='Example Product' ./site-check.sh https://example.com /var/log/site-check.log
Use a marker that is present on every legitimate response but unlikely to appear in an error page. If the site is localized or personalized, prefer a dedicated, stable health endpoint over scraping the home page.
Headers, cookies, and authentication
Some checks require a custom header, cookie, or authorization token. Add those options carefully and keep tokens out of the command line where other users can see the process list. Do not print secret-bearing URLs or headers to the log. A restricted environment file or a secret manager is safer than embedding credentials in the script.
Run it manually and read the result
- Test a known-good URL:
./site-check.sh https://example.com. Expect anOKline and exit status 0. - Check the exit status immediately with
echo "$?". - Test a deliberately invalid host or path. Expect a
FAILline, diagnostic reason, and a nonzero status. - Inspect the log after several runs. A useful line contains the UTC time, URL or target label, HTTP status, elapsed seconds, and failure reason.
Keep log permissions restrictive if the URL or diagnostics reveal internal names. Rotate the log with your operating system’s normal log-rotation facility; the script itself does not implement retention.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Schedule the check with cron
Edit the account’s crontab with crontab -e. This example runs every five minutes and sends standard output and errors to a separate scheduler log:
*/5 * * * * /opt/monitor/site-check.sh https://example.com /var/log/site-check.log >>/var/log/site-check-cron.log 2>&1
Use absolute paths because cron has a minimal environment. Ensure the cron user can execute the script and write its log. Choose an interval longer than the worst-case request duration plus any retry delay; otherwise runs can overlap. If overlap would be harmful, add a lock using your platform’s standard locking tool or move the job to a system timer that offers concurrency controls.
A systemd service and timer can provide clearer status and journal integration on Linux, but the same rules apply: bounded requests, explicit status checks, and a schedule that leaves enough time for completion.
Detect a missed cron run with a heartbeat
A local log cannot alert you when the machine, cron daemon, or network is down. A heartbeat service solves a different problem: it expects a success ping and alerts when that ping is late or missing. Healthchecks.io documents shell integration at its Bash guide and cron setup at its cron guide.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsSend the ping only after the website check succeeds. Keep the check’s failure status intact and give the heartbeat a grace period greater than the normal run duration. A simple wrapper is:
#!/usr/bin/env bash
set -Eeuo pipefail
HEARTBEAT_URL="${HEARTBEAT_URL:?Set HEARTBEAT_URL}"
/opt/monitor/site-check.sh https://example.com /var/log/site-check.log
curl --silent --show-error --max-time 10 --retry 2 "$HEARTBEAT_URL" >/dev/null
Schedule the wrapper rather than the underlying script. If you combine commands in a pipeline, use set -o pipefail; Bash otherwise reports the status of only the last command, allowing an earlier failure to be hidden. Healthchecks.io’s reliability guidance also cautions that “Sending monitoring signals over the public internet is inherently unreliable.” A heartbeat therefore complements, rather than replaces, direct checks from other locations. See Pinging Reliability Tips.
Or skip the browser setup
If you need a visual artifact for debugging a page, ScreenshotNeo provides a website screenshot API and MCP server. It is not a substitute for the Bash health decision above, but it can capture the page your monitor is investigating without you maintaining a browser installation. Before capture, it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; each response identifies the result with X-Page-Verdict and X-Billed headers. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
The API supports full-page captures with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDFs with paper size/margins/landscape/page ranges, HTML/CSS rendering, custom JavaScript and CSS, pre-capture clicks, hidden selectors, selector or network-idle waits, request/resource blocking, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API, and an OpenAPI specification. Every feature is on every plan. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots.
See the ScreenshotNeo documentation for request options. A one-call capture looks like this:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent clients are useful when your monitor is written in another language:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Sign up for 1,000 free screenshots a month with no card. Paid plans start at $5 for 3,000 shots, and yearly billing gives two months free.
Rank #4
Troubleshooting common failures
The script reports curl exit 6 or 7
These codes commonly indicate hostname resolution or connection problems. Verify DNS from the monitoring host, outbound firewall rules, proxy settings, and the URL spelling. Test the same URL manually with verbose curl output, but do not place verbose output in a shared log if it could expose credentials.
The status is 000 or the request times out
No HTTP response was obtained. Check the connect and total timeout values, TLS trust, routing, and server latency. Increase limits only when the site’s normal response time justifies it; otherwise a long timeout delays the alert.
The page returns 200 but the marker fails
The server responded, but the expected application content was absent. Confirm that the marker is stable, case-sensitive as intended, and present in the response body received by curl. A JavaScript-rendered page may not contain the text in its initial HTML; use a dedicated server-side health endpoint or a browser-capable monitor for that case.
Redirects or authentication produce unexpected results
Inspect whether the final URL is the intended host and whether cookies, headers, or authorization are required. Decide explicitly whether redirects are success and keep secrets out of arguments and logs.
Cron runs but no alert arrives
Check the cron user’s environment, absolute paths, file permissions, and scheduler logs. If a heartbeat is used, verify that the ping occurs after a successful check, that its grace period exceeds normal runtime, and that the heartbeat URL is reachable. A local log alone cannot notify you when the host itself is unavailable.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWhen Bash is enough—and when to move on
Bash plus cron is a reasonable fit for a small project that needs one monitoring location, a simple HTTP/content policy, and local evidence. Move to hosted or managed monitoring when you need independent network locations, retained history, integrated alert channels, richer TLS or protocol checks, or protection from the monitored machine failing. Keep the same explicit health contract whichever system you choose.
Best Value
FAQ
Does a 200 response prove the website is healthy?
No. It proves only that this request received a 200 response. A content marker or dedicated health endpoint can detect an application-level failure that still serves an HTTP success page.
Should I retry every failed request?
Only when the retry policy matches your alerting goals. A small retry allowance can smooth transient network errors, but it increases detection time and can hide intermittent failures.
Can a heartbeat replace multi-region monitoring?
No. It reports whether the scheduled job sent its completion signal. It does not test the site from several independent networks.
Free tools Windows power users keep installed
One-click scans. No signup required.
What should I log for an incident?
Record the UTC timestamp, target, final status, elapsed time, and a concise failure reason. Preserve detailed diagnostics separately and avoid logging credentials or sensitive query strings.
Frequently Asked Questions
Does a 200 response prove the website is healthy?
No. It proves only that this request received a 200 response. A content marker or dedicated health endpoint can detect an application-level failure that still serves an HTTP success page.
Should I retry every failed request?
Only when the retry policy matches your alerting goals. A small retry allowance can smooth transient network errors, but it increases detection time and can hide intermittent failures.
Can a heartbeat replace multi-region monitoring?
No. It reports whether the scheduled job sent its completion signal. It does not test the site from several independent networks.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
What should I log for an incident?
Record the UTC timestamp, target, final status, elapsed time, and a concise failure reason. Preserve detailed diagnostics separately and avoid logging credentials or sensitive query strings.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

