docs
Everything except two env vars is configured from the web UI and stored in the database. The README on GitHub has the same content.
──run the hub
docker run -d --name pingtxt --restart unless-stopped \-p 8090:8090 -v ./data:/data \ghcr.io/federico-it/pingtxt:latest
Or docker compose up -d with the docker-compose.yml in the repo. Open http://localhost:8090, create the admin account, add monitors. Binaries for Linux and macOS are on the releases page.
Public URL, built-in location, retention, defaults for new monitors, users and login live under settings. Only these are read at startup:
| env | default | |
|---|---|---|
PINGTXT_ADDR | :8090 | listen address |
PINGTXT_DATA | /data | SQLite database directory |
PINGTXT_DB_URL | PostgreSQL or MariaDB/MySQL instead of SQLite |
──postgresql or mariadb
SQLite is the default and needs nothing else. To keep the data in PostgreSQL (13+) or MariaDB (10.6+) / MySQL (8.0.13+), start the hub with PINGTXT_DB_URL. Tables are created and migrated at start. The compose files below start the hub and the database with one command; set the passwords in .env before the first start.
curl -O https://raw.githubusercontent.com/federico-it/pingtxt/main/docker-compose.postgres.ymlecho "DB_PASSWORD=$(openssl rand -hex 16)" > .envdocker compose -f docker-compose.postgres.yml up -d
curl -O https://raw.githubusercontent.com/federico-it/pingtxt/main/docker-compose.mariadb.ymlprintf "DB_PASSWORD=%s\nDB_ROOT_PASSWORD=%s\n" $(openssl rand -hex 16) $(openssl rand -hex 16) > .envdocker compose -f docker-compose.mariadb.yml up -d
With your own database server, create an empty database and a user, then:
PINGTXT_DB_URL=postgres://pingtxt:secret@db.example.com:5432/pingtxt?sslmode=requirePINGTXT_DB_URL=mariadb://pingtxt:secret@db.example.com:3306/pingtxt # mysql:// works too
Special characters in the password must be URL-encoded. History rollups, Prometheus metrics and the configuration export work the same on every database. The built-in backups in settings are SQLite files: with PostgreSQL or MariaDB back up the server with its own tools.
docker exec pingtxt-postgres pg_dump -U pingtxt -Fc pingtxt > pingtxt.dumpdocker stop pingtxtdocker exec -i pingtxt-postgres pg_restore -U pingtxt -d pingtxt --clean --if-exists < pingtxt.dumpdocker start pingtxt
docker exec pingtxt-mariadb sh -c 'mariadb-dump -u pingtxt -p"$MARIADB_PASSWORD" --single-transaction pingtxt' > pingtxt.sqldocker stop pingtxtdocker exec -i pingtxt-mariadb sh -c 'mariadb -u pingtxt -p"$MARIADB_PASSWORD" pingtxt' < pingtxt.sqldocker start pingtxt
To move an existing SQLite hub, export the configuration with secrets (settings → configuration export) and import it in the new hub. History stays behind.
──reverse proxy
Put the hub behind a reverse proxy with TLS (Caddy, Traefik, nginx) and set the public URL in settings, so login callbacks and agent commands use it. WebSocket upgrade must be allowed on /api/agent/ws.
status.example.com {reverse_proxy 127.0.0.1:8090}
──add a location (agent)
In the UI go to agents → add agent: you get a token and ready-to-paste commands.
# Dockerdocker run -d --name pingtxt-agent --restart unless-stopped --cap-add NET_RAW \-e PINGTXT_HUB=https://status.example.com -e PINGTXT_TOKEN=pt_xxx \ghcr.io/federico-it/pingtxt-agent:latest# Linux + systemd (binary downloaded from your hub)curl -fsSL https://status.example.com/install-agent.sh | sudo sh -s -- --hub https://status.example.com --token pt_xxx# uninstallcurl -fsSL https://status.example.com/install-agent.sh | sudo sh -s -- --uninstall
Then tick the new location in any monitor. The hub schedules every check and sends it to the agents; agents are stateless, open no ports and reconnect with backoff.
──agent updates
The hub bundles agent binaries built with its own version. Agents installed with the script update themselves when they reconnect after a hub upgrade: they download the binary from the hub, verify its SHA-256 and version, swap it and restart. A failed update keeps the old binary running.
- agents → update agents automatically turns it on or off; outdated agents also get an update button.
- Keep an agent on its version with
-no-auto-update(orPINGTXT_NO_AUTO_UPDATE=1in/etc/pingtxt-agent.env). - Docker agents never replace their binary: pull the new image or use Watchtower.
──check types
| type | target | what is checked |
|---|---|---|
| http(s) | https://example.com | status codes, headers, body, TLS expiry |
| keyword | url + text | the response body contains (or not) a keyword |
| tcp port | host:port | connection opens |
| ping | host | ICMP echo (agents need NET_RAW) |
| dns | name + record type | resolves, optional expected value |
| postgresql | postgres://user:pass@host:5432/db | connect + query (default SELECT 1), optional expected value |
| mysql / mariadb | mysql://user:pass@host:3306/db | same as postgresql |
| redis | redis://:pass@host:6379/0 | AUTH + PING, rediss:// for TLS |
| mongodb | mongodb://user:pass@host:27017/ | hello, auth, ping; shows primary/secondary |
| docker container | name or id | down when not running, restarting, paused or unhealthy |
| grpc health | grpc://host:port[/service] | grpc.health.v1 Check returns SERVING |
| smtp | smtp://host:587 | greeting, EHLO, STARTTLS when offered |
| heartbeat | push URL | your job calls the URL; down when it stops |
Passwords in connection URLs are hidden in the list and in notifications; use a read-only user. Docker checks need /var/run/docker.sock on the hub or agent that runs them (add -v /var/run/docker.sock:/var/run/docker.sock:ro).
──heartbeat monitors (cron jobs)
A heartbeat (push) monitor gets a secret URL. Call it at the end of every run, with any HTTP method:
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 https://status.example.com/api/push/<token> >/dev/null
It goes down when no call arrives within the expected period plus the grace time. Optional query parameters: status=down, msg=, ping= (run time in ms).
──status pages
Create them under status: pick monitors or whole groups, their order, and whether past incidents are shown. A page is public at https://<hub>/status/<slug>/. Visitors see names, status, uptime and incident times, never targets, locations or error messages.
For a custom domain (e.g. status.example.com), set it on the page, point its DNS at the hub and route the host to pingtxt in your reverse proxy. On that host only the status page is served.
──maintenance windows
One-off or recurring (daily, weekly, monthly, in any time zone), for some or all monitors, groups or tags. Checks keep running but no incidents or alerts are raised, and those checks don't count toward uptime.
──expiry alerts
TLS certificates of https monitors and domain registrations (RDAP, WHOIS fallback for registries such as .it). Thresholds in days are set in settings (default 14/7/3/1 for certificates, 30/14/7/1 for domains): one alert per threshold crossed, one on expiry, one on renewal.
──notifications
Webhook (JSON), Discord, Slack, Telegram, ntfy and email (SMTP with STARTTLS/TLS). Add them under notify, test them before saving, and attach them to monitors.
──single sign-on
In settings → authentication turn on GitHub, Google or generic OpenID Connect (Authentik, Keycloak, Authelia, Pocket ID, Zitadel, Okta, Entra ID...), paste the client id and secret and register the callback URL shown there.
- Logins are matched to existing users by verified email. New accounts can be allowed, optionally by email domain.
- Locked out after turning off password login? Run
docker exec pingtxt pingtxt -enable-password-loginand restart.
──two-factor authentication
In settings → two-factor authentication scan the QR code with any authenticator app. You get 10 single-use recovery codes. To reset a user: settings → users → reset 2fa, or docker exec pingtxt pingtxt -reset-2fa you@example.com.
──api tokens
Create personal tokens in settings → api tokens, read only or read + write, with an optional expiry. They are shown once and stored hashed.
curl -H "Authorization: Bearer ptk_..." https://status.example.com/api/monitors
GET /api/monitors accepts ?group=<id> and ?tag=<id>. Tokens never reach account endpoints (password, 2FA, tokens, users, instance settings).
──prometheus
GET /metrics needs a read token, unless settings → prometheus metrics → public is on. Output is cached for 10 seconds.
scrape_configs:- job_name: pingtxtscheme: httpsauthorization:credentials: ptk_...static_configs:- targets: ["status.example.com"]
| metric | meaning |
|---|---|
pingtxt_monitor_up | 1 up or degraded, 0 down |
pingtxt_monitor_uptime_ratio | uptime per window (24h, 7d, 30d) |
pingtxt_check_latency_seconds | last check per location |
pingtxt_cert_expiry_days | days before the certificate expires |
pingtxt_domain_expiry_days | days before the domain expires |
pingtxt_agent_connected | agent connectivity |
──upgrade + backup
docker compose pull && docker compose up -d
Database migrations run on start. With SQLite all state is one file, /data/pingtxt.db: settings → database backups takes copies while the hub runs, on demand or on a schedule, and restores them. With PostgreSQL or MariaDB use pg_dump or mariadb-dump. Agents installed with the script follow the hub version on their own.
──build from source
Needs Go 1.26+, Node.js 22+ with npm, make and git. No C compiler (everything builds with CGO_ENABLED=0).
git clone https://github.com/federico-it/pingtxt.git && cd pingtxtgit checkout v1.2.3 # optional: a release tagmake web hub agent agents
| target | output | |
|---|---|---|
make web | internal/ui/dist | the web UI, embedded in the hub: run it before make hub |
make hub | dist/pingtxt | the hub for this machine, UI included |
make agent | dist/pingtxt-agent | the agent for this machine |
make agents | dist/agents/ | agents for linux amd64/arm64/arm and darwin, served by the hub to the install script and for updates |
The version comes from git describe (override with VERSION=v1.2.3). Build hub and agents from the same checkout: agents update themselves to the hub version.
./dist/pingtxt # data in ./data, agents from ./dist/agents, :8090PINGTXT_DATA=/var/lib/pingtxt PINGTXT_AGENT_DIR=/usr/local/share/pingtxt/agents /usr/local/bin/pingtxt
Outside Docker, ping checks need raw sockets: sudo setcap cap_net_raw+ep /usr/local/bin/pingtxt (and the agent), or PINGTXT_UNPRIVILEGED_PING=1 with net.ipv4.ping_group_range. A systemd unit example is in the README.
V=$(git describe --tags --always)CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -trimpath -ldflags "-s -w -X main.version=$V" -o dist/pingtxt-linux-arm64 ./cmd/pingtxtCGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -trimpath -ldflags "-s -w -X main.version=$V" -o dist/pingtxt-agent-linux-armv7 ./cmd/pingtxt-agent
make docker # pingtxt:dev and pingtxt-agent:devdocker build --build-arg VERSION=$(git describe --tags --always) -t pingtxt:dev .docker build --build-arg VERSION=$(git describe --tags --always) -f Dockerfile.agent -t pingtxt-agent:dev .docker buildx build --platform linux/amd64,linux/arm64 -t registry.example.com/pingtxt:dev --push .
To run a compose file with your own build, add docker-compose.build.yml after it. Same for SQLite, PostgreSQL and MariaDB (create the .env with the passwords first):
docker compose -f docker-compose.yml -f docker-compose.build.yml up -d --builddocker compose -f docker-compose.postgres.yml -f docker-compose.build.yml up -d --builddocker compose -f docker-compose.mariadb.yml -f docker-compose.build.yml up -d --build
──contributing
pingtxt is free software under the AGPL-3.0 license: use it, self-host it and change it freely; if you distribute a modified version or run one as a service for others, publish its source under the same license. Bug reports, fixes and new check types or notifiers are welcome.
- bugs: search the issues first, then open a bug report with the version (shown in settings), how you run it, steps and logs.
- ideas: open a feature request. For bigger changes, agree on the approach in an issue before writing code.
- security: never in a public issue. Report it privately, see SECURITY.md.
- pull requests: fork, branch from
main, runmake test lint, open the PR againstmain. It is merged by a maintainer once thegoandwebchecks pass and the review is approved.
git clone https://github.com/<you>/pingtxt.git && cd pingtxt(cd web && npm ci)make dev-hub # hub on :8090make dev-web # UI on :3000, proxies /api
Everything else, commit style and code style included, is in CONTRIBUTING.md. Everyone taking part follows the code of conduct.