October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
Bash

How to Build a Website Monitoring Script in Bash (with Cron and Missed-Job Alerts)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Yes—you can monitor a website with a small Bash script. The reliable pattern is to make a bounded curl request, define what “healthy” means for that site, record evidence, return a nonzero exit code on failure, and run the check from a scheduler. Add a heartbeat ping only after a successful run when you also need to know that cron itself stopped running.

This is a single monitoring vantage point, not proof that a site works for every visitor. Use hosted or multi-location monitoring when geography, alert delivery, and historical availability matter.

What the script should prove

A TCP connection or a 200 response is only part of an application check. Decide these questions before writing code:

  • Which URL? Use a health endpoint or a stable page rather than a URL containing credentials or frequently changing query data.
  • Which status codes? A common policy is any final 2xx response. Some services intentionally return 3xx, 401, or 404 for a valid workflow, so set the rule for your application.
  • Should redirects count? Following redirects can model a browser visit, but you must decide whether the final host and path are acceptable.
  • What content proves success? A stable marker such as a page title, release identifier, or JSON field can catch an “HTTP 200” error page or an unhealthy backend.
  • How long may it run? Set both a connection timeout and an overall transfer timeout. Retries can soften transient network failures, but they also delay detection.

curl normally does not treat HTTP 4xx or 5xx responses as command failures. The current curl manual identifies itself as version 8.23.0 and documents --fail, timeout, redirect, and write-out options: curl man page. Its scripting guide separates status, headers, and body so you can inspect each deliberately: The Art Of Scripting HTTP Requests Using curl.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
CanaKit Raspberry Pi 4 4GB Starter PRO Kit - 4GB RAM
  • Includes Raspberry Pi 4 4GB Model B with 1.5GHz 64-bit quad-core CPU (4GB RAM)
  • Includes Pre-Loaded 32GB EVO+ Micro SD Card (Class 10), USB MicroSD Card Reader
  • CanaKit Premium High-Gloss Raspberry Pi 4 Case with Integrated Fan Mount, CanaKit Low Noise Bearing System Fan
  • CanaKit 3.5A USB-C Raspberry Pi 4 Power Supply (US Plug) with Noise Filter, Set of Heat Sinks, Display Cable - 6 foot (Supports up to 4K60p)
  • CanaKit USB-C PiSwitch (On/Off Power Switch for Raspberry Pi 4)

A complete Bash checker

Save this as website-check.sh and make it executable with chmod 700 website-check.sh. It follows redirects, records the final status and elapsed time, checks an optional content marker, and exits nonzero for transport, status, or content failures.

#!/usr/bin/env bash
set -o pipefail

set -u

URL="${1:-}"
EXPECTED_MARKER="${EXPECTED_MARKER:-}"
LOG_FILE="${LOG_FILE:-$HOME/var/log/website-check.log}"
CONNECT_TIMEOUT="${CONNECT_TIMEOUT:-5}"
MAX_TIME="${MAX_TIME:-15}"
MAX_REDIRECTS="${MAX_REDIRECTS:-5}"

if [[ -z "$URL" ]]; then
  printf 'Usage: %s URLn' "$0" >&2
  exit 64
fi
if ! command -v curl >/dev/null 2>&1; then
  printf '%s FAIL reason=curl_missing url=%sn' "$(date -u +%FT%TZ)" "$URL" >&2
  exit 69
fi
if ! command -v mktemp >/dev/null 2>&1; then
  printf '%s FAIL reason=mktemp_missing url=%sn' "$(date -u +%FT%TZ)" "$URL" >&2
  exit 69
fi

mkdir -p "$(dirname -- "$LOG_FILE")" || exit 73
body_file=$(mktemp) || {
  printf '%s FAIL reason=tempfile_failed url=%sn' "$(date -u +%FT%TZ)" "$URL" >&2
  exit 73
}
trap 'rm -f -- "$body_file"' EXIT

start_ns=$(date +%s%N)
status=$(curl --silent --show-error --location 
  --max-redirs "$MAX_REDIRECTS" 
  --connect-timeout "$CONNECT_TIMEOUT" --max-time "$MAX_TIME" 
  --output "$body_file" --write-out '%{response_code}' 
  "$URL" 2>"$body_file.curlerr")
