Install the daemon
A single static Go binary on a Linux box, verified before it runs, under systemd, with its data in one directory of SQLite files.
Download and verify
Get the binary for your architecture from the download table. Every release carries an SSH signature over a signed provenance file that names exactly what was built. Check the SHA-256 against the table, then verify the signature against the release public key,
pingtower-release.pub.sha256sum pingtower-linux-amd64 # compare with the table printf '[email protected] namespaces="pingtower-release" %s\n' \ "$(cut -d' ' -f1,2 pingtower-release.pub)" > allowed_signers ssh-keygen -Y verify -f allowed_signers -I [email protected] \ -n pingtower-release -s pingtower-linux-amd64.release.sig \ < pingtower-linux-amd64.release cat pingtower-linux-amd64.release # sha256= must match the sum aboveNeeds stock OpenSSH 8.2 or later. If any check fails, do not run the binary.
Install the binary and a user
sudo install -m 0755 pingtower-linux-amd64 /usr/local/bin/pingtower sudo useradd --system --home /var/lib/pingtower --shell /usr/sbin/nologin pingtower sudo mkdir -p /var/lib/pingtower sudo chown pingtower:pingtower /var/lib/pingtowerCreate a systemd unit
/etc/systemd/system/pingtower.service:[Unit] Description=pingtower After=network-online.target Wants=network-online.target [Service] Type=simple User=pingtower Group=pingtower ExecStart=/usr/local/bin/pingtower \ -listen 127.0.0.1:8391 \ -data-dir /var/lib/pingtower Restart=on-failure RestartSec=2 TimeoutStopSec=20 LimitNOFILE=65536 UMask=0077 NoNewPrivileges=yes ProtectSystem=strict ReadWritePaths=/var/lib/pingtower ProtectHome=yes PrivateTmp=yes [Install] WantedBy=multi-user.targetsudo systemctl daemon-reload sudo systemctl enable --now pingtower curl -s http://127.0.0.1:8391/healthzPut TLS in front
The daemon speaks plain HTTP on localhost and expects a reverse proxy to terminate TLS. See Reverse proxy & TLS.
First account, first tenant
curl -s https://alerts.example.com/v1/signup \ -H 'Content-Type: application/json' \ -d '{"email":"[email protected]","password":"…"}' # → {"account":{…},"token":"pts_…"} curl -s https://alerts.example.com/v1/tenants \ -H "Authorization: Bearer pts_…" \ -H 'Content-Type: application/json' \ -d '{"name":"Acme"}'From here the quickstart applies unchanged, with your domain in place of api.pingtower.com.
Useful flags
pingtower -h lists everything. The ones most boxes set:
| Flag | Does |
|---|---|
-listen | Address to serve on. Keep it on localhost behind the proxy. |
-data-dir | Where control.db and tenants/<id>/pingtower.db live. One directory, back it up as a unit. |
-retain-days | How long retained log rows are kept. |
-max-logs-per-source | A per-source cap on retained rows. |
-backup-dir | Where periodic SQLite backups are written. |
-secret-key | Seals integration credentials at rest with AES-256-GCM. Set it before creating any integration. |
Storage
One control.db holds accounts, tenants, memberships, sessions, API keys and invites. Each tenant gets its own tenants/<tenant_id>/pingtower.db with that tenant’s sources, templates, retained logs, alerts and project configs. Tenant isolation falls out of which file was opened, chosen by the caller’s credential. SQLite runs in WAL mode with a single writer per file, so the data directory copies cleanly from a backup.
Upgrading
Replace the binary and restart the unit. Schema migrations run at startup. Read the release notes on the blog first; a release that needs anything more says so there.