ScreenshotNeo

BlogHow-to

How to Monitor Website Health with Automated Checks

Build reliable website health checks that validate responses, content, workflows, and alerts before customers find the failure.

By the ScreenshotNeo team29 September 20269 min read

How to Monitor Website Health with Automated Checks

Direct answer: monitor website health by running recurring checks against meaningful URLs or endpoints, validating the expected status and response content, and sending confirmed failures to an alert channel. Use browser-based synthetic tests for important customer journeys. A simple uptime request can prove that a probe received an acceptable response, but it cannot prove that page assets loaded, JavaScript ran, or an interactive workflow works.

This guide shows how to design checks, implement a small monitor with cURL, Python, and Node.js, configure Google Cloud uptime checks and alerts, extend coverage with synthetic tests, and troubleshoot the failures that produce misleading green dashboards.

1. Decide what “healthy” means

An uptime result is conditional on the test you configured. A check for GET / that accepts any 2xx response answers one question: did the probe receive a successful HTTP response? It does not answer whether the checkout button works, the CSS loaded, an image is available, or an authenticated API can reach its database.

Start by listing the failure modes that matter to your users:

  • Reachability: DNS, TCP, TLS, redirects, and HTTP availability.
  • Correct response: expected status code, content type, and a known phrase or marker.
  • Important pages: homepage, login page, pricing page, documentation, and status endpoint.
  • Critical workflows: sign-in, search, checkout, form submission, or an API request sequence.
  • Performance: response latency and, for browser tests, page or step duration.

Choose a health endpoint when you have one. It should perform enough dependency checks to be meaningful without exposing private diagnostics. For a public page, choose a stable marker such as a title or a short server-rendered phrase. Avoid a marker that changes on every request.

2. Build a basic HTTP check

A lightweight check can run from cron, a CI runner, a serverless scheduler, or a monitoring service. The following example treats a response as healthy only when it returns status 200 and contains the expected text. It also records latency and uses a timeout so a hung connection becomes a failure.

cURL

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

URL="https://example.com/health"
EXPECTED="ok"
TMP=$(mktemp)
trap 'rm -f "$TMP"' EXIT

START=$(date +%s%3N)
STATUS=$(curl --silent --show-error --location \
  --connect-timeout 5 --max-time 20 \
  --output "$TMP" --write-out '%{http_code}' "$URL")
END=$(date +%s%3N)
LATENCY=$((END - START))

if [ "$STATUS" != "200" ]; then
  echo "FAIL status=$STATUS latency_ms=$LATENCY" >&2
  exit 1
fi

if ! grep -Fq "$EXPECTED" "$TMP"; then
  echo "FAIL missing expected content latency_ms=$LATENCY" >&2
  exit 1
fi

echo "OK status=$STATUS latency_ms=$LATENCY"

Python

import time
import requests

URL = "https://example.com/health"
EXPECTED = "ok"

started = time.perf_counter()
try:
    response = requests.get(URL, timeout=(5, 20), allow_redirects=True)
    elapsed_ms = round((time.perf_counter() - started) * 1000)
except requests.RequestException as exc:
    raise SystemExit(f"FAIL request error={exc}")

if response.status_code != 200:
    raise SystemExit(
        f"FAIL status={response.status_code} latency_ms={elapsed_ms}"
    )
if EXPECTED not in response.text:
    raise SystemExit(f"FAIL missing content latency_ms={elapsed_ms}")

print(f"OK status={response.status_code} latency_ms={elapsed_ms}")

Node.js

const url = 'https://example.com/health';
const expected = 'ok';
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 20_000);
const started = performance.now();

try {
  const response = await fetch(url, {
    redirect: 'follow',
    signal: controller.signal,
  });
  const body = await response.text();
  const latencyMs = Math.round(performance.now() - started);

  if (response.status !== 200) {
    throw new Error(`status=${response.status} latency_ms=${latencyMs}`);
  }
  if (!body.includes(expected)) {
    throw new Error(`missing content latency_ms=${latencyMs}`);
  }
  console.log(`OK status=${response.status} latency_ms=${latencyMs}`);
} catch (error) {
  console.error(`FAIL ${error.message}`);
  process.exitCode = 1;
} finally {
  clearTimeout(timeout);
}

