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.
Recommended Free Tools
#1 Best Overall
- 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.
Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCrashes, 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 minuteRedirects
--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
- 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
- Run a known-good URL and confirm an
OKline with status and elapsed time. - Use a URL that returns 404 or 500 and confirm a nonzero exit code:
./website-check.sh https://example.com/missing; echo "$?". - Set an impossible hostname or a very short timeout to exercise the transport branch.
- Set
EXPECTED_MARKERto text that is absent and verifycontent_marker_missing. - 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.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →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
- 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.
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 problemsDetect 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
- 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
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.
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.
Best Value
- 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.
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.
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
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.




