Install the daemon
A single static Go binary on a Linux box, verified before it runs, under systemd, with its data in PostgreSQL — or SQLite, with nothing else to install.
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
Settings that are secrets go in an environment file rather than the unit, which any local user can read.
/etc/pingtower/pingtower.env, owned by root, mode0600:PINGTOWER_SECRET_KEY=… # openssl rand -base64 48; back it up now PINGTOWER_CLIENT_IP_HEADER=X-Real-IP/etc/systemd/system/pingtower.service:[Unit] Description=pingtower After=network-online.target Wants=network-online.target [Service] Type=simple User=pingtower Group=pingtower EnvironmentFile=/etc/pingtower/pingtower.env 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 the daemon looks for config.yaml, and where SQLite keeps control.db and tenants/<id>/pingtower.db. |
-retain-days | How long retained log rows are kept. |
-max-logs-per-source | A per-source cap on retained rows. |
-backup-dir | Where periodic backups are written, readable by the daemon’s user alone. SQLite only; back up PostgreSQL as you would any server. |
-secret-key | Seals integration credentials, action headers and bodies, webhook signing secrets and the relay credential at rest with AES-256-GCM. Env PINGTOWER_SECRET_KEY. Use 32 or more random characters, such as openssl rand -base64 32 makes. Under 16 the daemon refuses to start with a FATAL line naming the flag; under 32 it starts with a warning. Set it before creating any integration. |
-client-ip-header | The header your reverse proxy sets to the real client address, such as X-Real-IP. Read only from loopback peers. Env PINGTOWER_CLIENT_IP_HEADER. See Reverse proxy. |
-log-reset-tokens | Writes password-reset tokens to the log for you to hand over, since a box sends no mail. On by default; -log-reset-tokens=false withholds them. Env PINGTOWER_LOG_RESET_TOKENS. |
--secret-key rather than serve tenants it cannot open. A different key opens none of the sealed secrets, and every signed webhook, integration and action fails until it is re-created. Keep the key with your backups.Database
The daemon takes its database from one setting, database.url, in config.yaml in the data directory (or the file --config or PINGTOWER_CONFIG names):
database:
url: postgres://pingtower:[email protected]/pingtower
- PostgreSQL (
postgres://orpostgresql://): the control plane and every tenant live on that server, each tenant in its own schema. A PostgreSQL URL never falls back to SQLite. - SQLite: with no file or no
url, the daemon keeps everything in the data directory, with nothing else to install.sqlite:///var/lib/pingtowernames that directory explicitly. - The URL is read from the file only — there is no flag or environment variable for it — so the password never appears in
ps. The daemon refuses aconfig.yamlthat others can read or rewrite; use mode0600or0640. pingtower pairwrites a SQLite data directory directly, so it refuses a PostgreSQL box and points you at the running daemon’s pairing flow instead.
Storage
Accounts, tenants, memberships, sessions, API keys and invites are kept apart from each tenant’s sources, templates, retained logs, alerts and project configs. On PostgreSQL each tenant is its own schema; on SQLite, control.db plus one tenants/<tenant_id>/pingtower.db per tenant. Tenant isolation falls out of which one was opened, chosen by the caller’s credential.
Back up PostgreSQL the way you back up any PostgreSQL server. On SQLite, the data directory copies cleanly — SQLite runs in WAL mode with a single writer per file — or let -backup-dir write periodic backups.
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.
Upgrade the addons agent on each box together with the tower: the tower first, then its agents. An agent older than the tower keeps running every check it knows, but a newer addon reaches it as an unknown addon. The synthetic addon needs addons agent 0.4.0 or later; the tower warns, without refusing the save, when a synthetic check names a source whose agent is older than 0.4.0, or reports no version (every release before 0.4.0), and the sources API reports each source’s agent_version. An agent that runs gRPC checks needs addons agent 0.5.0 or later: on 0.4.0 a gRPC check can hang and stop reporting, and the tower does not warn about it.
Take a backup before you upgrade. Migrations only go forward, and there is no downgrade other than restoring that backup: an older binary starts on a newer database without refusing it, and once Release 1 has added per-project access and custom roles, that widens what project-limited members can reach. Upgrading to Release 1 lists everything that release needs.