Do not alert on one transient failure unless the consequence is minor and the check is extremely stable. Run a second confirmation from the same probe or require multiple failed intervals. Keep the original failure, status, latency, and timestamp in the alert so the responder can investigate without reproducing it first.

3. Configure a managed uptime check

Google Cloud Monitoring uptime checks can query public HTTP, HTTPS, and TCP targets from multiple locations. For HTTP and HTTPS, the check follows redirects and evaluates the final response against configured success criteria. The default HTTP behavior expects a 2xx response; you can also require text to be present or absent in the response body. See the Google Cloud uptime-check documentation for current fields and limits.

Health checks become more useful when they test meaningful responses from more than one location.
Health checks become more useful when they test meaningful responses from more than one location.
  1. Choose a public URL, path, or health endpoint. Use the path that represents the service you need to protect.
  2. Select HTTP, HTTPS, or TCP. Configure the port and path when the target is not the default.
  3. Set the expected status and, when useful, a response-content condition. Match a stable phrase rather than a timestamp or random identifier.
  4. Select probe locations relevant to your users. A global selection checks from all supported uptime-check regions; several locations help distinguish a regional network problem from a broad outage.
  5. Test the configuration and inspect the first results before creating an escalation policy.
  6. Create an alerting policy for failed checks and attach notification channels such as email, Slack, PagerDuty, or Pub/Sub. Google’s alerting guidance explains the relationship between a check and its policy.
Setting Practical choice Why it matters
URL Health endpoint or important page A homepage can stay green while a key service is broken.
Expected status Usually 200, sometimes a documented 3xx or 4xx Do not treat an intentional response as an outage.
Content match Stable server-rendered marker Catches an error page returned with HTTP 200.
Locations Regions where users and dependencies exist Separates regional failures from global failures.
Alert duration Long enough to suppress brief network noise Reduces paging for one lost packet while preserving real incidents.

4. Add browser and synthetic tests

Basic uptime checks do not load page assets or run JavaScript. Google Cloud explicitly separates uptime checks from synthetic monitors, which can simulate requests and record success and latency. Custom or Mocha-based synthetic tests are appropriate when a browser action, API sequence, or client-side result defines health. See the synthetic-monitor documentation.

Add a synthetic test for a workflow such as:

  1. Open the login page.
  2. Submit a test account.
  3. Verify that the account dashboard appears.
  4. Open a key route and confirm a visible data element.
  5. Sign out or delete test data when the workflow creates state.

Keep synthetic accounts isolated from production customers, store credentials in a secret manager, and make cleanup idempotent. A browser test should fail when a required selector is missing, a navigation exceeds its deadline, a console error prevents the workflow, or an API response contains an application error despite returning 200.

5. Use screenshots to detect visual breakage

Some failures are easiest to detect from the rendered page: a consent dialog covers the navigation, a deployment removes a hero section, or a CSS bundle fails while the endpoint remains available. A screenshot check complements status and synthetic checks; it should not replace them. Compare a full page or a stable element against a baseline, and allow for dynamic regions such as timestamps, ads, and rotating recommendations.

Rendered checks can reveal overlays and visual failures that a status request cannot see.
Rendered checks can reveal overlays and visual failures that a status request cannot see.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. It can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server gives Claude, Cursor, and other MCP clients take_screenshot, get_page_info, and capture_pdf tools.

See the ScreenshotNeo API documentation for all 63 options. Relevant monitoring options include full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets, custom viewport and retina scale, custom CSS and JavaScript, clicks, selector or network-idle waits, ad and tracker blocking, custom headers and cookies, timezone and geolocation, transparent backgrounds, image resizing, chosen cache TTL, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, and a usage API.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);

