# TinyStatus: a status page rendered from YAML checks into three static files

> TinyStatus is a Python script that probes HTTP endpoints, hosts, and ports from a YAML check list and renders a static status page, a history page, and a JSON history file. It needs Python 3.11 or newer, five dependencies, and no database or web framework, and it can be run in a loop with nohup or built into a small Docker image.

**harsxv/tinystatus** — Tiny status page generated by a Python script

- Repository: https://github.com/harsxv/tinystatus
- Website: https://status.harry.id/
- Stars: 1,588 · Forks: 95
- Language: Python
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/harsxv-tinystatus

## The whole pipeline is one script and three output files

TinyStatus is a simple, customizable status page generator that allows you to monitor the status of various services and display them on a clean, responsive web page. There is a live demo at status.harry.id, and the project ships a favicon-sized PNG in an `assets/` directory, which tells you how small the output is.

What it produces is three files. Running the script generates `index.html`, the main status page, `history.html`, the status history page, and `history.json`, the status history and timestamp data. That is the entire output contract, and it is the reason the tool is interesting: there is no server component, no database, and no runtime process serving traffic. The output is static files that any web server can hand out.

The script itself is a single file, `tinystatus.py`, sitting at the root of the tree next to the configuration. The Dockerfile copies exactly six things into the image, `checks.yaml`, `history.html.theme`, `incidents.md`, `index.html.theme`, `requirements.txt`, and `tinystatus.py`, and then runs the script. That is a complete inventory of what the tool needs to operate, and it is short enough to read.

The templates are Jinja2 templates, `index.html.theme` and `history.html.theme`, and the incident log is a Markdown file, `incidents.md`. Markdown for incidents is a deliberate choice, because it means the incident history is edited in a text file rather than through a form, and it is the same file a person would write in a wiki or a runbook.

The design is coherent throughout: configuration in YAML, incidents in Markdown, appearance in templates, output in HTML and JSON, and no moving parts at runtime beyond the script itself.

## Three check types, and one flag for self-signed certificates

The check definitions live in `checks.yaml`, and the structure has two levels, a group with a title and a list of checks under it. The `checks.yaml` example in the README shows three check types.

The `http` type takes a `host` and a `url` and an `expected_code`, as in the GitHub Home check with `host: https://github.com`, `url: https://docs.github.com/en`, and `expected_code: 200`. The `ping` type takes a `host` address, as in the Google Public DNS check pointing at `8.8.8.8`. The `port` type takes a `host` and a `port`, as in the Dummy MySQL Database check pointing at `db.example.com` on port 3306.

The `expected_code` field is the part that makes the http check worth having. A check that only asks whether the connection succeeded will pass for a 500 error page, which is the exact case a status page exists to surface. Comparing the returned status code against an expected value is what makes the check about the service rather than about the network.

The fourth example in the list is the one that shows the tool's real audience. The Home Server with Self-Signed Certs check has `ssc: True`, along with a host of `https://homeserver.local` and an `expected_code` of 200. A self-signed certificate makes a normal HTTPS check fail, because the certificate cannot be validated against a trusted authority. The `ssc` flag is the switch that tells the checker to accept that case.

That single field is a good shortcut to who this is for. A status page for public services does not need it. A status page for a homelab, where half the hosts are behind a reverse proxy with a private certificate, needs it on every check. The dependency list supports the same reading, since aiohttp is present for the HTTP work and the port and ping checks are the same category of network probe.

## The environment file is the control panel

All of the runtime behaviour is configured in a `.env` file in the project root, and the full set of variables is short enough to reproduce.

```
MONITOR_CONTINOUSLY=True
CHECK_INTERVAL=30
MAX_HISTORY_ENTRIES=100
LOG_LEVEL=INFO
CHECKS_FILE=checks.yaml
INCIDENTS_FILE=incidents.md
TEMPLATE_FILE=index.html.theme
HISTORY_TEMPLATE_FILE=history.html.theme
STATUS_HISTORY_FILE=history.json
HTML_OUTPUT_DIRECTORY=/var/www/htdocs/status/
```

