Hosting: monitoring#
This documentation applies to Piler enterprise edition 2.2.5+
Revision #3
Publication date: 2026-OCT-27
piler-ui exposes its metrics in the Prometheus text format at /metrics,
and a health check at /healthz (503 when MariaDB, Manticore or Redis is
down). Any monitoring system that reads Prometheus metrics can use them:
Prometheus, Grafana Cloud, VictoriaMetrics, Datadog, Zabbix, Checkmk and
others.
The monitoring example has the full list of metrics, alert rules with tests, and a Grafana Cloud setup with Alloy and a dashboard.
What to scrape#
- A single node: its piler-ui (port 3000).
- Multi-node: the master's piler-ui, and every worker's piler-ui on the private network. The per-tenant metrics come from the workers, where the emails and indexes are, the quota metrics from the master.
/metrics and /healthz answer only the addresses in ALLOWED_CIDRS: add
your scraper's. Scrape piler-ui directly: a request through a reverse proxy
counts only from a proxy in TRUSTED_PROXIES, as the client address in its
PROXY_HEADER (default X-Forwarded-For), and a forwarded request from any
other address is refused.
What matters most#
| Watch | Metric | Why |
|---|---|---|
| Each tenant's email keeps arriving | piler_tenant_messages_received grows |
A customer's journaling rule or the path to piler-smtp broke: their email is missing from the archive. The most important alert for a hoster. |
| No email is stuck | piler_archive_messages_total{result="error"}, piler_error_dir_files |
An email the piler daemon can't archive goes to the error directory: it's not in the archive until it's processed again |
| The daemon keeps up | piler_queue_files, the rate of piler_archive_messages_total |
Emails waiting while the daemon processes none: it's stopped, or MariaDB or Manticore is down |
| S3 uploads keep up | piler_s3_uploads_total{result="error"}, piler_s3_queue_oldest_age_seconds |
Until the upload an email is only on the node's local disk, and the archive can't read it |
| Archive and index size per tenant | piler_tenant_stored_bytes, piler_index_disk_bytes |
Capacity planning and billing |
| Tenants within their quotas | piler_tenant_quota_used / piler_tenant_quota_limit |
Over 1: raise the quota or bill the overage. See below |
| piler-ui answers | up of the scrape |
|
| The tenant metrics are fresh | piler_tenant_metrics_last_success_timestamp_seconds |
The collector runs every minute; piler-ui logs a failing tenant |
| Disk | disk_used_bytes / disk_size_bytes |
The store disk; with S3 storage, watch the MariaDB and Manticore volumes with a host exporter |
| MariaDB connections, HTTP errors, request time | db_connections_utilization_percent, http_requests_by_status_class, http_request_duration_seconds |
The piler daemon and s3uploader write their metrics to the Redis of
REDIS_ADDR, and piler-ui serves them with its own: on a multi-node
deployment each worker's piler-ui has those of its node.
A tenant with little email may be quiet for hours: set the "journaling stopped" window per tenant if needed.
Quotas#
The master's piler-ui (a single node's) has the quotas of every tenant from the quota snapshot, collected every 15 minutes:
| Metric | |
|---|---|
piler_tenant_quota_used{tenant,quota} |
The usage: quota="storage" in bytes, "emails", "users" (active users, see billing) |
piler_tenant_quota_limit{tenant,quota} |
The quota, only those set: an unlimited quota has no series |
piler_tenant_quota_age_seconds{tenant} |
Age of the tenant's usage |
piler_quota_snapshot_age_seconds |
Age of the snapshot |
When a worker doesn't answer the master, the usage of the others is too low, and a quota alert would resolve while the worker is down: the metrics keep the tenant's last usage collected from every worker, and its age grows. The example alerts: a tenant over a quota or over 90% of it for an hour, the snapshot or a tenant's usage over an hour old. With several masters each serves the same values.
Logs#
The containers log to stdout in JSON: collect them with your log system
(the Grafana Cloud example sends them to Loki). piler logs every archived
email (stored metadata id=..., message id=...), and errors with the
reason.