Use a stable selector for a focused check, a full-page capture for layout regressions, and a chosen cache TTL when repeated checks do not need a fresh render every time. Inspect the verdict and billing headers in your monitor so a bot challenge or failed load is visible as a diagnostic result rather than mistaken for a paid successful capture.

ScreenshotNeo includes 1,000 shots per month on the free plan with no card. Paid plans start at $5 for 3,000 shots; Growth is $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account.

6. Alerting and incident handling

An alert is useful only when somebody owns the response. Define the notification channel, severity, and escalation before enabling the check. A practical policy includes:

  • warning after repeated failures from one location;
  • critical when multiple locations fail or a synthetic workflow fails;
  • recovery notification when the check returns to normal;
  • links to logs, deployment history, and the runbook;
  • an owner and backup owner for each service.

Deduplicate alerts by check and incident window. Include whether the failure is DNS, TLS, connection, status, content, browser, or visual. Record the probe location and latency. This classification makes a regional provider outage easier to separate from an application regression.

7. Performance, reliability, and cost

Check frequency is a trade-off. A one-minute check detects incidents quickly but creates more requests and alerts; five or ten minutes may be sufficient for a low-risk marketing site. Use faster checks for revenue paths and slower checks for secondary pages.

Probe from more than one location when geography matters. A single probe can report its own network problem as your outage. Conversely, a global check can create noise when only one region is affected, so alert messages should preserve location detail.

Keep health endpoints cheap. Avoid starting a large report, calling slow third-party APIs, or performing destructive writes. For browser tests, block irrelevant resources where possible and wait for a meaningful readiness signal instead of an arbitrary long delay. Cache screenshots when the monitoring question is about layout rather than current content. For ScreenshotNeo, cache hits are not billed, and asynchronous jobs or bulk capture can reduce scheduler overhead for larger URL sets.

8. Troubleshooting common failures

Symptom Likely cause Fix
Check reports 200 but users see an error Application error page returned with success status Add a stable response-content match or synthetic assertion.
Check times out intermittently Slow dependency, overloaded origin, or probe network path Set explicit connect and total timeouts, compare locations, and inspect origin latency.
Redirect causes failure Checker does not follow redirects or final status is unexpected Enable redirect following and validate the final URL and status.
Content assertion never matches Marker is client-rendered or changes between requests Use server-rendered stable text or a browser synthetic test.
Browser test fails on a cookie dialog Consent banner covers or intercepts the target Accept consent in the script, hide the overlay, or use ScreenshotNeo’s consent handling.
Screenshot differs every run Ads, timestamps, animations, fonts, or responsive viewport changes Block dynamic resources, hide selectors, freeze the viewport, wait for fonts, and compare selected regions.
Many alerts arrive during deployment Checks run while the service is intentionally unavailable Use a maintenance window, deployment-aware suppression, or a longer alert duration.
ScreenshotNeo response is not billed Cache hit, bot check, blank page, timeout, or failed load Read X-Page-Verdict and X-Billed; fix the target or adjust waiting and blocking options.

9. Coverage checklist

  • A public reachability check covers the important hostname.
  • Expected status and stable response content are configured.
  • Checks run from locations that match your users.
  • A critical workflow has a browser or synthetic test.
  • Visual checks cover pages where layout failure matters.
  • Alerts include owner, severity, location, latency, and runbook links.
  • Recovery notifications and deployment suppression are tested.
  • Credentials, test data, and cleanup are handled safely.

10. FAQ

Is a 200 response enough to call a website healthy?

No. It proves only that the configured request received a successful response. Add content assertions and synthetic tests for client-side behavior.

Should I monitor the homepage or a health endpoint?

Use both when possible. The homepage tests a user-visible route; a health endpoint can provide a focused dependency signal.

How many probe locations do I need?

Use locations that represent your users and hosting dependencies. Multiple locations help identify regional failures.

When should I use a screenshot check?

Use one when visual layout, overlays, missing assets, or rendered content can fail while HTTP remains successful.

Can one monitor cover every failure?

No. Layer reachability, content, workflow, performance, and visual checks according to the failures your service can realistically have.