Pingtower Docs
Docs Self-hosting Install the daemon

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.

  1. 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 above
    

    Needs stock OpenSSH 8.2 or later. If any check fails, do not run the binary.

  2. 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/pingtower
    
  3. Create 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.target
    
    sudo systemctl daemon-reload
    sudo systemctl enable --now pingtower
    curl -s http://127.0.0.1:8391/healthz
    
  4. Put TLS in front

    The daemon speaks plain HTTP on localhost and expects a reverse proxy to terminate TLS. See Reverse proxy & TLS.

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

FlagDoes
-listenAddress to serve on. Keep it on localhost behind the proxy.
-data-dirWhere control.db and tenants/<id>/pingtower.db live. One directory, back it up as a unit.
-retain-daysHow long retained log rows are kept.
-max-logs-per-sourceA per-source cap on retained rows.
-backup-dirWhere periodic SQLite backups are written.
-secret-keySeals 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.

Last updated 1 Sep 2026 Report a problem with this page