Skip to content

Repository files navigation

PHP Status Page

A self-hosted status page — component health, incident timeline, and email subscriptions — in plain PHP and MySQL. No framework, no build step, no Composer, no monthly bill.

Designed to be deployed on a different host from the application it reports on, which is the entire point of a status page: when your platform is down, the page that says so must still be up.


Why not just use a hosted one

Statuspage and its competitors are good products with per-seat pricing and someone else's branding on your outage. For a single product on shared hosting, the requirement is small and the tool should be too: a page, a database, and an endpoint your platform can push to.

The one thing you cannot compromise on is independence. A status page hosted inside the thing it monitors reports "operational" right up until it stops responding at all.


Features

Component status — per-component states (operational, degraded, partial outage, major outage, maintenance) rendered as a live board.

Incident timeline — incidents with updates over time, each with its own permalink page, so you can link a customer straight to the record instead of retelling it.

Email subscriptions with double opt-in — subscribers confirm via an HMAC-signed token before they receive anything, and every notification carries a signed unsubscribe link. Tokens are derived per purpose and per subscriber, so a confirm link cannot be replayed as an unsubscribe link or reused for a different address.

Its own SMTP client — outbound mail is spoken directly over the SMTP conversation rather than depending on mail() or a Composer mailer, because the deployment target is a cheap host where neither is reliable. Includes dot-stuffing, MIME assembly and response-code checking.

Queued, rate-limited delivery — notifications go into status_email_queue and are drained in batches under a daily cap. An incident affecting a large subscriber list will not trip the host's hourly mail limit and get the domain throttled.

Signed sync APIapi/sync.php accepts pushed status from the monitored platform, authenticated with an HMAC shared secret and a replay window (max_age_seconds), so a captured request cannot be replayed later to fake an all-clear.

Health endpointhealth.php for upstream monitoring of the status page itself.

Settings from the database — thresholds and display options read at runtime rather than living in code.


Layout

bootstrap.php               DB, helpers, templating, SMTP client, mail queue
index.php                   the status board
incident.php                individual incident page
subscribe.php               subscription form + double opt-in dispatch
verify.php                  confirms a subscription from a signed token
unsubscribe.php             signed one-click unsubscribe
health.php                  health probe for the status page itself
api/sync.php                HMAC-authenticated status ingest
assets/status.css           styles
src/StatusMonitoringService.php   component evaluation (host application side)
src/StatusSyncService.php         pushes status to this page (host application side)
src/Core/                   Config, Database, Logger

The two files in src/ that end in Service.php run on the monitored platform, not on the status page — they are what pushes state into api/sync.php. They are included so the contract between the two halves is legible.


Setup

cp config.local.php.example config.local.php

Fill in the database connection, the site URL, the SMTP credentials, and — most importantly — a long random secret shared with whatever pushes to api/sync.php. Generate it, don't invent it:

php -r "echo bin2hex(random_bytes(32));"

Then create the schema (status_settings, status_components, status_incidents, status_subscribers, status_email_queue) and point a webserver at the directory. .htaccess files restrict what is reachable.

Requirements: PHP 8.0+, PDO MySQL, outbound SMTP.


Operational notes

config.local.php holds a database password, an SMTP password and the sync secret. It is gitignored — keep it that way, and if this code was ever deployed with a secret committed, rotate it rather than assuming nobody looked.

Subscriber email addresses are personal data. Whatever retention and erasure rules apply to your customer list apply to this table too, and the unsubscribe path needs to actually work.

Host it away from your application. Different server, ideally different provider.


Notes on this repository

Extracted and generalised from a production platform built under contract. Client branding, logo and business logic have been removed; namespaces were rebased to App\. What remains is the status-page subsystem and the minimal core it depends on.


Licence

MIT — see LICENSE.

About

Self-hosted status page with incidents and email subscriptions

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages