# How Uptime Kuma Exposes Prometheus Metrics: A Complete Technical Guide

> Learn how Uptime Kuma exposes Prometheus metrics via its built-in exporter. Discover the /metrics endpoint and understand monitor gauges in this technical guide.

- Repository: [Louis Lam/uptime-kuma](https://github.com/louislam/uptime-kuma)
- Tags: deep-dive
- Published: 2026-02-28

---

**Uptime Kuma exposes Prometheus metrics through a built-in exporter in [`server/prometheus.js`](https://github.com/louislam/uptime-kuma/blob/main/server/prometheus.js) that registers gauges for each monitor and serves them via a basic-auth protected `/metrics` endpoint using the `prometheus-api-metrics` middleware.**

Uptime Kuma ships with a native Prometheus exporter that transforms every monitor’s runtime data into labeled time-series metrics. This implementation leverages the `prom-client` library and integrates directly into the Express server architecture, allowing any Prometheus server to scrape monitoring data for alerting and dashboarding.

## Architecture Overview

The exporter consists of four coordinated components that handle metric definition, endpoint exposure, per-monitor data updates, and push-based monitor support. The `Prometheus` class in [`server/prometheus.js`](https://github.com/louislam/uptime-kuma/blob/main/server/prometheus.js) acts as the central registry, while [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js) mounts the scrape endpoint. Each monitor instance maintains its own `Prometheus` helper that updates gauges on every heartbeat.

## Core Components

### Metric Definitions in [`server/prometheus.js`](https://github.com/louislam/uptime-kuma/blob/main/server/prometheus.js)

The `Prometheus` class initializes all gauge families in its constructor or `init()` method. It registers six primary gauges:

- `monitor_cert_days_remaining` – Days until TLS certificate expiry
- `monitor_cert_is_valid` – Boolean validity of TLS certificates
- `monitor_uptime_ratio` – Calculated uptime percentage over sliding windows (1d, 7d, 30d, 90d)
- `monitor_response_time_seconds` – Response time in seconds
- `monitor_response_time` – Response time in milliseconds
- `monitor_status` – Current monitor status (1 = up, 0 = down)

All gauges share a common label set defined at startup: `monitor_id`, `monitor_name`, `monitor_type`, `monitor_url`, `monitor_hostname`, `monitor_port`, plus every tag name existing in the database. The `sanitizeForPrometheus()` method strips illegal characters from tag names and values, keeping only alphanumerics and underscores.

### The `/metrics` Endpoint in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js)

The scrape endpoint is registered in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js):

```javascript
app.get("/metrics", apiAuth, prometheusAPIMetrics())

```

The route uses `apiAuth` middleware, which enforces basic authentication using the first Uptime Kuma user’s credentials. The `prometheus-api-metrics` middleware reads the `prom-client` registry and renders the metrics in Prometheus text exposition format (`text/plain; version=0.0.4`).

### Per-Monitor Updates in [`server/model/monitor.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/monitor.js)

Each monitor instantiates a `Prometheus` helper during initialization:

```javascript
// In server/model/monitor.js constructor
this.prometheus = new Prometheus(this, await this.getTags());

```

On every heartbeat, the monitor calls:

```javascript
this.prometheus.update(bean, tlsInfo, uptime);

```

The `update()` method extracts TLS certificate information, response times, and uptime window calculations, then invokes `.set()` on each gauge with the monitor’s pre-calculated label values. Errors during update are caught and logged to prevent a single monitor from crashing the exporter.

### Push Monitor Support in [`server/routers/api-router.js`](https://github.com/louislam/uptime-kuma/blob/main/server/routers/api-router.js)

For push-type monitors, the API router handles inbound heartbeats at `/api/push/:monitorID`. When a heartbeat is received, the router creates a temporary `Prometheus` instance:

```javascript
new Prometheus(monitor, []).update(bean, undefined);

```

This updates the gauges immediately without requiring the monitor to maintain a persistent connection, ensuring push monitors appear in the metrics endpoint alongside polled monitors.

## How Metrics Are Collected and Updated

The data flow follows this sequence:

1. **Startup**: `Prometheus.init()` registers gauge families and collects all tag names from the database to define the complete label schema.
2. **Monitor Creation**: Each monitor constructs a `Prometheus` instance with its static labels (ID, name, type, URL, hostname, port) and dynamic tag values.
3. **Heartbeat Processing**: On every check (or push), the monitor calls `prometheus.update()`, which sets gauge values for response time, status, certificate validity, and uptime ratios.
4. **Scrape**: Prometheus server queries `GET /metrics`, triggering `prometheus-api-metrics` to serialize the registry into text format.

**Important**: Because tag names are collected once at startup, adding new tags via the UI requires restarting Uptime Kuma to expose them as metric labels.

## Accessing the Metrics Endpoint

To scrape metrics, authenticate using the first user’s credentials created during setup:

```bash
curl -u admin:yourPassword http://localhost:3001/metrics

```

The endpoint returns standard Prometheus exposition format:

```

# HELP monitor_uptime_ratio Uptime ratio calculated over sliding window

# TYPE monitor_uptime_ratio gauge

monitor_uptime_ratio{monitor_id="1",monitor_name="Production API",monitor_type="http",monitor_url="https://api.example.com",tag_env="production",window="1d"} 0.9987
monitor_uptime_ratio{monitor_id="1",monitor_name="Production API",monitor_type="http",monitor_url="https://api.example.com",tag_env="production",window="30d"} 0.9854

```

## Summary

- **Uptime Kuma exposes Prometheus metrics** via a built-in exporter using the `prom-client` library and `prometheus-api-metrics` middleware.
- **Metric definitions** reside in [`server/prometheus.js`](https://github.com/louislam/uptime-kuma/blob/main/server/prometheus.js), where gauges for response time, uptime ratios, certificate validity, and status are registered with a comprehensive label set including monitor fields and tags.
- **Data updates** occur in [`server/model/monitor.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/monitor.js), where each monitor maintains a `Prometheus` instance that updates gauges on every heartbeat.
- **The `/metrics` endpoint** is mounted in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js) with basic authentication (`apiAuth`), allowing Prometheus to scrape metrics in standard text format.
- **Push monitors** are supported via [`server/routers/api-router.js`](https://github.com/louislam/uptime-kuma/blob/main/server/routers/api-router.js), which creates temporary `Prometheus` instances to update metrics when heartbeats are pushed to the API.

## Frequently Asked Questions

### What authentication is required to access the Prometheus metrics endpoint?

The `/metrics` endpoint uses the same basic authentication as the Uptime Kuma web interface. You must provide the username and password of the first user created in the system. The `apiAuth` middleware enforces this in [`server/server.js`](https://github.com/louislam/uptime-kuma/blob/main/server/server.js) before allowing access to the Prometheus exposition format.

### Which metrics are exposed by Uptime Kuma?

Uptime Kuma exposes six primary gauge metrics: `monitor_cert_days_remaining` for TLS expiry countdown, `monitor_cert_is_valid` for certificate validity, `monitor_uptime_ratio` for availability percentages over 1d/7d/30d/90d windows, `monitor_response_time_seconds` and `monitor_response_time` for latency in seconds and milliseconds, and `monitor_status` indicating up (1) or down (0) states.

### Why do new tags not appear in Prometheus metrics immediately?

Tag names are collected once during startup when `Prometheus.init()` runs in [`server/prometheus.js`](https://github.com/louislam/uptime-kuma/blob/main/server/prometheus.js). Because the label schema is defined at initialization, any tags added through the UI after the server has started will not appear as metric labels until you restart Uptime Kuma to re-initialize the Prometheus registry.

### How are push monitors handled differently from regular monitors?

Regular monitors update their Prometheus gauges automatically during the heartbeat processing loop in [`server/model/monitor.js`](https://github.com/louislam/uptime-kuma/blob/main/server/model/monitor.js). For push monitors, the API router in [`server/routers/api-router.js`](https://github.com/louislam/uptime-kuma/blob/main/server/routers/api-router.js) handles incoming heartbeats by instantiating a temporary `Prometheus` object with an empty tag array and calling `update()` directly, ensuring push-based checks still populate the metrics endpoint without requiring a persistent polling connection.