lerko 92efb8e270
CI / test (pull_request) Successful in 2m37s
CI / lint (pull_request) Successful in 56s
CI / vulncheck (pull_request) Successful in 51s
fix(security): make SSH key revocation fail closed
keyCache.Invalidate existed but had zero callers, and refresh silently
swallowed store errors — a revoked key kept working off the stale
cache for as long as the DB stayed down.

Invalidate now clears the key set (not just the timestamp) and is
wired through userInvalidatingStore, a decorator at the composition
root that drops the cache on AddUser/UpdateUser/DeleteUser/ImportData.
Transient refresh errors still retain the previous key set so a DB
blip can't lock every admin out, but a post-revocation refresh failure
denies. Refresh errors are logged. First tests for the SSH auth gate.

Also suppresses per-request HTTP logging when the local TUI owns the
terminal — request logs scribbled over the alt screen.
2026-06-11 12:26:40 -04:00

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.

S
Description
Live uptime monitoring dashboard for your terminal. SSH-accessible. HTTP, ping, TCP, DNS, push checks with alerts, clustering, and Prometheus metrics.
Readme MIT 3.7 MiB
Languages
Go 99.6%
Dockerfile 0.3%