Uptrail documentation
Monitor the systems your product depends on
Configure service checks, cron heartbeats, incidents, and a public status page from one self-hosted control plane. This guide documents every exposed option and the tradeoffs behind it.
Getting started
Introduction
Uptrail checks services from the server where it is deployed. That detail matters: a target that is reachable from your laptop may not be reachable from the Uptrail process, container, or private network. Design checks around the path your production users and workloads actually take.
Service monitors
HTTP, MySQL, PostgreSQL, Redis, Nats JetStream, and raw TCP connectivity.
Cron health
Detect jobs that fail to start, stop sending heartbeats, or miss their schedule.
Incidents
Publish updates and resolved timelines to a stable, embeddable public page.
Quick start
- 1
Open the control plane
Sign in, create or select a workspace, then open Monitors. Workspace boundaries isolate monitors, incidents, settings, and members. - 2
Create a service group
Use a stable product or capability name such asCheckout API. A group rolls its child monitor states into one status-page service. - 3
Add the first monitor
Choose the protocol, enter its connection details, and keep the initial interval at 60 seconds. Start with three down retries to filter brief network noise. - 4
Run Check now
Trigger an immediate check. Confirm the response code, matched value, latency, and any error before relying on scheduled checks. - 5
Configure alert delivery
Add workspace notification destinations, then test a controlled failure. An alerting system is only complete after the delivery path is verified.
HTTP/1.1 200 OK
Content-Type: application/json
{
"status": "ok",
"dependencies": {
"database": "ok"
}
}Self-hosting
Uptrail consists of a static marketing site, a Vite-built React control plane, an Express API, a background monitor scheduler, WebSocket updates, and a MySQL application database. In production, run one authoritative scheduler process unless you have explicitly added distributed locking; duplicate schedulers can perform duplicate checks and alerts.
Production checklist
- 1
Provision MySQL
Create a dedicated database and least-privilege user. Back it up on the same schedule as other operational data. - 2
Build the client
Install dependencies and runnpm run build. SetVITE_API_BASE_URLbefore building only when the API lives on another origin. - 3
Set production secrets
At minimum setNODE_ENV=production, a randomSESSION_SECRET, database credentials,APP_BASE_URL, and the exactCORS_ORIGINS. - 4
Place behind TLS
Terminate HTTPS at a trusted reverse proxy, forward WebSocket upgrades for/ws, and setTRUST_PROXY=true. - 5
Verify network reachability
Allow the Uptrail host to reach every monitored host and port. Restrict inbound access to the UI/API and outbound access to only required targets where practical.
NODE_ENV=production
PORT=3001
APP_BASE_URL=https://uptime.example.com
CORS_ORIGINS=https://uptime.example.com
TRUST_PROXY=true
SESSION_SECRET=replace-with-at-least-32-random-bytes
MYSQL_HOST=mysql.internal
MYSQL_PORT=3306
MYSQL_USER=uptrail
MYSQL_PASSWORD=replace-me
MYSQL_DATABASE=uptrail
RESEND_API_KEY=re_...
EMAIL_FROM=Uptrail <[email protected]>Environment variables
Values are read when the server starts, except VITE_API_BASE_URL, which is compiled into the frontend at build time. Durations ending in _MS are milliseconds; monitor and cron form windows use seconds.
| Option | Default | How to use it |
|---|---|---|
| NODE_ENV | development | Runtime mode. Set to production for a deployed server. |
| PORT | 3001 | Port used by the API and production server. |
| APP_BASE_URL | first CORS origin | Canonical browser-facing URL used in links and authentication flows. |
| CORS_ORIGINS | http://localhost:3000,http://localhost:5173 | Comma-separated browser origins allowed to call the API. |
| TRUST_PROXY | true in production | Trust reverse-proxy headers. Enable behind a trusted TLS proxy. |
| SESSION_SECRET | change-me | Secret used to sign sessions. Always replace with a long random value. |
| SESSION_MAX_AGE_MS | 86400000 | Authenticated session lifetime in milliseconds (24 hours by default). |
| PASSWORD_RESET_TTL_MS | 3600000 | Password-reset link lifetime in milliseconds. |
| MYSQL_HOST | localhost | MySQL host for Uptrail's own application data. |
| MYSQL_PORT | 3306 | MySQL port for Uptrail's own application data. |
| MYSQL_USER | root | Application database user. |
| MYSQL_PASSWORD | empty | Application database password. |
| MYSQL_DATABASE | uptrail | Application database/schema name. |
| MYSQL_CONNECTION_LIMIT | 10 | Maximum connections in the application pool. |
| MONITOR_POLL_MS | 1000 | How frequently the scheduler looks for due monitor checks. |
| REQUEST_TIMEOUT_MS | 10000 | Global fallback timeout for service checks. |
| CRON_POLL_MS | 1000 | How frequently cron state is evaluated. |
| CRON_SWEEP_INTERVAL_MS | 30000 | Interval between scans for missed cron windows. |
| CRON_CATCHUP_GRACE_MS | 120000 | Grace period allowed while recovering overdue cron evaluations. |
| CRON_RUN_RETENTION_DAYS | 90 | Number of days cron-run history is retained. |
| GOOGLE_CLIENT_ID | empty | OAuth client ID. Leave empty to disable Google sign-in. |
| RESEND_API_KEY | empty | Resend API key used for transactional email. |
| EMAIL_FROM | Uptrail <[email protected]> | From identity for outgoing mail. |
| EMAIL_REPLY_TO | Uptrail <[email protected]> | Reply-to identity for outgoing mail. |
| CONTACT_EMAIL | [email protected] | Destination for contact and support messages. |
| VITE_API_BASE_URL | same origin | Build-time frontend API origin. Omit when UI and API share a host. |
Monitors
Monitor concepts and shared options
Every monitor belongs to a group and produces timestamped check runs. A check is successful only when the connection/probe completes and its configured assertion passes.
| Option | Default | How to use it |
|---|---|---|
| Group name | required | Customer-facing service grouping. Choose a durable capability name rather than a host name. |
| Monitor name | required | Operator-facing check name, for example Public API /health or Primary Redis PING. |
| Monitor type | HTTP | Selects the connection parser and probe runner. Type cannot be inferred from the URL. |
| Interval seconds | 60 | Time between scheduled checks. Use 30–60s for critical paths and longer intervals for expensive dependency probes. |
| Down retries | 3 | Consecutive failures required before changing to down. Higher values reduce noise but delay detection by roughly interval × retries. |
| Up retries | 1 | Consecutive successes required to recover. Increase when a dependency tends to flap during recovery. |
| Connection JSON | type-specific | Transport, credentials, TLS, and timeout settings used by non-HTTP monitors. |
| Probe command | type-specific | Command or query executed after connecting. Empty uses the safe default for that monitor type. |
| Expected probe value | type-specific | Assertion value. JSON-looking values are parsed, so use quoted JSON strings such as "ok". |
Retry timing example
With a 60-second interval and three down retries, a continuously failing service is normally marked down after the third failed check—approximately two to three minutes depending on where the failure begins relative to the schedule. Manual Check now is useful for diagnosis, but should not replace a realistic retry policy.
HTTP monitors
Use HTTP monitors for APIs, web applications, health endpoints, and authenticated synthetic requests. Uptrail sends the configured request and can assert both the status code and a value inside a JSON response.
| Option | Default | How to use it |
|---|---|---|
| URL | required | Absolute http:// or https:// target reachable from the Uptrail server. |
| Method | GET | GET, POST, PUT, PATCH, DELETE, HEAD, or OPTIONS. |
| Headers JSON | empty | JSON object of request headers. Use for Authorization, content negotiation, or tenant headers. |
| Body | empty | Raw request body. Usually JSON when Content-Type is application/json. |
| Expected status | 200 | Exact HTTP status required for success. |
| Expected JSON path | empty | Dot-separated path into a JSON response. Leave empty when status alone is sufficient. |
| Expected JSON value | empty | Value at the JSON path that must match. Configure together with JSON path. |
| connection_json.timeoutMs | 10000 | Per-monitor request timeout in milliseconds. |
URL: https://api.example.com/v1/health/deep
Method: POST
Headers JSON:
{
"Authorization": "Bearer <read-only-token>",
"Content-Type": "application/json"
}
Body:
{
"check": ["database", "queue"]
}
Expected status: 200
Expected JSON path: status
Expected JSON value: okMySQL monitors
A MySQL monitor opens a fresh connection, runs one query, compares the first returned scalar value when an expectation is configured, then closes the connection. The default query is SELECT 1 AS health.
| Option | Default | How to use it |
|---|---|---|
| host | 127.0.0.1 | Database host reachable from Uptrail. |
| port | 3306 | MySQL TCP port. |
| user | root | Use a dedicated account with only the permissions needed by the probe. |
| password | empty | Probe-account password. |
| database | optional | Database selected after connection. |
| connectTimeout | REQUEST_TIMEOUT_MS | Connection timeout in milliseconds. |
| probe command | SELECT 1 AS health | A read-only SQL statement. The first row/first column is the assertion value. |
| expected probe value | none | Optional JSON-compatible scalar such as 1, true, or "primary". |
Connection JSON:
{
"host": "mysql.internal",
"port": 3306,
"user": "uptrail_probe",
"password": "<secret>",
"database": "app_db",
"connectTimeout": 5000
}
Probe command:
SELECT IF(@@read_only = 0, 'primary', 'replica') AS role
Expected probe value:
"primary"PostgreSQL monitors
PostgreSQL checks follow the same assertion model as MySQL: connect, execute one query, inspect the first scalar value, and disconnect. The default probe is SELECT 1 AS health.
| Option | Default | How to use it |
|---|---|---|
| host | 127.0.0.1 | PostgreSQL host reachable from Uptrail. |
| port | 5432 | PostgreSQL TCP port. |
| user | postgres | Dedicated read-only probe role. |
| password | empty | Probe-role password. |
| database | optional | Database used for the session. |
| ssl | driver default | Set according to your pg client deployment and certificate policy. |
| connectionTimeoutMillis | REQUEST_TIMEOUT_MS | Connection timeout in milliseconds. |
| probe command | SELECT 1 AS health | Read-only SQL query; first row/first column is asserted. |
| expected probe value | none | Optional JSON-compatible expected scalar. |
Connection JSON:
{
"host": "postgres.internal",
"port": 5432,
"user": "uptrail_probe",
"password": "<secret>",
"database": "app_db",
"ssl": { "rejectUnauthorized": true },
"connectionTimeoutMillis": 5000
}
Probe command:
SELECT CASE WHEN pg_is_in_recovery() THEN 'replica' ELSE 'primary' END
Expected probe value:
"primary"Redis monitors
Redis monitors connect, issue one command, optionally assert the response, and close cleanly. The default command is PING and its default expectation is "PONG".
| Option | Default | How to use it |
|---|---|---|
| url | optional | Full Redis URL such as redis://user:password@host:6379/0. When present it replaces host-based fields. |
| host | 127.0.0.1 | Redis host used when url is omitted. |
| port | 6379 | Redis port used when url is omitted. |
| username | optional | ACL username. |
| password | optional | ACL or legacy password. |
| database | optional | Numeric logical database index. |
| probe command | PING | Whitespace command or a JSON array. Prefer JSON arrays when arguments contain spaces. |
| expected probe value | "PONG" for PING | Optional assertion. Leave blank for non-PING commands when execution alone proves health. |
# Simple whitespace form
GET uptime:sentinel
# Unambiguous JSON-array form
["SET", "uptime:last_probe", "healthy service", "EX", "120"]
# Typical read-only assertion
Probe command: GET app:deployment
Expected probe value: "2026.06.29"Nats JetStream monitors
Nats checks connect to one or more servers and execute a JetStream-aware probe. Use jetstream.info for general availability, stream.info for a critical stream, and consumer-lag probes when backlog growth is the operational risk.
Connection JSON
| Option | Default | How to use it |
|---|---|---|
| servers | [nats://127.0.0.1:4222] | String or array of Nats URLs. The client can fail over across the list. |
| server / url | optional aliases | Single-server alternatives to servers. |
| name | Uptrail | Optional client name visible to Nats operators. |
| user | optional | Username; username is also accepted. |
| pass | optional | Password; password is also accepted. |
| token | optional | Token authentication. Do not combine unrelated authentication schemes. |
| timeoutMs | REQUEST_TIMEOUT_MS | Nats connection timeout in milliseconds. |
| lag_thresholds | empty | Per-monitor object mapping consumer names and/or default to non-negative lag thresholds. |
Every supported probe command
| Probe command | What it checks | Expected value |
|---|---|---|
| jetstream.info | Connects to JetStream Manager and reads account information. Best general JetStream readiness check. | "ok" |
| stream.info:STREAM | Loads metadata for one stream and returns its configured name. Use the stream name as the expectation. | "STREAM" |
| consumers.lag | Enumerates consumers across all streams and fails when any consumer exceeds its effective lag threshold. | "ok" |
| consumers.lag:THRESHOLD | Same global scan with one command-level threshold for consumers without a stronger override. | "ok" |
| consumer.lag:STREAM:CONSUMER | Checks one durable consumer using configured/default threshold precedence. | "ok" |
| consumer.lag:STREAM:CONSUMER:THRESHOLD | Checks one consumer with an explicit non-negative command threshold. | "ok" |
Connection JSON:
{
"servers": [
"nats://nats-a.internal:4222",
"nats://nats-b.internal:4222"
],
"user": "uptrail_probe",
"pass": "<secret>",
"name": "uptrail-production",
"timeoutMs": 5000,
"lag_thresholds": {
"ORDERS_WORKER": 250,
"EMAIL_WORKER": 1000,
"default": 500
}
}
Probe command:
consumer.lag:ORDERS:ORDERS_WORKER:200
Expected probe value:
"ok"How consumer lag is evaluated
Uptrail evaluates pending acknowledgement count, waiting pull requests, pending delivery, and changes from the previous sample. A consumer is reported as lagging when its computed backlog exceeds the effective threshold. Threshold selection is deterministic:
- Consumer-specific key in the monitor’s
lag_thresholds. - Threshold appended to the probe command.
defaultin the monitor’slag_thresholds.- Built-in fallback of
128.
TCP port monitors
TCP monitors test whether a socket can be opened. They are ideal for basic reachability of protocols without a dedicated Uptrail probe, but they do not prove that the application protocol is healthy.
| Option | Default | How to use it |
|---|---|---|
| host | 127.0.0.1 | DNS name or IP reachable from Uptrail. |
| port | 443 | Required TCP port in the valid 1–65535 range. |
| timeoutMs | 5000 | Maximum connection time in milliseconds. |
| expected probe value | "open" | The TCP runner returns open after a successful socket connection. |
{
"host": "smtp.internal",
"port": 587,
"timeoutMs": 5000
}
Expected probe value: "open"Cron jobs
Cron monitoring
Cron monitoring tracks whether scheduled work starts and, optionally, continues to report progress. Enable cron monitoring at the workspace level, then define each job’s schedule, delivery mechanism, and timing windows.
| Option | Default | How to use it |
|---|---|---|
| Cron name | required | Stable unique identifier used in events and run history. |
| Expression | required | Cron schedule interpreted by the server. Keep server timezone in mind; production start sets TZ=UTC. |
| Service | apis | Groups jobs for status and bulk pause controls. |
| Endpoint | empty | HTTP destination when trigger type is HTTP. |
| Trigger type | Nats | How Uptrail invokes or signals the job: Nats publish or HTTP request. |
| Start window seconds | 60 | Time after the scheduled instant in which the run must begin. |
| Ping window seconds | 60 | Maximum allowed time between progress heartbeats for tracked runs. |
| Track run | true | Require ongoing pings after start. Disable for fire-and-forget or very short jobs. |
| Status | enabled | Disable a definition without deleting its configuration or history. |
HTTP cron triggers
HTTP cron triggers call an endpoint with GET or POST. Use GET only for an idempotent trigger; use POST when creating a run or sending a payload.
| Option | Default | How to use it |
|---|---|---|
| HTTP method | GET | GET or POST for HTTP triggers. |
| Endpoint | required | Absolute URL called at the scheduled time. |
| Headers JSON | empty | JSON object for Authorization, Content-Type, signatures, or routing. |
| Body | empty | Raw POST body. Ignored or unnecessary for most GET triggers. |
Trigger type: HTTP
Method: POST
Endpoint: https://jobs.example.com/v1/run/nightly-report
Headers JSON:
{
"Authorization": "Bearer <job-trigger-token>",
"Content-Type": "application/json"
}
Body:
{
"source": "uptrail",
"job": "nightly-report"
}Nats cron triggers
Nats triggers publish the cron payload to a subject. Configure the workspace connection first under Settings → Nats Cron Trigger; these credentials are separate from Nats service-monitor connection JSON.
| Option | Default | How to use it |
|---|---|---|
| Nats connection URL | required | Must begin with nats://. |
| Username | optional | Workspace Nats username. |
| Password | optional | Workspace Nats password. |
| Token | optional | Alternative token authentication. |
| Subject | crons.uptrail | Dot-delimited Nats subject; letters, numbers, underscore, and hyphen are accepted per token. |
# Shared worker with job name in payload
crons.uptrail
# Environment-scoped subjects
production.crons.billing
staging.crons.billingOperations
Incident management
Open an incident when users need context beyond a red monitor. The title should name the customer-visible symptom, while updates explain impact, investigation, mitigation, and recovery.
- 1
Open
Use a concise title such as “Elevated checkout failures” and include the observed impact in the first message. - 2
Update
Post material changes: confirmed scope, workaround, mitigation deployed, and monitoring results. Avoid low-information updates. - 3
Close
Add a resolution note stating what recovered and how it was verified. Closing moves the incident into the public history.
We identified elevated database lock contention affecting checkout writes.
A configuration change was deployed at 14:32 UTC. Error rates have returned
below 0.2%, and we are monitoring recovery before resolving the incident.The incident timeline records who opened, updated, and closed the incident. Editors can mutate incidents; viewers can inspect them. Press Escape or use the circular close control to dismiss the incident detail dialog without changing state.
Public status pages
The public incidents page exposes current health and incident history at a stable workspace URL. Open it from the Incidents header, copy the link, or copy the generated iframe snippet.
| Option | Default | How to use it |
|---|---|---|
| Public URL | workspace-specific | Share directly with customers or link from your support site. |
| Embed code | generated | Iframe using 100% width and 640px height. Adjust height to fit your host layout. |
| Logo | optional | Workspace branding shown on the public experience. |
| Footer text | optional | Support, ownership, or legal context shown below incident history. |
<iframe
src="https://uptime.example.com/incidents-public/YOUR_PUBLIC_ID"
width="100%"
height="640"
style="border:0"
title="Service status"
loading="lazy"
></iframe>Workspace
Notifications
Notification settings are workspace-scoped. Configure destinations for monitor state changes and cron failures, then test both failure and recovery messages. Email requires server-level Resend configuration; workspace settings determine recipients and event behavior.
Use a team-owned destination rather than an individual inbox. Route critical production alerts to a staffed operational channel, and use lower-noise destinations for non-production workspaces.
Teams, permissions, and audit history
Workspaces isolate configuration and membership. Owners and administrators should be limited to people who manage monitors, integrations, branding, and users. Editors can operate monitoring resources; viewers should receive read-only access when they only need visibility.
| Practice | Recommendation | Why |
|---|---|---|
| Least privilege | Use read-only target credentials and the lowest sufficient workspace role. | Limits blast radius if an account or stored secret is compromised. |
| Shared ownership | Keep at least two trusted workspace administrators. | Avoids operational lockout when one person is unavailable. |
| Audit review | Review the audit log after configuration and membership changes. | Creates accountability for operational changes. |
| Secret rotation | Rotate probe tokens and passwords on a defined schedule. | Reduces the lifetime of leaked credentials. |
| Network controls | Restrict Uptrail egress to monitored targets and required providers. | Prevents the monitor host from becoming a general network pivot. |
Was this documentation useful?
Tell us what is missing or unclear.