curl_rc=$?
end_ns=$(date +%s%N)
elapsed_ms=$(( (end_ns - start_ns) / 1000000 ))

if (( curl_rc != 0 )); then
  reason=$(tr 'n' ' ' < "$body_file.curlerr" | cut -c1-240)
  line=$(printf '%s FAIL reason=transport_or_curl_error curl_rc=%s elapsed_ms=%s url=%s detail=%s' 
    "$(date -u +%FT%TZ)" "$curl_rc" "$elapsed_ms" "$URL" "$reason")
  printf '%sn' "$line" | tee -a "$LOG_FILE" >&2
  rm -f -- "$body_file.curlerr"
  exit 1
fi
rm -f -- "$body_file.curlerr"

if [[ ! "$status" =~ ^2[0-9][0-9]$ ]]; then
  line=$(printf '%s FAIL reason=unexpected_status status=%s elapsed_ms=%s url=%s' 
    "$(date -u +%FT%TZ)" "$status" "$elapsed_ms" "$URL")
  printf '%sn' "$line" | tee -a "$LOG_FILE" >&2
  exit 1
fi

if [[ -n "$EXPECTED_MARKER" ]] && ! grep -Fq -- "$EXPECTED_MARKER" "$body_file"; then
  line=$(printf '%s FAIL reason=content_marker_missing status=%s elapsed_ms=%s url=%s' 
    "$(date -u +%FT%TZ)" "$status" "$elapsed_ms" "$URL")
  printf '%sn' "$line" | tee -a "$LOG_FILE" >&2
  exit 1
fi

line=$(printf '%s OK status=%s elapsed_ms=%s url=%s' 
  "$(date -u +%FT%TZ)" "$status" "$elapsed_ms" "$URL")
printf '%sn' "$line" | tee -a "$LOG_FILE"
exit 0

The script treats a curl error as a transport or local execution failure, then separately rejects non-2xx responses and a missing marker. It captures the body in a temporary file, removes it on exit, and limits an error detail to 240 characters. Do not log authorization headers, cookies, access tokens, or secret-bearing URLs.

Choose the content check carefully

Set a marker only when it is stable:

EXPECTED_MARKER='<title>Status dashboard</title>' 
  ./website-check.sh https://example.com/health

For JSON, a simple fixed-string search may be adequate for a small check, but a parser such as jq is safer when whitespace or field ordering varies. Keep the marker specific enough to identify the expected application, not a generic word that could appear on an error page.

Useful curl policies and variations

Use --fail when every 4xx/5xx is failure

You can add --fail (or --fail-with-body where supported) and rely on curl’s exit status for HTTP errors. Explicitly capturing %{response_code}, as the example does, is more transparent when you need to accept a site-specific range or log the exact code. Neither choice replaces your application’s health policy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Redirects

--location follows redirects; --max-redirs 5 prevents an infinite chain. Remove --location if a redirect itself should fail, or add a separate check that the final URL is an approved host. A redirect to a login page can otherwise look healthy if it returns 200.

Rank #2
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (8GB RAM)
  • Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM)
  • Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
  • CanaKit Turbine Black Case for the Raspberry Pi 5
  • CanaKit Low Noise Bearing System Fan
  • Mega Heat Sink - Black Anodized

Retries

For a noisy network, use curl’s retry controls intentionally, for example --retry 2 --retry-delay 2 --retry-max-time 20. A retry policy changes alert timing and load. Do not retry unsafe requests that mutate state; keep monitoring probes read-only.

TLS and authentication

Keep certificate verification enabled. If the endpoint requires authentication, supply credentials through protected environment variables or a header file with restrictive permissions, never on a shared command line or in logs. Test certificate expiry separately if that is an operational requirement; a successful request proves only that this client completed the current TLS exchange.

Test the checker before scheduling it

  1. Run a known-good URL and confirm an OK line with status and elapsed time.
  2. Use a URL that returns 404 or 500 and confirm a nonzero exit code: ./website-check.sh https://example.com/missing; echo "$?".
  3. Set an impossible hostname or a very short timeout to exercise the transport branch.
  4. Set EXPECTED_MARKER to text that is absent and verify content_marker_missing.
  5. Inspect the log directory’s ownership and permissions. The scheduler’s user must be able to write it.

