Skip to main content
Glama

OpenUptime

OpenUptime

Open-source uptime monitoring and status pages. Runs on Cloudflare Workers with Postgres, or in one Docker container.

Hosted version · Docs · Reference · MIT license

Features

Monitoring

  • HTTP / HTTPS with keyword matching and slow-response alerts

  • TCP, DNS and WebSocket

  • Heartbeat (push) monitors for cron jobs and background workers

  • PostgreSQL, MySQL and Redis

  • Domain expiry

  • EVM and Solana RPC node health

  • MCP server checks

  • Multi-region checks, with your own probes

  • Monitor dependencies, so a downstream outage doesn't page you twice

Incidents and status pages

  • Incidents with acknowledgement, assignment and postmortems

  • Maintenance windows, one-off or recurring

  • Public status pages

  • Email and RSS subscriptions for status page visitors

  • Status badges and embeds for your site or README

Alerts

  • Slack, Discord, Microsoft Teams, Telegram, Lark / Feishu and ntfy

  • PagerDuty, webhooks and email

  • Push notifications to the iOS app

  • Repeat reminders and escalation

Teams and automation

  • Workspaces, projects and roles

  • GitHub sign-in and two-factor authentication

  • REST API with API keys

  • MCP server so your AI agent can manage monitors, incidents and status pages

  • JSON import / export

Part

Stack

apps/api

Hono, better-auth, Cloudflare Worker (cron + queue consumer)

apps/web

Astro (static pages, SSR for status pages) + React islands

packages/repo

Storage interface (Repo) and entity types

packages/db-pg

Postgres implementation of Repo: Drizzle schema, migrations

Related MCP server: DevHelm MCP Server

Deploy to Cloudflare

The site is two Workers on one domain:

Worker

Serves

Also does

openuptime-api

