Files
uptop/README.md
T
lerko 809620340e
CI / test (pull_request) Successful in 2m36s
CI / lint (pull_request) Successful in 56s
CI / vulncheck (pull_request) Successful in 46s
fix(security): close XFF bypass and three secret-leak paths
Four fixes hardening the secrets and rate-limit posture a prior audit
left or that regressed:

X-Forwarded-For rate-limit bypass + memory DoS (ratelimit.go): clientIP
returned the raw XFF header, so an attacker rotating it minted unlimited
distinct limiter keys — never tripping the limit and growing the visitors
map without bound. XFF is now honored only when the immediate peer is a
configured trusted proxy (UPTOP_TRUSTED_PROXIES, CIDRs or bare IPs), using
the right-most non-trusted hop; otherwise the key is the real RemoteAddr.
The visitors map is bounded with LRU eviction as defense in depth.

Export redaction denylist -> per-provider allowlist (server.go): the old
six-key denylist missed the actual credentials — the webhook URL for
discord/slack/webhook/ntfy/gotify and api_key for opsgenie — exporting
them in the clear. redactByProvider keeps only known-safe keys per
provider type and redacts everything else, so unknown/new keys fail safe.

ImportData plaintext secrets (sqlstore.go): import inserted raw
json.Marshal(settings), bypassing the encryption AddAlert/UpdateAlert
use. It now routes through marshalSettings, so a restore with
UPTOP_ENCRYPTION_KEY set stores enc:-prefixed ciphertext, not plaintext.

Alert error credential leak (alert.go): provider Send returned the raw
*url.Error, whose URL carries the secret (Telegram bot token in the path,
webhook secrets in the URL); it was persisted to AlertHealth.LastError
and shown in the TUI. sanitizeError strips the URL, keeping the operation
and underlying cause.

Tests cover trusted/untrusted XFF + spoofed-bypass + map bound, the
allowlist per provider, encrypted-on-import round-trip, and URL-stripped
errors. README documents UPTOP_TRUSTED_PROXIES. Full suite green under
-race; golangci-lint clean.
2026-06-10 18:50:19 -04:00

6.5 KiB

uptop

Self-hosted uptime monitoring with a TUI over SSH.

No browser. No client install. Just ssh -p 23234 your-server.

CI MIT License Go 1.26 Docker Pulls

uptop monitors view

What is this

An uptime monitor you manage entirely from the terminal. It runs as a server, exposes an SSH endpoint, and drops you into a full TUI — monitors, alerts, logs, nodes, all there.

Built on RDGames/go-upkeep. Rewritten for clustering, config-as-code, and a proper dashboard.

Features

  • 6 check types — HTTP, Push (heartbeat), Ping, Port, DNS, Groups
  • 10 alert providers — Discord, Slack, Email, Ntfy, Webhook, Telegram, PagerDuty, Pushover, Gotify, Opsgenie
  • Config as code — define monitors in YAML, apply declaratively, version control your setup
  • HA clustering — leader/follower with automatic failover
  • Prometheus metrics/metrics endpoint, wire it straight to Grafana
  • Public status page — HTML + JSON, toggle with an env var
  • SQLite or Postgres — SQLite for single-node, Postgres for production
  • Uptime Kuma import — migrate from Kuma with one command

Screenshots

detail panel alerts view
logs view cluster nodes
theme selection

Quick start

go run cmd/uptop/main.go
ssh -p 23234 localhost

Want some data to look at first:

go run cmd/uptop/main.go -demo

Install

Docker (recommended)
services:
  uptop:
    image: lerkolabs/uptop:latest
    restart: unless-stopped
    ports:
      - "23234:23234"
      - "8080:8080"
    environment:
      - UPTOP_DB_TYPE=sqlite
      - UPTOP_DB_DSN=/data/uptop.db
      - UPTOP_STATUS_ENABLED=true
      # - UPTOP_ADMIN_KEY=ssh-ed25519 AAAA... you@host
    volumes:
      - ./data:/data

First run: set UPTOP_ADMIN_KEY to your SSH public key, or attach to the container and add it in the Users tab.

Binary (Linux amd64)

Download from Releases.

From source
go install gitea.lerkolabs.com/lerkolabs/uptop/cmd/uptop@latest

Upgrading: Pull the new image (or binary) and restart. Database migrations run automatically on startup.

Config as code

Export your current monitors:

uptop export -o monitors.yaml

Apply a config file:

uptop apply -f monitors.yaml
uptop apply -f monitors.yaml --dry-run   # see what would change
uptop apply -f monitors.yaml --prune     # delete anything not in the YAML

Full reference in docs/config-as-code.md.

Environment variables

Variable Default Description
UPTOP_PORT 23234 SSH server port
UPTOP_HTTP_PORT 8080 HTTP server port (status page, push, metrics)
UPTOP_DB_TYPE sqlite sqlite or postgres
UPTOP_DB_DSN uptop.db Database path or connection string
UPTOP_STATUS_ENABLED false Enable public status page
UPTOP_STATUS_TITLE System Status Status page title
UPTOP_ENCRYPTION_KEY AES-256-GCM key for alert credentials (details)
UPTOP_CLUSTER_MODE leader leader, follower, or probe
UPTOP_PEER_URL Leader URL for follower and probe nodes
UPTOP_CLUSTER_SECRET Shared key for cluster + API auth
UPTOP_INSECURE_SKIP_VERIFY false Skip TLS verification for checks
UPTOP_ALLOW_PRIVATE_TARGETS false Allow monitoring RFC1918/loopback addresses
UPTOP_ADMIN_KEY SSH public key seeded as first admin on startup
UPTOP_TRUSTED_PROXIES Comma-separated CIDRs/IPs whose X-Forwarded-For is trusted (details)

See .env.example for all options including TLS, probes, and advanced settings.

Running behind a reverse proxy

By default uptop ignores the X-Forwarded-For header and rate-limits by the direct connection address — so a client can't spoof the header to bypass limits. If uptop sits behind a reverse proxy (nginx, Caddy, Cloudflare, an ALB), set UPTOP_TRUSTED_PROXIES to the proxy's address(es) so the real client IP is used instead:

# single nginx/Caddy on the same host
UPTOP_TRUSTED_PROXIES=127.0.0.1

# a proxy subnet, or Cloudflare ranges
UPTOP_TRUSTED_PROXIES=10.0.0.0/8,172.16.0.0/12

Only requests whose immediate peer is in this list have their X-Forwarded-For honored (right-most non-trusted hop wins). Bare IPs are treated as single hosts; invalid entries are warned about and skipped. Leave it unset if uptop is exposed directly.

Encryption

Set UPTOP_ENCRYPTION_KEY to encrypt alert credentials (SMTP passwords, webhook URLs, API tokens) at rest with AES-256-GCM. Generate a key:

openssl rand -hex 32

Without this, credentials are stored as plaintext in the database. uptop warns on startup if unset. To encrypt credentials on an existing install, run uptop migrate-secrets with the key set.

Clustering

uptop supports three modes: leader (default single node), follower (HA failover — takes over if the leader goes down), and probe (stateless distributed checks from multiple regions).

See docs/clustering.md for setup guides, or the working examples in deploy/.

Migrating from Uptime Kuma

Export your Kuma backup JSON, then:

curl -X POST http://localhost:8080/api/import/kuma \
  -H "X-Upkeep-Secret: your-secret" \
  -H "Content-Type: application/json" \
  -d @kuma-backup.json

License

MIT — see LICENSE.