Skip to content
Back to Operations & SRE

Uptime Checks

Lightweight TCP/HTTP pings from multiple regions to detect reachability outages with minimal cost.

Why

Synthetic transactions are thorough but expensive and slow; uptime checks are cheap, fast, and answer the single most important question: "can a user reach us at all?" A 5-region HTTP GET to /health every 10 seconds costs pennies and catches the most common outage class — a load balancer, CDN, or DNS failure that makes the site unreachable — within 10 seconds. They are the floor of your monitoring stack: every service should have one, regardless of how sophisticated the rest of the instrumentation is. Without an uptime check, your most embarrassing outage is the one where the site is down and you find out from a customer tweet.

When

Every public endpoint, day one. This is the first monitoring signal to add, before dashboards, before RUM, before anything else.

How

1. Define a liveness endpoint per service
   - GET /health returns 200 + {"status":"ok"} with no dependencies checked (fast, <10ms)
   - GET /ready returns 200 only if the service can serve real traffic (DB connected) — use for rollout, not uptime

2. Configure checks from 3+ external regions
   - HTTP(S) check to /health, 10s cadence, 5s timeout
   - Regions: at least 3 continents (e.g., US, EU, Asia)
   - Use a third-party (UptimeRobot/Pingdom/Better Stack) — NOT your own cloud

3. Alert on 2 consecutive failures from 2+ regions
   - Single-region failure = a probe problem, not an outage → ticket, don't page
   - 2+ regions failing = the site is genuinely down → page immediately

4. Wire the alert to the owning team with a runbook
   - Alert includes: URL, failing regions, last 200 timestamp, runbook link
   - Page within 60s of the first failing region (10s cadence × 2 + eval)

5. Track uptime as an SLI and report it externally
   - "99.95% uptime over 28 days" = the SLI; alert if burning the error budget
   - Publish a status page (status.example.com) so customers see the same number

Example

# /health — liveness (no deps, always fast)
app.get('/health', (req, res) => res.json({ status: 'ok' }));

# /ready — readiness (checks deps, used by load balancer)
app.get('/ready', async (req, res) => {
  try { await db.ping(); res.json({ status: 'ready' }); }
  catch { res.status(503).json({ status: 'not_ready' }); }
});

# UptimeRobot config (3 regions, 10s cadence):
#   monitor type: HTTP(s), keyword: "ok"
#   interval: 10s, timeout: 5s
#   alert: 2 fails → @oncall-payments (PagerDuty)
# SLI: uptime = (total_seconds - down_seconds) / total_seconds ≥ 0.9995 over 28d

Common Problems

  • ⚠Checking /ready (which includes dependencies) instead of /health — a dependency blip marks the service down even though it could still serve most traffic.
  • ⚠Running the checker from your own cloud — when your cloud has a regional outage, the checker goes down too and you miss the outage you're trying to detect.
  • ⚠Paging on a single failed check — one network blip pages the on-call; require 2 consecutive failures to confirm a real outage.
  • ⚠No status page — customers report the outage on Twitter before your team knows; a public status page builds trust and reduces support load.

Best Practices

  • ✓Separate /health (liveness, no deps, always fast) from /ready (readiness, checks deps) — uptime checks hit /health so a dependency blip doesn't false-alarm.
  • ✓Run checks from a third-party in 3+ regions on 10s cadence; page only on 2+ regions failing to eliminate single-probe noise.
  • ✓Publish a public status page fed by the same uptime data customers see — it reduces inbound support tickets during an outage by ~60%.
  • ✓Track uptime as an SLI with an error budget and review it monthly — if you're burning budget, invest in reliability before you hit a month that breaks the SLO.