Keep the command’s exit status intact when you wrap it in another script. Bash pipelines normally report the status of the last command; set -o pipefail makes a pipeline fail when an earlier command fails. Healthchecks.io explains this caveat in its shell-monitoring guide: Monitor Shell Scripts with Healthchecks.io.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Run it with cron

Open the crontab for the account that owns the script:

crontab -e

This example runs every five minutes and sends standard output and errors to a separate scheduler log:

Rank #3
CanaKit Raspberry Pi 3 B+ (B Plus) Starter Kit (32 GB EVO+ Edition, Premium Black Case)
  • Includes Made in UK Raspberry Pi 3 B+ (B Plus) with 1.4 GHz 64-bit Quad-Core Processor, 1 GB RAM
  • Dual Band 2.4GHz and 5GHz IEEE 802.11.b/g/n/ac Wireless LAN, Enhanced Ethernet Performance
  • Includes 32 GB EVO+ Micro SD Card (Class 10) Pre-loaded with OS, USB MicroSD Card Reader
  • CanaKit 2.5A USB Power Supply with Micro USB Cable and Noise Filter - Specially designed for the Raspberry Pi 3 B+ (UL Listed)
  • Premium Raspberry Pi 3 B+ Case, Display Cable, 2 x Heat Sinks, GPIO Quick Reference Card, CanaKit Full Color Quick-Start Guide
*/5 * * * * /home/you/bin/website-check.sh https://example.com/health >> /home/you/var/log/website-check-cron.log 2>&1

Use absolute paths because cron has a minimal environment. If the script needs variables, define them in the script or source a protected configuration file. Ensure the interval is longer than the worst-case timeout and does not create unnecessary load. Rotate or prune logs; an unbounded log eventually fills a disk.

A systemd timer is another option when you need service dependencies, journal logging, or per-run resource controls. The same script and exit codes apply; only the scheduler changes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Detect a cron job that never ran

A local log cannot alert you when the machine is down. A heartbeat service can: create a check with an expected interval and grace period, then ping it only after every website assertion succeeds. Healthchecks.io’s cron documentation describes this pattern and recommends grace time above the expected job duration: How to Monitor Cron Jobs with Healthchecks.io.

One robust wrapper preserves the check’s status and pings success only on success:

#!/usr/bin/env bash
set -o pipefail

CHECK_URL='https://example.com/health'
HEARTBEAT_URL='https://hc.example.net/your-uuid'

if /home/you/bin/website-check.sh "$CHECK_URL"; then
  curl --silent --show-error --max-time 10 --retry 2 "$HEARTBEAT_URL" >/dev/null || {
    printf '%s FAIL reason=heartbeat_ping_failedn' "$(date -u +%FT%TZ)" >&2
    exit 1
  }
else
  rc=$?
  curl --silent --show-error --max-time 10 --retry 2 
    "$HEARTBEAT_URL/fail" >/dev/null || true
  exit "$rc"
fi

Configure the heartbeat’s grace period longer than the normal request plus retry duration. A missing ping means the job did not signal completion; it does not prove that the website is down. Healthchecks.io’s reliability guidance warns, “Sending monitoring signals over the public internet is inherently unreliable”: Pinging Reliability Tips. Treat heartbeat delivery as a second network dependency and retain the local log for diagnosis.

Rank #4
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (4GB RAM)
  • Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (4GB RAM)
  • Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
  • CanaKit Turbine Black Case for the Raspberry Pi 5
  • CanaKit Low Noise Bearing System Fan
  • CanaKit Mega Heat Sink - Black Anodized
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

“curl succeeded” but the site is unhealthy

HTTP success may be a cached shell, login page, maintenance page, or proxy error. Add a stable content marker, use a dedicated health endpoint, and record the final status after redirects.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Intermittent timeout alerts

Check DNS, routing, TLS negotiation, and the server’s response time from the monitoring host. Increase --connect-timeout or --max-time only to a value consistent with your service’s objective; add limited retries rather than hiding persistent slowness.

Redirect loop or unexpected host

Lower or retain --max-redirs, inspect the final destination, and decide whether HTTP-to-HTTPS or canonical-host redirects are expected.

Works interactively but fails in cron

Use absolute paths, check the cron user’s permissions, define required environment variables, and write stderr to a log. Run the exact cron command manually as that user.