Three of these decide the operating mode. `MONITOR_CONTINOUSLY=True` makes the script loop instead of running once and exiting. `CHECK_INTERVAL=30` is how many seconds between check cycles in that loop. `MAX_HISTORY_ENTRIES=100` bounds how much history is kept, which matters because the script rewrites `history.json` each cycle and an unbounded history would grow that file forever.

The rest are file paths, and they are all overridable, which is what makes the tool embeddable in an existing layout. `CHECKS_FILE` names the YAML with the checks, `INCIDENTS_FILE` the Markdown incident log, `TEMPLATE_FILE` and `HISTORY_TEMPLATE_FILE` the two Jinja2 templates, `STATUS_HISTORY_FILE` the JSON history, and `HTML_OUTPUT_DIRECTORY` where the HTML is written. The default output path is `/var/www/htdocs/status/`, which is an Apache document root path, so the tool is written on the assumption that a normal web server will serve the result.

`LOG_LEVEL=INFO` is the odd one out, and it is the only one that is not about paths or timing. The values are read by python-dotenv, which is one of the five dependencies, so the `.env` file is the standard dotenv format and needs no parsing code of its own.

Grouping is supported, which is what the `title` field on a group in `checks.yaml` gives you, and cards can be made clickable, optionally. Both are in the feature list, and the grouping is the one that changes the shape of the output page rather than just its content.

## Two ways to run it, and one way to serve it

The usage section is deliberately minimal, and it starts with the direct invocation:

```bash
python tinystatus.py
```

One run, one set of output files, and the script exits. For a continuously updated page, the instruction is to run the script in the background. On Unix-like systems, Linux and macOS, that is:

```bash
nohup python tinystatus.py &
```

and on Windows the suggestion is to use the Task Scheduler to run the script at startup. The Windows path is a different mechanism rather than a different tool, which is a sensible choice given that there is no nohup equivalent that behaves the same way there.

Serving is left to the reader. The instruction is to serve the generated HTML files using your preferred web server, with Apache, NGINX, or a simple Python HTTP server for testing named as examples. The last of those is only for testing, but it is a useful reminder that the output directory is just a directory of files and the tool does not care what serves them.

The Docker route is the tidiest way to run it, and the image is minimal. The Dockerfile is based on `python:3-alpine`, sets the working directory to `/usr/src/app`, copies the five input files and the script in one instruction, installs the requirements with `pip install --no-cache-dir -r requirements.txt`, and sets the command to run the script.

The two documented Docker commands are:

```bash
docker build -t tinystatus .
docker run -ti --rm --name tinystatus -v "$PWD":/usr/src/myapp -w /usr/src/myapp tinystatus
```

The volume mount is the point of the second command. It maps your working directory into the container at `/usr/src/myapp` and sets it as the working directory, so the `.env` file, the `checks.yaml`, and the output directory are all on the host rather than inside the container's writable layer. Without that mount, the status files would be written into the container and disappear when it stopped.

## The output is a template plus a CSS edit, not a theme system

Customisation is split between three files, and it is worth being clear that there is no configuration-driven theming layer.

The appearance of the status page is changed by editing the CSS in `index.html.theme` and `history.html.theme`. That is a direct edit of the templates, not a set of variables, and the practical consequence is that a site with two brand themes needs two copies of the template or a template that reads a variable. There is no documented mechanism for that.

Light and dark mode are supported as a feature, and the project shows both side by side with screenshots. Given that the CSS is edited by hand in the template, the light and dark handling is most likely a media query or a class in that CSS rather than a switch, which means it is a starting point you can edit rather than a setting you can flip.

The services themselves are added and removed by modifying `checks.yaml`, which is the structured, version-controllable part of the configuration and the one you would put under review. Incidents and maintenance schedules are added by editing `incidents.md`, which is optional in both the configuration steps and the usage steps.

That optionality is a design choice with a consequence. The incident log is a file you maintain, not something the tool derives from failed checks, so a check that starts failing does not automatically appear as an incident with a start and end time. The history page is generated from the recorded check results, and the incident page is generated from your prose. For a homelab that is a reasonable division, because the person who knows why the service is down is the person writing the note, but it does mean the two pages can disagree if you forget to write the note.

The responsive design covers both the status page and the history page, so a phone can read the result.

## Where TinyStatus is the wrong tool

The boundaries of this project are clear, and most of them come from the static-file design.

First, there is no alerting. Nothing here pages you, opens a ticket, or sends a message. The tool produces a page that says whether a service was up when it was last checked, and a human has to look at it. If you need to be told, you need a monitoring system that pushes, and the usual arrangement is to point that system at the same YAML or to use its own check definitions.

Second, there is no uptime percentage and no availability calculation in the output described. `history.json` holds status history and timestamp data and `MAX_HISTORY_ENTRIES` trims it, so what you have is a bounded window of recent checks rather than a long-term record. A status page that promises a 99.9 percent figure needs history that survives restarts and a definition of what counts as an outage, and neither is described here.

Third, the check set is small. Three types, http, ping, and port, cover most homelab services, but there is no check for a database query, a gRPC endpoint, a specific string in a response body, or a multi-step transaction. A team that needs its checks to mean something specific will have to encode that in the expected status code or write a wrapper.

Fourth, the running model is a foreground process that has to be kept alive. `nohup` on a Unix box or a Task Scheduler entry on Windows is a fine answer for one machine, and a poor answer for a fleet of servers. There is no service unit, no container orchestration manifest, and no supervisor configuration in the tree.

Fifth, the security posture is simple, and worth stating plainly. There is no authentication on the status page itself, and the tool writes static files to a directory you name. If the services you monitor are internal, an unlisted status page reveals internal hostnames to anyone who guesses the URL, so the directory should not be served publicly without a decision about that. Related to this, the `ssc` flag for self-signed certificates is convenient and also means a certificate that fails validation for a genuine reason will be reported as healthy.

Finally, the project is small and the releases are old. The newest tag is v1.3.0 from 2024-10-24, with v1.2.0 from September 2024 and v1.1.0 from the week before, and the last push to the repository was on 2026-09-29. The code is still being touched, but there is no recent release to pin, and the single-file architecture means there is no plugin surface to grow into.

## Conclusion

TinyStatus fits a homelab operator or small site owner who wants a status page that says whether a handful of services are up, who can edit a YAML file and a template, and who is content to serve static files from an existing web server. It is the wrong tool for a public service that needs incident communication, pager integration, or uptime percentages, and it is the wrong choice for a fleet of monitored targets. Check three things before you deploy it: that your web root is writable at the path named in HTML_OUTPUT_DIRECTORY, that you have a place to run the loop, since the script is a foreground process that needs nohup or a scheduler, and that the history is bounded, because MAX_HISTORY_ENTRIES trims the JSON and the history page, and the incidents file is yours to maintain by hand.

## FAQ

### What is TinyStatus and what does it produce?

TinyStatus is a simple, customizable status page generator written in Python that monitors services and displays them on a responsive web page. Running the script generates three files: `index.html` as the main status page, `history.html` as the status history page, and `history.json` as the status history and timestamp data.

### How do I install and run TinyStatus?

Clone the repository with `git clone https://github.com/harsxv/tinystatus.git`, change into the directory, and run `pip install -r requirements.txt`. Python 3.11 or higher and pip are the prerequisites. Then create a `.env` file, edit `checks.yaml`, and run `python tinystatus.py`, or use `nohup python tinystatus.py &` to keep it running in the background.

### What kinds of checks can TinyStatus run?

Three types, configured in the `checks.yaml` file: `http` checks with a host, a url, and an `expected_code` to compare against, `ping` checks against a host address, and `port` checks against a host and port number. There is also an `ssc` flag for hosts using self-signed certificates, and checks can be organised into groups with a title.

### How do I run TinyStatus with Docker?

Build the image with `docker build -t tinystatus .` and run it with `docker run -ti --rm --name tinystatus -v "$PWD":/usr/src/myapp -w /usr/src/myapp tinystatus`. The volume mount is what puts your `.env` file, checks file, and output directory on the host rather than inside the container.

## Sources

- [harsxv/tinystatus on GitHub](https://github.com/harsxv/tinystatus)
- [License: MIT](https://github.com/harsxv/tinystatus/blob/master/LICENSE)
- [Project website](https://status.harry.id/)
- [README](https://github.com/harsxv/tinystatus/blob/master/README.md)
- [Releases](https://github.com/harsxv/tinystatus/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/harsxv-tinystatus