your-domain/api/* (a Worker route on your zone)

minute cron, queue consumer that runs the checks

openuptime-web

everything else on your-domain (a Worker custom domain)

status pages (SSR); reaches the API through a service binding

Keeping the API under /api/* of the same domain means session cookies stay same-origin. You need no separate API hostname.

Before you start

  • A Cloudflare account and a domain whose DNS is on Cloudflare (the zone).

  • A Postgres database reachable from the internet (Neon, Supabase, RDS, your own). Workers connect through Hyperdrive.

  • Node.js 22+ and pnpm (corepack enable provides the pinned version).

  • Workers Paid plan for the Queue (default). On the Free plan, run in inline mode instead: see Reference.

1. Get the code and sign in

git clone https://github.com/becoolme/openuptime.git && cd openuptime
pnpm install
cd apps/api && npx wrangler login

All npx wrangler … commands below run in apps/api unless noted.

2. Create the Queue and Hyperdrive

npx wrangler queues create openuptime-checks
npx wrangler hyperdrive create openuptime-prod --connection-string="postgres://user:pass@host:5432/db"

The second command prints an id. In apps/api/wrangler.jsonc uncomment the hyperdrive line and paste it:

"hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<id printed above>" }],

The commented placement line is optional (it runs the Worker next to your database host); leave it commented unless you want that.

3. Create the schema

From the repository root:

DATABASE_URL="postgres://user:pass@host:5432/db" pnpm db:migrate

It only applies what is new, so run it again after every upgrade. It never issues CREATE SCHEMA, so a role without database-level CREATE works too.

4. Point the config at your domain

Edit two files. In both, replace status.example.com with your own domain.

apps/api/wrangler.jsonc, where zone_name is the zone (the apex domain) even when you serve from a subdomain. Uncomment routes and set:

"routes": [{ "pattern": "status.example.com/api/*", "zone_name": "example.com" }],
"vars": {
  "BETTER_AUTH_URL": "https://status.example.com",   // public origin: cookies, OAuth, email links
  "WEB_ORIGIN": "https://status.example.com",        // same value
  "EMAIL_FROM": "alerts@example.com"                 // sender address; optional, see below
}

Email is optional: without it invitations and alerts are written to the Worker log. To send it, onboard your domain under Email Sending in the Cloudflare dashboard first (see Reference).

apps/web/wrangler.jsonc:

"routes": [{ "pattern": "status.example.com", "custom_domain": true }],
"vars": { "PRODUCT_URL": "https://status.example.com" }   // target of the "Powered by" link on status pages

The custom domain creates its DNS record and certificate for you; the hostname must not already have a DNS record pointing elsewhere.

No domain on Cloudflare? Leave routes out of both files and set BETTER_AUTH_URL and WEB_ORIGIN to the web Worker's https://openuptime-web.<your-subdomain>.workers.dev address. The web Worker then forwards /api/* to the API Worker through the service binding, so cookies stay same-origin.

5. Set the secrets

npx wrangler secret put BETTER_AUTH_SECRET     # openssl rand -hex 32
npx wrangler secret put CRON_SECRET            # any long random string; guards /internal/*

If wrangler asks to create the Worker first, answer yes. Optional: GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET add "Sign in with GitHub".

6. Deploy

pnpm exec wrangler deploy                     # in apps/api
cd ../web && pnpm build && pnpm exec wrangler deploy

Deploy the API first: the web Worker binds to it by name.

7. Check it

curl https://status.example.com/api/health    # {"ok":true}

Open the domain and sign up. Once your own account exists you can set "DISABLE_SIGNUP": "true" in the API vars and deploy again to close registration (invited people can still join).

Upgrade later: pull, pnpm install, pnpm db:migrate, then deploy the API and the web Worker, in that order.

Self-host with Docker

One container runs the API, the web app and the check scheduler; Postgres comes with the compose file. Either run the published image or build it from source; the settings are the same.

Option A: run the published image

Tagged releases are published to ghcr.io/becoolme/openuptime for linux/amd64 and linux/arm64. You need no clone and no build:

mkdir openuptime && cd openuptime
curl -fsSLO https://raw.githubusercontent.com/becoolme/openuptime/main/docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/becoolme/openuptime/main/.env.docker.example -o .env

Edit .env: set POSTGRES_PASSWORD and BETTER_AUTH_SECRET (openssl rand -hex 32), and uncomment OPENUPTIME_IMAGE. Pin a version (ghcr.io/becoolme/openuptime:0.1.0) or follow the newest release (:latest). Then:

docker compose pull && docker compose up -d     # http://localhost:8080

Upgrade: change the tag if you pinned one, then run the same two commands again. Migrations run on start.

Option B: build from source

git clone https://github.com/becoolme/openuptime.git && cd openuptime
cp .env.docker.example .env      # set POSTGRES_PASSWORD and BETTER_AUTH_SECRET (openssl rand -hex 32)
docker compose up -d --build     # http://localhost:8080

Upgrade: git pull, then docker compose up -d --build.

Either way

The schema is migrated on start and the data lives in the pgdata volume. Set PUBLIC_URL when the app is not served from http://localhost:8080. Email is optional; see Reference.

Once your own account exists, set DISABLE_SIGNUP=true in .env and run docker compose up -d to close registration (invited people can still join).

Develop

pnpm install
cp .env.example apps/api/.dev.vars   # edit DATABASE_URL and secrets
DATABASE_URL=postgres://user:pass@localhost:5432/openuptime_dev pnpm db:migrate   # same URL as in .dev.vars
pnpm dev:api    # http://localhost:8787  (checks run inline every 15s)
pnpm dev:web    # http://localhost:4321  (proxies /api to the API)
pnpm typecheck

MCP server

Agents (Claude, Cursor, Codex and any MCP client) can manage monitors, incidents and status pages over Streamable HTTP at /api/mcp, authorizing with OAuth or an API key:

claude mcp add --transport http openuptime https://openuptime.app/api/mcp   # or https://<your-host>/api/mcp

Client setup, scopes and the tool list are in the MCP docs. An agent skill that teaches the common workflows ships in skills/openuptime: npx skills add becoolme/openuptime. The hosted server is listed in the MCP Registry as app.openuptime/mcp; server.json is its entry.

Tests, how checks run, the MCP server, alert channels and the other details are in docs/reference.md.

Open source vs hosted

The monitoring itself is the same code in both. The hosted service at openuptime.app adds plans, billing and a few features that live only in that build; it plugs into this repo through one interface (Edition, see docs/editions.md), so nothing in the core is locked or throttled.

Self-hosted (this repo, MIT)

Hosted (openuptime.app)

Monitors, incidents, status pages, alert channels, API, MCP

All of it

All of it

Limits

None: no plans, quotas or metering

Free plan, paid plans from $5/month (pricing); monitors, checks, emails and history length depend on the plan

Running it

You: Postgres, the Worker or container, a mail provider, backups, upgrades

We do; nothing to install

SSL certificate monitoring (expiry alerts, ssl monitor type)

Not included

Lite and above

Custom domain for a status page

Not included

Every paid plan (Lite and above), plus early-access workspaces

Where your data lives

Your database

Ours (Postgres on managed hosting)

Pick self-hosting if you want to own the data and the infrastructure, or have no limits to worry about. Pick hosted if you would rather not run anything. Both receive the same core updates.

Contributing & security

See CONTRIBUTING.md. Report vulnerabilities privately as described in SECURITY.md, not in public issues.

License

MIT

Available Tools

26 tools
acknowledge_incidentAInspect

Assign an existing incident to the authenticated caller and return the incident record. Use this when taking ownership of an investigation; it does not resolve the incident or post a subscriber update. Use post_incident_update to communicate status, and list_incidents to discover the incident UUID.

Inputs: id (required), project, workspace.

create_incidentAInspect

Open a manual incident in the selected project and return its record and UUID. Use this for an event not already represented by an automatic incident; use post_incident_update for an existing incident. A monitorId limits it to pages containing that monitor; otherwise it applies to project status pages. The optional body becomes the first investigating update, public unless visibility is internal. Creation assigns the caller; it does not send subscriber update emails.

Inputs: title (required), body, project, severity, +3 more.

create_monitorAInspect

Create and start a monitor in the selected project; returns its record and UUID (heartbeat monitors include a ping token). Use list_regions and list_channels to discover optional region/channel ids. Heartbeats wait for pings; other types schedule outgoing checks. Auto-enabled alert channels are added, and failures can open incidents and notify channels. Hosted plans may reject capacity, frequency or region choices; private network targets are refused by default.

Inputs: name (required), tags, type, method, +18 more.

create_projectAInspect

Create a project inside the selected workspace and return its record and id. Use this to separate monitors within an existing workspace; use create_workspace for a separate team. Requires access to all projects in that workspace. The new project is immediately usable through the project argument.

Inputs: name (required), project, workspace.

create_status_pageAInspect

Publish a status page at /status/ and return its record and UUID. Use list_monitors to select components and an unused slug (a conflict returns an error); omitted monitorIds creates an empty page. The page and selected monitors' public status become readable without authentication. Use update_status_page to edit an existing page; branding options remain subject to the hosted plan.

Inputs: slug (required), title (required), theme, project, +5 more.

create_workspaceAInspect

Create a workspace owned by the signed-in user, including its first project. Use create_project instead to organize an existing workspace. Requires an OAuth grant for all workspaces; a workspace-scoped API key cannot create one. Returns the new workspace record and id; use list_workspaces to discover its project.

Inputs: name (required), project, workspace.

delete_monitorA
DestructiveIdempotent
Inspect

Permanently remove a monitor and its check history; returns an empty success response. Use pause_monitor instead when you may need it again. Requires workspace owner/admin access in addition to write scope. The monitor cannot be recovered through this tool; deletion does not create an incident-resolution update.

Inputs: id (required), project, workspace.

get_incidentA
Read-onlyIdempotent
Inspect

Investigate one incident by its UUID from list_incidents. Returns its write-up, update timeline (including internal updates), related monitor/checks, assignee and notification deliveries. Use list_incidents to discover ids; use update_incident to edit the write-up and post_incident_update to change status. This is private team information, unlike list_public_status_page_incidents.

Inputs: id (required), project, workspace.

get_monitorA
Read-onlyIdempotent
Inspect

Read one monitor's configuration, recent check history and uptime statistics by its UUID from list_monitors. Use this to investigate a specific monitor; list_monitors is the summary view and run_check performs a new check. Returns the monitor plus history; secret HTTP headers are masked for callers without management access.

Inputs: id (required), project, workspace.

get_public_status_pageA
Read-onlyIdempotent
Inspect

Read the published health summary for a status-page slug without credentials. Returns overall/component status, 90-day uptime and active public incidents; use list_public_status_page_incidents for incident history or list_public_status_page_maintenance for downtime schedules. Only published information is returned; a missing page or one with MCP access disabled returns an error.

Inputs: slug (required).

list_channelsA
Read-onlyIdempotent
Inspect

Discover alert-channel UUIDs and delivery types (email, webhook, Slack and others) in the selected project. Use the ids with create_monitor/update_monitor.channelIds to route alerts. Returns channel records, not a delivery test. Channel creation and secret configuration are managed through the app or REST API.

Inputs: project, workspace.

list_incidentsA
Read-onlyIdempotent
Inspect

Find automatic and manually opened incidents visible in the selected project, with open incidents first. Returns incident summaries and UUIDs; use get_incident for the timeline, failed checks and investigation details. To change status or resolve an incident, use post_incident_update rather than update_incident.

Inputs: project, workspace.

list_maintenanceA
Read-onlyIdempotent
Inspect

Read maintenance windows in the selected project, including schedule and monitor associations. Use this authenticated list for planning; list_public_status_page_maintenance only shows windows for a published page slug. This tool does not schedule downtime; use the app or REST API to create or remove windows.

Inputs: project, workspace.

list_monitorsA
Read-onlyIdempotent
Inspect

Read monitor summaries for the selected project, including ids, current status, uptime and latency. Use this to find a monitor before changing it or to scan for outages; use get_monitor for recent check history. Secret HTTP header values are masked unless the caller may manage the workspace.

Inputs: project, workspace.

list_public_status_page_incidentsA
Read-onlyIdempotent
Inspect

Read recent public incidents and their public updates for a published status-page slug, newest first. Use get_public_status_page for current health, or authenticated get_incident for internal investigation details. No credentials are needed; internal updates are excluded and a missing page or one with MCP access disabled returns an error.

Inputs: slug (required).

list_public_status_page_maintenanceA
Read-onlyIdempotent
Inspect

Read scheduled and ongoing public maintenance windows for a published status-page slug without credentials. Use this to explain announced downtime; authenticated list_maintenance is the project-wide management view. Returns public window records; a missing page or one with MCP access disabled returns an error.

Inputs: slug (required).

list_regionsA
Read-onlyIdempotent
Inspect

Discover configured check locations and the maximum regions per monitor on this deployment. Use the returned ids with create_monitor/update_monitor.regions or run_check.region; do not guess ids. auto is always available. Explicit remote regions support HTTP/TCP only; hosted plans may restrict how many are used.

Inputs: project, workspace.

list_status_pagesA
Read-onlyIdempotent
Inspect

Read status-page records and UUIDs in the selected project for management. Use these ids with update_status_page; use get_public_status_page with a slug to read the published component/outage summary instead. This private list does not include pages outside the selected project.

Inputs: project, workspace.

list_workspacesA
Read-onlyIdempotent
Inspect

Discover the workspace and project ids this connection may use before calling private tools. Returns accessible workspaces with their projects; it does not list other users' workspaces. Pass workspace/project ids to private tools to select a scope, or omit them to use this connection's defaults.

Inputs: project, workspace.

pause_monitorAInspect

Stop scheduled checks and alerts for one monitor, preserving its configuration and history; returns the paused monitor. Use this for a pause-only change, or update_monitor to combine a pause with configuration edits. For planned downtime that should still run checks, schedule maintenance through the app or REST API instead. Use resume_monitor to restart checking.

Inputs: id (required), project, workspace.

post_incident_updateAInspect

Append an incident timeline update and change its status; returns the new update record. Use this to communicate progress or resolve an incident, rather than update_incident (write-up only). Public updates appear on status pages and are emailed to subscribers; internal updates remain team-only and send no subscriber email. resolved closes the incident and must be public. Repeating a call creates another update and may send another email.

Inputs: id (required), body (required), status (required), project, +2 more.

resume_monitorAInspect

Restart checking a paused monitor and return its record with pending status. Use this for a resume-only change; use update_monitor for combined configuration edits. Its next check becomes due immediately and may create incidents or send alerts. Hosted active-monitor limits are checked again; existing history is retained.

Inputs: id (required), project, workspace.

run_checkAInspect

Check a monitor immediately. Without region, records the result through the normal incident/alert workflow and returns {ok:true}; it can notify channels. With region from list_regions, returns {ok:true,region,result} for an unrecorded spot check without changing monitor history/status. Use get_monitor to read existing checks. Heartbeats have no outgoing spot check; hosted check credits apply and probe/network errors are returned.

Inputs: id (required), region, project, workspace.

update_incidentAInspect

Edit an incident's root cause or Markdown postmortem and return the updated record. Use post_incident_update instead to change status or notify subscribers. Omitted fields stay unchanged; null or blank text clears a write-up field. Write-ups remain private unless publishPostmortem is true; a published postmortem appears on public status pages after resolution, and false hides it again.

Inputs: id (required), project, rootCause, workspace, +2 more.

update_monitorAInspect

Patch an existing monitor and return its updated record. Read get_monitor first, then send id and at least one changed field; omitted fields stay unchanged and type cannot change. Lists and headers replace their previous values, while documented nulls clear optional settings. Prefer pause_monitor/resume_monitor for a pause-only change; use this for configuration changes or a combined edit. Resuming sets status to pending; heartbeat interval/grace changes recalculate its deadline. New settings affect later checks and alerts; hosted plan limits and target restrictions still apply.

Inputs: id (required), name, tags, method, +19 more.

update_status_pageA
Idempotent
Inspect

Patch an existing status page by its UUID from list_status_pages; returns {ok:true}. Use this to edit presentation or selected components; use get_public_status_page to read the public result. Omitted fields stay unchanged; monitorIds replaces the complete component list and [] clears it. Changes affect the published page; an empty externalUrl removes its header link. Hosted branding restrictions still apply.

Inputs: id (required), theme, title, project, +5 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.2/5.0

    Scored across 26 tools

    Disambiguation4/5

    Most tools have clearly separated purposes, especially monitor lifecycle, incident lifecycle, and public vs private status-page reads. A few boundaries could still be confused, such as update_incident versus post_incident_update, or list_incidents versus list_public_status_page_incidents, but descriptions explicitly clarify them.

    Naming Consistency5/5

    Names are consistently snake_case and generally follow a verb_noun pattern: list_monitors, get_monitor, create_monitor, update_monitor, pause_monitor, resume_monitor, delete_monitor. Public and private variants are named predictably, with no mixed casing or chaotic verb styles.

    Tool Count3/5

    The domain is broad—workspaces, projects, monitors, incidents, status pages, maintenance, channels, and regions—so many operations are justified. However, 26 tools is heavy and includes several public/private read variants, making the surface borderline overgrown.

    Completeness3/5

    Core monitor and incident workflows are well covered, but several resources are only partially managed: maintenance windows and alert channels are list-only, and projects/workspaces/status pages lack full CRUD. These gaps are notable because agents cannot schedule maintenance or configure channels through this MCP server.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      MCP server for Uptrack uptime monitoring. Manage monitors and incidents from AI agents like Claude, ChatGPT, and Cursor.
      13 npm
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server for Uptimepage uptime monitoring. An LLM client can read your monitors and incidents, run a check on demand, and post incident updates. Writes need an OAuth login and a scoped token, and each one is logged.
      54
      AGPL 3.0
    • A
      license
      Not graded
      quality
      D
      maintenance
      Official Hyperping MCP server for uptime, API, cron and server monitoring. 26 tools covering monitors, outages and timelines, uptime, response time, MTTR and MTTA, on-call schedules and escalation policies, over a remote Streamable HTTP endpoint with Bearer token auth and no install.
      1
      MIT