How to Add a Status Page to Your App for Free
Add a status page to your app for free: build one in ~80 lines, run Gatus or Upptime, or feed one from Temps monitors. Statuspage's free plan has caps.
Adding a status page to your app takes less than an afternoon using open-source tools, and it means users can check what's happening during an outage instead of opening tickets. Atlassian Statuspage has a free plan capped at 100 subscribers, 25 components and two team members; paid plans start at $29/month. This guide shows the free paths: build one yourself in ~80 lines of code, or run Gatus or Upptime — and how to feed one from the monitors and incidents Temps already runs.
TL;DR: A status page needs health check endpoints, a polling aggregator, and a page hosted somewhere other than your app — roughly 80 lines of code. Or use an open-source tool like Gatus or Upptime. Statuspage's free plan covers small teams; paid plans start at $29/month. Temps runs uptime monitors and incidents for every environment, but it has no public status page, so you publish one with one of the options below.
How Do You Add a Status Page to Your App?
The fastest path is four steps: expose a /health endpoint, run a polling script every 60 seconds, write results to a JSON file, and serve a static HTML page from a separate host. The whole thing fits in about 80 lines of code across three files.
If you want something pre-built, Gatus and Upptime are the two open-source options worth reaching for. Both are actively maintained, free to self-host, and require no custom code.
If you deploy on Temps, monitoring is already running: every environment gets an uptime monitor, and failures open incidents automatically. What Temps doesn't do is publish a public page, so you still need one of the options in this guide for the part your users see.
Why Does Every App Need a Status Page?
A status page isn't just about transparency — it reduces the operational cost of every outage you'll ever have. Atlassian's own guidance on incident communication makes the same point: proactive status updates mean fewer duplicate support tickets.
It Kills Support Ticket Volume
When your app goes down, users panic. They can't tell if it's their network, their browser, or your server. So they file tickets. Every single one of those tickets costs you time — reading, triaging, responding with "we're aware of the issue."
A status page short-circuits that entire loop. Users check the page, see the incident banner, and wait. Your support queue stays manageable. Your on-call engineer stays focused on actually fixing the problem instead of answering emails.
Enterprise Customers Expect It
If you sell to businesses, you'll hit SLA requirements sooner than you think. SOC 2 audits ask about incident communication procedures. Enterprise procurement teams check whether you have a public status page before signing contracts.
A status page is often the simplest way to show a buyer how you communicate during incidents.
Users Check Status Before Filing Bugs
Have you ever received a bug report that was actually just a service outage? It happens constantly. Without a status page, users can't distinguish between "the app is broken for everyone" and "something is wrong on my end."
A visible status indicator saves both sides time. Users self-triage. Your engineering team gets fewer false bug reports. Everyone wins.
What Should a Good Status Page Show?
A well-designed status page covers five elements. Users expect current information during an outage, and meeting that expectation requires more than a green checkmark.
Component-Level Status
Don't show a single "up" or "down" indicator for your entire app. Break it into components: API, web app, database, authentication, CDN, background jobs. Users need to know which parts are affected.
Three states work well: Operational, Degraded Performance, and Major Outage. Some teams add a fourth — Partial Outage — for situations where only some regions or user segments are impacted.
Incident Timeline with Updates
Every incident needs a timeline. When did it start? What's being done? When was it resolved? Post updates every 20-30 minutes during active incidents, even if the update is "still investigating."
The worst thing you can do is post "we're investigating" and go silent for two hours. Users assume you've forgotten about them. Frequent updates — even without new information — signal that the team is actively working.
Uptime Percentage Over 90 Days
Show a rolling 90-day uptime percentage per component. This gives users context. Is this a rare blip on a 99.99% track record, or is it the third outage this month on a service struggling to hit 99.5%?
A simple bar chart with daily uptime works well. Green bars for clean days, yellow for degraded, red for outages. GitHub's status page does this effectively.
Scheduled Maintenance Windows
Proactive communication matters as much as reactive. Post maintenance windows at least 48 hours in advance. Include the expected duration, which components are affected, and whether users will experience downtime.
Response Time Graph
A latency graph for the past 24-72 hours shows users whether performance has been degrading. If your API response time crept from 100ms to 800ms over four hours before the outage, that context helps users understand what happened.
How Does the Architecture Work?
A status page system has five parts: health check endpoints, an aggregator, status computation, a public page, and a notification system.
Health Check Endpoints
Every service in your stack needs a /health endpoint. This isn't a simple return 200 — a good health check verifies that the service can actually do its job.
For an API server, that means checking database connectivity, cache availability, and external service reachability. For a background worker, it means verifying the job queue is connected and processing. A health endpoint that always returns 200 is worse than no health endpoint — it gives you false confidence.
// Good health check response
{
"status": "healthy",
"checks": {
"database": { "status": "up", "latency_ms": 3 },
"redis": { "status": "up", "latency_ms": 1 },
"storage": { "status": "up", "latency_ms": 12 }
},
"version": "2.4.1",
"uptime_seconds": 847293
}
The Aggregator
The aggregator is a service (or cron job) that polls each health endpoint at regular intervals — typically every 30-60 seconds. It records the response status, latency, and any error details.
Keep the polling interval consistent. Irregular checks create noisy data. And always poll from outside your infrastructure — checking health from the same server that runs the service doesn't tell you much about actual user reachability.
Status Computation
Raw health check data needs processing before it becomes useful status information. A single failed check shouldn't flip a component to "Major Outage" — network blips happen. Most systems use a threshold: repeated consecutive failures trigger a status change, and recovery requires multiple consecutive successes to prevent flapping.
The Public Page
The public-facing status page should be hosted separately from your main application. If your app is down, your status page needs to still be accessible. Many teams host the status page on a separate subdomain (status.yourapp.com) with a different hosting provider or CDN.
Notification System
Subscribers should be able to opt in for email, webhook, or Slack notifications. When a component status changes, the system fires notifications to all subscribers for that component. Keep it simple — status change events in, notifications out.
How Do You Build a Status Page from Scratch?
Building a minimal status page takes roughly 80 lines of code and an afternoon. You don't need any health-check packages for a basic setup.
We've found that the simplest status page implementations — a polling script, a JSON file, and a static HTML page — tend to be the most reliable. Every dependency you add is another thing that can break during an outage, which is exactly when you need the status page to work.
Step 1: Add Health Check Endpoints
Start with a /health route in your application. Here's a Node.js/Express example that checks database and Redis connectivity:
app.get('/health', async (req, res) => {
const checks = {};
// Check database
try {
const start = Date.now();
await db.query('SELECT 1');
checks.database = { status: 'up', latency_ms: Date.now() - start };
} catch (err) {
checks.database = { status: 'down', error: err.message };
}
// Check Redis
try {
const start = Date.now();
await redis.ping();
checks.redis = { status: 'up', latency_ms: Date.now() - start };
} catch (err) {
checks.redis = { status: 'down', error: err.message };
}
const allUp = Object.values(checks).every(c => c.status === 'up');
res.status(allUp ? 200 : 503).json({
status: allUp ? 'healthy' : 'unhealthy',
checks,
timestamp: new Date().toISOString()
});
});
Step 2: Build the Aggregator
A simple polling script runs on a schedule, checks each endpoint, and writes results to a JSON file or database:
const SERVICES = [
{ name: 'API', url: 'https://api.yourapp.com/health' },
{ name: 'Web App', url: 'https://yourapp.com/health' },
{ name: 'Worker', url: 'https://worker.yourapp.com/health' },
];
async function pollServices() {
const results = [];
for (const service of SERVICES) {
try {
const start = Date.now();
const res = await fetch(service.url, { signal: AbortSignal.timeout(5000) });
results.push({
name: service.name,
status: res.ok ? 'operational' : 'degraded',
latency_ms: Date.now() - start,
checked_at: new Date().toISOString()
});
} catch (err) {
results.push({
name: service.name,
status: 'down',
error: err.message,
checked_at: new Date().toISOString()
});
}
}
// Write to JSON file (or insert into database)
await fs.writeFile('./status.json', JSON.stringify(results, null, 2));
}
// Run every 60 seconds
setInterval(pollServices, 60_000);
pollServices();
Step 3: Serve the Status Page
The simplest approach is a static HTML file that fetches the JSON data and renders it. Host this on a separate server or CDN — it needs to work even when your main app is down.
<!DOCTYPE html>
<html>
<head>
<title>Status - YourApp</title>
<style>
.status-item { padding: 12px; margin: 8px 0; border-radius: 6px; }
.operational { background: #d4edda; color: #155724; }
.degraded { background: #fff3cd; color: #856404; }
.down { background: #f8d7da; color: #721c24; }
</style>
</head>
<body>
<h1>System Status</h1>
<div id="services"></div>
<script>
fetch('/status.json')
.then(r => r.json())
.then(services => {
const el = document.getElementById('services');
el.innerHTML = services.map(s =>
`<div class="status-item ${s.status}">
<strong>${s.name}</strong>: ${s.status}
${s.latency_ms ? `(${s.latency_ms}ms)` : ''}
</div>`
).join('');
});
</script>
</body>
</html>
That's roughly 80 lines across three files. It won't win any design awards, but it works. Run the aggregator as a systemd service or cron job, deploy the HTML to a CDN, and you've got a functional status page.
What Open-Source Status Page Tools Exist?
The open-source ecosystem offers several mature alternatives to building from scratch (GitHub's status-page topic lists hundreds). Here are the ones actually worth using.
Upptime — GitHub-Powered Monitoring
Upptime runs entirely on GitHub Actions. No server required. It stores uptime data in the repository itself, uses GitHub Issues for incidents, and deploys a static status page via GitHub Pages.
Why would you want this? Zero infrastructure cost. GitHub Actions gives you the compute, GitHub Pages gives you the hosting, and the repository itself becomes your database. The tradeoff is that you're limited by GitHub Actions quotas and can't customize beyond what the templating system offers.
Gatus — The Developer's Choice
Gatus is a single Go binary (Apache-2.0, 12K+ GitHub stars as of October 2026) configured entirely through YAML. Define your endpoints, set alerting conditions, and run it. It supports HTTP, TCP, DNS, ICMP, and even SSH health checks out of the box, and its status page can show announcements for incidents and maintenance.
What sets Gatus apart is its condition language. You can write checks like [STATUS] == 200 && [BODY].status == 'healthy' && [RESPONSE_TIME] < 500. That's more expressive than most monitoring tools at any price point.
Statping-ng — Full-Featured Web UI
Statping-ng is the successor to Statping (which, like Cachet, went unmaintained for a while). It includes a web dashboard, multiple notification integrations (Slack, Discord, email, Telegram), and a REST API.
It's heavier than Gatus — it needs a database (SQLite, PostgreSQL, or MySQL) — but gives you more out of the box. The catch is maintenance: its last release, v0.93.0, shipped in June 2025 and the repository has had no commits since (GitHub releases, checked October 2026). For a maintained option, use Gatus (v5.37.0, September 2026) or Upptime.
Cstate — Hugo-Based Static Pages
Cstate generates a static status page using Hugo. You define incidents and component status in Markdown files, and Hugo builds a fast, CDN-friendly static site. It's the lightest option — no backend, no database, no running service.
The downside: incident updates are manual. You create a Markdown file for each incident, which is fine for small teams but doesn't scale if you want automated status changes based on health checks.
Most teams start with a simple tool like Upptime or Cstate and graduate to something like Gatus once they hit 10+ services. The mistake is starting with a heavy tool before you know what you actually need to monitor. Pick the lightest option that covers your current requirements.
Comparison: Temps vs Statuspage vs Gatus
| Feature | Temps | Atlassian Statuspage | Gatus |
|---|---|---|---|
| Price | Free (self-host) | Free plan (100 subscribers, 25 components); paid from $29/mo | Free (self-host) |
| License | MIT / Apache 2.0 | Proprietary SaaS | Apache-2.0 |
| Setup | Monitor auto-created per environment | Manual configuration | YAML config file |
| Check interval | 60 seconds by default | Fed by your own monitoring or integrations | Configurable (30s+ typical) |
| Retry logic | 5xx retried up to 3 times with backoff; timeouts retried up to 3 times | N/A | Configurable |
| Incident tracking | Automatic + manual | Manual or integration-driven | Announcements and alerting |
| Public status page | No — status overview in the authenticated dashboard and API | Yes (hosted by Atlassian) | Yes (built-in web UI) |
| Deployment platform, analytics, error tracking | Built in | None | None |
Open-Source Tool Comparison
| Tool | Language | Database | Auto-Detection | UI | Setup Time |
|---|---|---|---|---|---|
| Upptime | TypeScript | GitHub repo | Yes (Actions) | Static (Pages) | ~15 min |
| Gatus | Go | SQLite/Postgres | Yes (polling) | Built-in web | ~10 min |
| Statping-ng | Go | SQLite/Postgres/MySQL | Yes (polling) | Full dashboard (last release June 2025) | ~30 min |
| Cstate | Hugo | None (Markdown) | No (manual) | Static site | ~20 min |
| DIY | Any | Any | Custom | Custom | ~2 hours |
How Does Temps Handle Monitoring and Incidents?
Temps covers the monitoring half of a status page — the checks and the incident record — and leaves the public page to you. The monitoring docs are the reference.
Automatic Monitor Creation per Environment
When you create a new environment in Temps, an uptime monitor is created for it automatically, so by the time your first deployment completes, monitoring is already running. Monitors are removed when environments are deleted. You can add more from the dashboard or with bunx @temps-sdk/cli monitors create.
Health Checks Every 60 Seconds
Monitors run every 60 seconds by default (300, 600, 900 and 1,800 seconds are also available), with up to 10 checks in parallel. Each check requests the environment's public URL plus a check path (default /), so it reflects what real users reach. Point the path at a cheap health endpoint rather than your homepage.
Each request times out after 10 seconds. 5xx responses are retried up to 3 times with exponential backoff (100 ms, 200 ms, 400 ms); timeouts are retried too; a refused connection is recorded immediately, because retrying a crashed container adds noise without any chance of success.
HTTP Status Code Mapping
- Operational: 2xx and 3xx (many apps redirect
/to a login page) - Degraded: 4xx client errors
- Major outage: 5xx after retries, timeouts or refused connections
Incident Tracking
Temps opens an incident automatically when a monitor fails and resolves it when the monitor recovers. You can also create incidents by hand — for planned maintenance or partial outages that automated checks don't catch — and post status updates to them. Notifications go to the email, Slack or webhook channels you configure.
Publishing a Public Page
Temps doesn't publish a public status page. The project's status overview — every monitor's state, uptime history and open incidents — lives in the authenticated dashboard and API (GET /api/projects/{project_id}/status). To show it to users, run Gatus or Upptime alongside it, or adapt the DIY page above to read from that API with a read-only token, and host the page somewhere other than your Temps server so it stays up when your app doesn't. The uptime monitoring guide covers the checking side in more depth.
To install Temps:
curl -fsSL https://temps.sh/deploy.sh | bash
Temps is free to self-host (dual MIT / Apache 2.0 license), so the only cost is the server you already pay for. Temps Cloud, a hosted control plane for your own servers, is coming soon from $29/month, with no per-seat fees.
FAQ
How often should health checks run?
Every 30-60 seconds is the sweet spot for most applications. More frequent checks (every 5-10 seconds) generate excessive load and noisy data without meaningfully faster detection. Temps checks every 60 seconds by default, which strikes the balance between responsiveness and load. If you need sub-minute detection, you're looking at push-based health checks instead of polling.
Should the status page be hosted separately from the main app?
Yes, always. If your application server goes down and your status page lives on the same server, users see nothing — which is worse than showing a "Major Outage" banner. Host your status page on a separate provider, CDN, or static hosting service. GitHub Pages, Cloudflare Pages, or a separate VPS in a different region all work. This applies to Temps users too: Temps has no public status page, so host yours away from the Temps server.
Do I need a status page if I already have uptime monitoring?
Uptime monitoring tells you when something breaks. A status page tells your users. They serve different audiences. Your monitoring tool alerts your on-call engineer at 3am. Your status page prevents 200 support tickets from landing in your inbox while that engineer is fixing the problem. You need both.
What's the minimum viable status page?
A static HTML page that shows component status and updates manually via git commits. That's it. Cstate does exactly this with Hugo. You don't need automated health checks, subscriber notifications, or historical charts on day one. Start with manual updates, add automation when the manual process becomes painful. Most teams hit that point around 5-10 incidents per quarter.
How does Temps handle connection failures vs HTTP errors?
Temps distinguishes between connection failures and HTTP errors. A connection refused error records a failure immediately — there's no point retrying a crashed container. A timeout retries up to 3 times. HTTP 5xx errors retry up to 3 times with exponential backoff starting at 100ms, and 4xx responses count as degraded rather than down. This avoids false alarms from transient network blips while still catching real outages quickly.
Stop Leaving Your Users in the Dark
A status page is one of those things that seems optional until your first major outage. Then it becomes the difference between "we handled that well" and "why didn't anyone tell us the app was down?"
You've got three paths. Build it yourself with the code examples above — it's an afternoon of work and you'll understand exactly how it fits together. Pick an open-source tool like Gatus or Upptime if you want something battle-tested without the maintenance burden. Or use a platform that runs the monitoring alongside your deployment pipeline, so the checks and incidents aren't another tool in your stack, and publish the page with one of the first two.
Temps gives you an uptime monitor for every environment, checks every 60 seconds, and incident tracking built in — as part of a deployment platform that replaces seven SaaS tools, Pingdom among them. See the monitoring docs to get started.
Get weekly updates