Canonical: https://devhelm.io/blog/definitive-guide-status-pages
Customers rarely ask for a status page on a calm Tuesday. They ask when login fails, invoices hang, or the API returns 500s while your marketing site still implies everything is fine.
Without an authoritative page, every outage becomes a guessing game. Users open tickets, refresh your homepage for answers that are not there, and check third-party outage trackers because your company has nothing trustworthy to link to.
A missing, stale, or dishonest status page creates its own damage: support improvises answers, engineering posts one-off updates across Slack and email, and buyers evaluating your reliability find no incident history. This guide is about breaking that loop.
What you will walk away with
- What belongs on a public vs internal page
- How to name components customers understand
- How to wire monitoring so the page updates itself
- Incident communication cadence and templates
- Hosted vs self-hosted trade-offs
- Concrete setup patterns for Spring Boot, FastAPI, and Next.js
The durable pattern is monitoring and status communication in one system: checks detect the issue, incidents track the response, component state follows monitor data, and customers get a clear public answer without someone toggling dropdowns at 3 a.m.
flowchart LR
A[External probes] --> B[Monitor state]
B --> C[Incident]
B --> D[Component status]
C --> E[Public status page]
D --> E
E --> F[Subscribers + support macros]
A status page is a customer-facing surface that reports current and historical health of a product, API, or platform. Typical pieces: overall status, components, active incidents, scheduled maintenance, recent history, and subscription options.
| Audience | Tone | Detail level |
|---|---|---|
| Public | Plain language, shareable | Impact, workaround, timing — no stack traces |
| Internal | Ops-facing, often auth-gated | Service names, owners, runbook links, deeper granularity |
Most SaaS companies need a public page first. Add an internal page when support needs more detail than customers should see.
Name components the way customers talk: Website, Dashboard, API, Authentication, Billing, Webhooks — not checkout-worker-v2. Group when the list gets long (Product, APIs, Regions, Dependencies).
| State | Meaning |
|---|---|
| Operational | Working normally |
| Degraded performance | Works, but latency or errors are elevated |
| Partial outage | Some users, regions, or functions affected |
| Major outage | Unavailable for most or all users |
| Under maintenance | Planned work in progress |
Also include browseable incident history and scheduled maintenance windows so planned work does not look like a surprise outage.
flowchart TD
L[Launch checklist] --> C1[Pick customer-facing components]
L --> C2[Public / internal / password-protected]
L --> C3[Hosted vs self-hosted]
L --> C4[Connect monitoring]
L --> C5[Incident templates + cadence]
L --> C6[Notifications]
L --> C7[Custom domain]
L --> C8[Link from app, docs, support]
Ship the first update fast. It does not need a root cause — it needs acknowledgment, impact, and a next-update time:
We are investigating elevated error rates on the API. Customers may see failed requests or delayed responses. We will post another update by 14:30 UTC.
Cadence: every 15–30 minutes for severe incidents (even if the update is “still investigating”); 30–60 minutes for lower-severity degradation.
Lifecycle
- Investigating
- Identified
- Monitoring
- Resolved
Keep tone direct. Prefer “Customers in EU West may see 5xx when loading invoices” over “Some users may be impacted.”
| Channel | Best for |
|---|---|
| Default customer subscription | |
| Atom / RSS | Technical users and support aggregators |
| Webhooks | Ticket systems, customer portals |
| Slack / Teams | Internal responders (not a substitute for public email) |
Avoid fatigue: not every minor blip needs to notify every subscriber.
Investigating
We are investigating [symptom] affecting [component/users/region]. Customers may see [visible impact]. We started at [time] and will post the next update by [time].
Identified
We have identified the issue as [confirmed cause]. Affected components: [components]. We are [mitigation] and will update by [time].
Maintenance
Scheduled maintenance on [date/time] for [component]. Expected impact: [none/degraded/unavailable]. Window: [duration].
Use status.yourcompany.com. Hosted products usually ask for a CNAME; keep branding restrained (logo, title, brand color) and host the page separately from the product it reports on so it stays reachable when the app is down.
Manual pages drift. During an incident the responder is debugging, rolling back, and coordinating — flipping components is easy to forget.
Better model:
- HTTP / DNS / TCP / heartbeat / browser monitors check user-visible paths
- Monitors map to public components
- Component status derives from check state
- Incidents open (or are suggested) from failures
- One public update reaches the page and subscribers
- Recovery follows the same monitor data
sequenceDiagram
participant Probe
participant Monitor
participant Incident
participant StatusPage
participant Customer
Probe->>Monitor: Failed assertion
Monitor->>Incident: Open / update
Monitor->>StatusPage: Component → Partial/Major outage
StatusPage->>Customer: Email / feed / page view
Probe->>Monitor: Passing again
Monitor->>StatusPage: Component → Operational
Monitor->>Incident: Resolve
| Option | Choose when | Watch out for |
|---|---|---|
| Hosted bundle (monitoring + page) | You want one workflow | Pricing that scales badly by seat or subscriber |
| Hosted standalone (Statuspage-style) | You already have mature monitoring | You must maintain monitor→page automation |
| Self-hosted | Control / residency / cost | The page becomes another service to run |
| DIY static banner | Temporary need today | Will not stay accurate without engineering work |
Vendor-by-vendor detail: Best status page software.
A bare /health that always returns 200 only proves the process is running. Map customer-visible surfaces to components:
| Component | What to probe |
|---|---|
| API | Health URL + at least one real customer route |
| Web App / Dashboard | URL where users open the product |
| Website | Marketing or docs homepage |
| Authentication | Login or token endpoint |
| Webhooks / jobs | Delivery health or worker heartbeat |
@Hidden
@RestController
@RequiredArgsConstructor
public class PublicHealthController {
public record HealthResponse(String status) {}
private final HealthEndpoint healthEndpoint;
@GetMapping("/public/health")
public ResponseEntity<HealthResponse> health() {
Status status = healthEndpoint.health().getStatus();
int code = Status.UP.equals(status) ? 200 : 503;
return ResponseEntity.status(code).body(new HealthResponse(status.getCode()));
}
}Map this monitor to API, then add auth / webhooks / business routes when customers depend on them.
@app.get("/health")
async def health() -> dict[str, Any]:
uptime = time.monotonic() - _start_time
return {
"status": "healthy",
"uptime_seconds": round(uptime, 1),
}Treat this as liveness, not proof that every route works. Add monitors on routes customers actually call.
Monitor the URLs customers open (app.example.com, marketing site, API). A green Web App check means the page loads; failed API calls show up on the API component.
- Create monitors for API health, web app, login, billing, DNS, SSL
- Create a status page in app.devhelm.io
- Map components → monitors (customer names on the page)
- Settings: visibility, incident mode (
AUTOMATICvsMANUAL), branding, custom domain - Enable email / feed subscribers
- Publish and link from footer, docs, help center, support macros
Code-first teams: define pages beside monitors in devhelm.yml — see Monitoring as code and devhelm status-pages.
- URL is obvious, branded, and linked from public places
- Components use customer language
- Critical paths monitored from outside your infra
- States cover degraded / partial / major / maintenance
- Users can subscribe (email or feed)
- First-update template includes impact + next-update time
- Support is trained to link the page instead of improvising
- Page stays available if the main app is down
Hosted or self-hosted? Hosted is the default — the page must stay up during incidents.
Does DevHelm replace Statuspage.io? For teams that want monitoring and the page together, yes. Statuspage is a mature communication layer without built-in uptime checks.
Full original with deeper FAQ and product walkthrough: The Definitive Guide to Status Pages