# Status Page and Uptime Monitor (Python, nothing to install)

> Check your sites every minute, log incidents, alert Slack or Discord, and publish a 90-day status page. Python standard library only.

- Category: Developer tools
- Version: 1.1
- Source: https://ownlyplans.com/bhaskar/status-page-and-uptime-monitor-python-nothing-to-install/

## Overview

Build your own uptime monitor and public status page in one small Python file. It checks each of your sites on a schedule, stores every check in SQLite, opens an incident after two failures in a row (so one slow response does not wake you up), closes it when the site is back, posts to a Slack or Discord webhook, and serves a status page with 90-day uptime bars and a JSON API.

Who it is for: anyone with a side project, client sites or an API who wants to know it is down before their users tell them, without paying for a monitoring service.

What you need: Python 3.10 or newer and an AI coding tool. Nothing to install: it uses only Python's standard library (urllib, sqlite3, http.server, threading).

How to use this plan: put CLAUDE.md (asset 1) in an empty folder, then paste each step's prompt into your AI tool in order. Check the "done when" line before moving on. Each step builds on the last.

## Tech stack

- HTML
- Python
- SQLite

## Build steps

### 1. Start the project and check sites once

Put CLAUDE.md in an empty folder first: it keeps the AI on the rules (one file, standard library only, UTC times). Then ask for the config file and a one-off check.

If a site that opens fine in your browser shows CERTIFICATE_VERIFY_FAILED, your Python install is missing a root certificate (common on Windows and macOS). On macOS run "Install Certificates.command" from your Python folder; on Windows update Python from python.org. Do not let the AI turn certificate checks off.

Prompt:

```text
Read CLAUDE.md. Create sites.json with "interval": 60 and a "sites" list of two entries, each with a "name" and a "url" (use https://example.com and https://www.python.org). Create monitor.py with a function check(url) that sends a GET request with a 10 second timeout and returns (ok, status_code, ms, error). Any 2xx or 3xx is ok; timeouts, connection errors and 4xx/5xx are not. Add a command `python monitor.py --once` that checks every site once and prints one line per site: name, UP or DOWN, status code and response time.
```

**Done when:** python monitor.py --once prints one UP/DOWN line per site with a status code and time in ms

### 2. Save every check in SQLite

Every result goes into status.db so the status page can show history later.

Prompt:

```text
Store each check in SQLite (status.db next to monitor.py). Table checks: id, site, ts (UTC ISO 8601), ok (0/1), status, ms, error. Create the table on start if it is missing, turn on WAL mode, and add an index on (site, ts). --once should save its results too.
```

**Done when:** after running --once twice, the checks table in status.db has 2 rows per site (open it in any SQLite viewer, or ask your AI for a one-line count)

### 3. Run the checks on a schedule

Now it runs by itself. Checks run in parallel so one slow site does not delay the others.

Prompt:

```text
Make `python monitor.py` run forever: every "interval" seconds check all sites in parallel (one thread each), save the results and print them. Add an --interval N flag that overrides sites.json (useful for testing). Ctrl+C must stop it cleanly with a short goodbye, no traceback.
```

**Done when:** python monitor.py --interval 5 adds a new row per site every 5 seconds, and Ctrl+C exits without a traceback

### 4. Open and close incidents

One failed check is often a blip. Two in a row is an incident.

Prompt:

```text
Add an incidents table: id, site, started (UTC ISO), ended (NULL while open), reason. After a check, if a site has failed twice in a row and has no open incident, open one with the last error as the reason. On the first successful check, close the open incident. Print a clear line when an incident opens or closes. Never open two incidents for the same site at once.
```

**Done when:** with a site pointed at http://127.0.0.1:9 an incident opens after the second failed check, and it closes on the first check after you point it back

### 5. Serve the status page

One command runs the checks and the page together. The page is plain server-rendered HTML with no external files, so it loads fast and works anywhere.

Prompt:

```text
Add `python monitor.py --serve` (with --port, default 8080) that runs the checks in a background thread and serves a status page at / with http.server. The page shows: a banner ("All systems operational" in green, or "Partial outage" in red when any site has an open incident); for each site its name, current status, uptime % over 90 days, and a strip of 90 small bars, one per UTC day (green 100%, amber 95 to 99.99%, red below 95%, grey for no data) with a tooltip giving the date and %; and the last 10 incidents for the sites in sites.json with start, end or "ongoing", duration and reason. Escape every value you put into HTML. Work in light and dark mode (prefers-color-scheme) and on a phone.
```

**Done when:** http://localhost:8080 shows every site with its bars, and the banner turns red while a site has an open incident

### 6. Add a JSON API

Handy for your own dashboards, a badge, or another tool.

Prompt:

```text
Add GET /api/status.json returning {"generated": ts, "operational": bool, "sites": [{"name", "url", "up", "uptime_90d", "last_check": {"ts", "status", "ms"}, "open_incident": {...} or null}]}. Send Content-Type application/json and Cache-Control: no-store. Any other path returns 404.
```

**Done when:** curl http://localhost:8080/api/status.json returns valid JSON with every site, and /nope returns 404

### 7. Alert Slack or Discord

Slack incoming webhooks and Discord webhooks (with /slack added to the end of the URL) both accept {"text": "..."}.

Prompt:

```text
Read an optional "webhook" URL from sites.json, or the STATUS_WEBHOOK environment variable (the environment wins). When an incident opens, POST JSON {"text": "🔴 <name> is down: <reason>"}; when it closes, POST {"text": "🟢 <name> is back after <duration>"}. Use a 10 second timeout and never let a failed webhook stop the monitor: log it and carry on. Send exactly one message per open and per close.
```

**Done when:** a local test receiver gets exactly one "down" message when a site goes down and one "back" message when it recovers

### 8. Keep it tidy and tested

Old checks are pruned so the database stays small, and a few tests protect the incident logic.

Prompt:

```text
Once a day (and on start) delete checks older than 90 days. Then add test_monitor.py using unittest only: test that one failure does not open an incident, two in a row do, a success closes it, a site name containing <script> is escaped on the status page, and the JSON API reports operational=false while an incident is open. Use a temporary database file in the tests.
```

**Done when:** python -m unittest passes and the old-check cleanup runs on start without errors

## Assets

### CLAUDE.md — Project rules for your AI tool. Put it in the empty project folder before step 1.

```
# Uptime monitor and status page

## Rules
- One file: `monitor.py` (tests go in `test_monitor.py`). Python 3.10+.
- Standard library only: urllib, sqlite3, http.server, threading, json. Do not add pip packages.
- Config lives in `sites.json`. Data lives in `status.db` (SQLite, WAL mode).
- Store every time in UTC as ISO 8601 (`2026-11-08T14:03:00Z`). Show times in UTC on the page.
- Escape every value you put into HTML with `html.escape`.
- A check is ok for any 2xx or 3xx response within 10 seconds.
- An incident opens after 2 failed checks in a row and closes on the first success. Never two open incidents for one site.
- Network failures (including webhooks) are logged and never crash the monitor.
- Keep functions small and named for what they do. No classes unless they make the code shorter.

## Commands
- `python monitor.py --once`  check every site once
- `python monitor.py`  run the checks forever (`--interval N` to override)
- `python monitor.py --serve`  checks + status page on http://localhost:8080 (`--port N`)
- `python -m unittest`  run the tests
```


## Build instructions by tool

### Claude Code

Open a terminal in an empty folder, run `claude`, then paste the prompt.

```text
Build the "Status Page and Uptime Monitor (Python, nothing to install)" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/status-page-and-uptime-monitor-python-nothing-to-install/.
Stack: HTML, Python, SQLite.
Follow the plan's steps in order: Start the project and check sites once → Save every check in SQLite → Run the checks on a schedule → Open and close incidents → Serve the status page → Add a JSON API → Alert Slack or Discord → Keep it tidy and tested.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Claude Code.)
```

### Cursor

Open an empty folder in Cursor, start an Agent chat, then paste the prompt.

```text
Build the "Status Page and Uptime Monitor (Python, nothing to install)" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/status-page-and-uptime-monitor-python-nothing-to-install/.
Stack: HTML, Python, SQLite.
Follow the plan's steps in order: Start the project and check sites once → Save every check in SQLite → Run the checks on a schedule → Open and close incidents → Serve the status page → Add a JSON API → Alert Slack or Discord → Keep it tidy and tested.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Cursor.)
```

### Lovable

Start a new Lovable project and paste the prompt as your first message.

```text
Build the "Status Page and Uptime Monitor (Python, nothing to install)" app from the OwnlyPlans blueprint at https://ownlyplans.com/bhaskar/status-page-and-uptime-monitor-python-nothing-to-install/.
Stack: HTML, Python, SQLite.
Follow the plan's steps in order: Start the project and check sites once → Save every check in SQLite → Run the checks on a schedule → Open and close incidents → Serve the status page → Add a JSON API → Alert Slack or Discord → Keep it tidy and tested.
Download the full plan (Markdown) from the link above and keep it in the project root as PLAN.md.
Ask me before adding anything not in the plan. (Prepared for Lovable.)
```

## Definition of done

- Every build step is complete and the app runs locally.
- The main user flow works end to end.
- Share your build on OwnlyPlans with a demo link, build time and tool.