Pipeline reports success after a failed check

Enable set -o pipefail or avoid the pipeline. The shell-monitoring guide documents why the default last-command status can mask an earlier failure: Healthchecks.io shell guide.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Best Value
Freenove Ultimate Starter Kit for Raspberry Pi 5 4 Zero 2 W (NOT Included)
  • 5 sets of code: Python (compatible with 2&3), C, Java, Scratch and Processing (Scratch and Processing code provide graphical interfaces)
  • Detailed tutorial: Can be downloaded (in English, 962-page in total) or viewed online (original in English, can be translated into other languages by browsers) (The tutorial link can be found on the product box, no paper tutorial)
  • 128 projects from simple to complex: Provides step-by-step guide with electronics and components knowledge, each project has schematics, wiring diagrams, complete code and detailed explanations
  • 223 items in total: This ultimate kit includes the most commonly used electronic components, modules, sensors, wires and other compatible items
  • Compatible models: Raspberry Pi 5 / 500 / 400 / 4B / 3B+ / 3B / 3A+ / 2B / 1B+ / 1A+ / Zero 2 W / Zero W / Zero (NOT included in this kit)

Heartbeat says late while the script is still running

Increase the heartbeat grace period, reduce retries, or move the ping to the true completion point. Never send the success ping at the start of the job.

When Bash is enough—and when it is not

Bash plus cron is appropriate for a small project with one monitoring location, a simple HTTP/content rule, and local log inspection. Move to managed monitoring when you need externally delivered alerts, retained history, several geographic vantage points, TLS-specific reporting, richer probes, or less dependence on the machine running the script.

Compare alternatives by vantage points, frequency, HTTP and content-check support, TLS diagnostics, alert destinations, retention, setup effort, and cost. A single host can tell you that that host reached the URL; it cannot establish worldwide availability.

Or skip the browser setup

If your real goal is a repeatable visual or PDF capture rather than a status probe, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes 60+ known consent platforms, newsletter popups, and chat widgets; failed loads, bot checks/CAPTCHAs, blank pages, timeouts, and cache hits are not billed as clean shots. Responses include X-Page-Verdict and X-Billed headers. Its MCP tools—take_screenshot, get_page_info, and capture_pdf—work with Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

One GET request is enough:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the full parameter list and setup in the ScreenshotNeo documentation. Python and Node.js equivalents are available when your monitoring system is not shell-based:

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}`);

The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for ScreenshotNeo and try the free allowance.

Frequently Asked Questions

Does a 200 response guarantee that my website is healthy?

No. Check a stable application marker or a dedicated health endpoint as well as the status code.

Should I use cron or a heartbeat service?

Use cron to run the check. Add a heartbeat when you need an external alert for a missing successful run; it does not replace checks from other locations.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How many retries should a monitor use?

Choose a small, intentional number based on your latency and alerting requirements. Retries reduce transient alerts but delay detection and add request load.

Quick Recap

Bestseller No. 1
CanaKit Raspberry Pi 4 4GB Starter PRO Kit - 4GB RAM
CanaKit Raspberry Pi 4 4GB Starter PRO Kit - 4GB RAM
Includes Raspberry Pi 4 4GB Model B with 1.5GHz 64-bit quad-core CPU (4GB RAM); Includes Pre-Loaded 32GB EVO+ Micro SD Card (Class 10), USB MicroSD Card Reader
$159.99
Bestseller No. 2
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (8GB RAM)
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (8GB RAM)
Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM); CanaKit Turbine Black Case for the Raspberry Pi 5
$259.95
Bestseller No. 3
CanaKit Raspberry Pi 3 B+ (B Plus) Starter Kit (32 GB EVO+ Edition, Premium Black Case)
CanaKit Raspberry Pi 3 B+ (B Plus) Starter Kit (32 GB EVO+ Edition, Premium Black Case)
Dual Band 2.4GHz and 5GHz IEEE 802.11.b/g/n/ac Wireless LAN, Enhanced Ethernet Performance
$109.99
Bestseller No. 4
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (4GB RAM)
CanaKit Raspberry Pi 5 Starter Kit PRO - Turbine Black (128GB Edition) (4GB RAM)
Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (4GB RAM); CanaKit Turbine Black Case for the Raspberry Pi 5
$209.99

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.