> pingtxt_
$ man pingtxt

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
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:

envdefault
PINGTXT_ADDR:8090listen address
PINGTXT_DATA/dataSQLite database directory
PINGTXT_DB_URLPostgreSQL 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.

postgresql
curl -O https://raw.githubusercontent.com/federico-it/pingtxt/main/docker-compose.postgres.yml
echo "DB_PASSWORD=$(openssl rand -hex 16)" > .env
docker compose -f docker-compose.postgres.yml up -d
mariadb
curl -O https://raw.githubusercontent.com/federico-it/pingtxt/main/docker-compose.mariadb.yml
printf "DB_PASSWORD=%s\nDB_ROOT_PASSWORD=%s\n" $(openssl rand -hex 16) $(openssl rand -hex 16) > .env
docker compose -f docker-compose.mariadb.yml up -d

With your own database server, create an empty database and a user, then:

env
PINGTXT_DB_URL=postgres://pingtxt:secret@db.example.com:5432/pingtxt?sslmode=require
PINGTXT_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.

postgresql backup / restore
docker exec pingtxt-postgres pg_dump -U pingtxt -Fc pingtxt > pingtxt.dump
docker stop pingtxt
docker exec -i pingtxt-postgres pg_restore -U pingtxt -d pingtxt --clean --if-exists < pingtxt.dump
docker start pingtxt
mariadb backup / restore
docker exec pingtxt-mariadb sh -c 'mariadb-dump -u pingtxt -p"$MARIADB_PASSWORD" --single-transaction pingtxt' > pingtxt.sql
docker stop pingtxt
docker exec -i pingtxt-mariadb sh -c 'mariadb -u pingtxt -p"$MARIADB_PASSWORD" pingtxt' < pingtxt.sql
docker 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.

Caddyfile
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.

# Docker
docker 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
# uninstall
curl -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 (or PINGTXT_NO_AUTO_UPDATE=1 in /etc/pingtxt-agent.env).
  • Docker agents never replace their binary: pull the new image or use Watchtower.

──check types

typetargetwhat is checked
http(s)https://example.comstatus codes, headers, body, TLS expiry
keywordurl + textthe response body contains (or not) a keyword
tcp porthost:portconnection opens
pinghostICMP echo (agents need NET_RAW)
dnsname + record typeresolves, optional expected value
postgresqlpostgres://user:pass@host:5432/dbconnect + query (default SELECT 1), optional expected value
mysql / mariadbmysql://user:pass@host:3306/dbsame as postgresql
redisredis://:pass@host:6379/0AUTH + PING, rediss:// for TLS
mongodbmongodb://user:pass@host:27017/hello, auth, ping; shows primary/secondary
docker containername or iddown when not running, restarting, paused or unhealthy
grpc healthgrpc://host:port[/service]grpc.health.v1 Check returns SERVING
smtpsmtp://host:587greeting, EHLO, STARTTLS when offered
heartbeatpush URLyour 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-login and 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.

prometheus.yml
scrape_configs:
- job_name: pingtxt
scheme: https
authorization:
credentials: ptk_...
static_configs:
- targets: ["status.example.com"]
metricmeaning
pingtxt_monitor_up1 up or degraded, 0 down
pingtxt_monitor_uptime_ratiouptime per window (24h, 7d, 30d)
pingtxt_check_latency_secondslast check per location
pingtxt_cert_expiry_daysdays before the certificate expires
pingtxt_domain_expiry_daysdays before the domain expires
pingtxt_agent_connectedagent 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 pingtxt
git checkout v1.2.3 # optional: a release tag
make web hub agent agents
targetoutput
make webinternal/ui/distthe web UI, embedded in the hub: run it before make hub
make hubdist/pingtxtthe hub for this machine, UI included
make agentdist/pingtxt-agentthe agent for this machine
make agentsdist/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.

run
./dist/pingtxt # data in ./data, agents from ./dist/agents, :8090
PINGTXT_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.

cross-compile
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/pingtxt
CGO_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
docker images (only docker needed)
make docker # pingtxt:dev and pingtxt-agent:dev
docker 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 --build
docker compose -f docker-compose.postgres.yml -f docker-compose.build.yml up -d --build
docker 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, run make test lint, open the PR against main. It is merged by a maintainer once the go and web checks pass and the review is approved.
dev setup
git clone https://github.com/<you>/pingtxt.git && cd pingtxt
(cd web && npm ci)
make dev-hub # hub on :8090
make 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.