Deploy Thanos Ruler to evaluate recording and alerting rules across multiple Prometheus clusters, integrate it with Alertmanager and S3-compatible storage, and run it in HA mode with deduplication.
Prerequisites
- A working Thanos Query deployment with at least two connected Prometheus clusters
- Alertmanager already deployed and reachable over the network
- S3-compatible object storage (AWS S3 or self-hosted MinIO)
- Root or sudo access on the hosts running Thanos Ruler
- Basic familiarity with PromQL and Prometheus rule syntax
What this solves
When you split Prometheus across multiple clusters, alerting rules that need a global view (like total error rate across regions) can't run reliably on a single local Prometheus. Thanos Ruler evaluates recording and alerting rules against Thanos Query's unified view, ships results to object storage as immutable TSDB blocks, and forwards alerts to Alertmanager with deduplication.
This tutorial covers architecture, installation, rule authoring, Alertmanager integration, S3/MinIO backend configuration, and running two Ruler replicas in HA with proper deduplication labels.
Understanding Thanos Ruler architecture
Thanos Ruler sits downstream of Thanos Query. It does not read metrics from local Prometheus TSDB directly; instead it queries the Thanos Query layer over gRPC or HTTP, which itself fans out to all connected Sidecars, Receivers, or Store Gateways across clusters. This lets a single rule evaluate against metrics from every cluster in your fleet.
Evaluated results (both recording rule outputs and alert series) are written to a local TSDB block by Ruler, then uploaded to object storage on the same interval as Prometheus does with Sidecar. Alerts are pushed to one or more Alertmanager instances using the standard Alertmanager v2 API. Because Ruler produces its own metrics, it also exposes a StoreAPI endpoint so Thanos Query can read historical rule results back, which is why Ruler is typically registered as another StoreAPI endpoint in Query's --store flag.
If you haven't set up the query layer yet, complete Set up Thanos Query and Compactor for distributed metrics querying first, since Ruler depends on a working Querier endpoint.
Step-by-step installation
Create a dedicated system user
Run Thanos Ruler as an unprivileged user, never as root.
sudo groupadd --system thanos
sudo useradd --system --gid thanos --no-create-home --shell /usr/sbin/nologin thanosDownload and install the Thanos binary
Thanos ships as a single static binary containing all components, including Ruler.
curl -L -o thanos.tar.gz https://github.com/thanos-io/thanos/releases/download/v0.36.1/thanos-0.36.1.linux-amd64.tar.gz
tar -xzf thanos.tar.gz
sudo mv thanos-0.36.1.linux-amd64/thanos /usr/local/bin/thanos
sudo chown root:root /usr/local/bin/thanos
sudo chmod 755 /usr/local/bin/thanos
thanos --versionCreate directories for rules, data, and config
Ruler needs a local data directory for its TSDB block staging area and a rules directory for rule files.
sudo mkdir -p /etc/thanos/rules /etc/thanos/tls /var/lib/thanos-ruler
sudo chown -R thanos:thanos /etc/thanos /var/lib/thanos-ruler
sudo chmod 750 /var/lib/thanos-ruler
sudo chmod 750 /etc/thanos/rulesDefining recording and alerting rule files
Write a cross-cluster recording rule
Recording rules precompute expensive aggregations. This example rolls up HTTP error rate across every cluster labeled with a cluster label injected by each Prometheus's external_labels.
groups:
- name: global-http-slo
interval: 30s
rules:
- record: job:http_requests_error_rate:ratio_rate5m
expr: |
sum by (job, cluster) (rate(http_requests_total{status=~"5.."}[5m]))
/
sum by (job, cluster) (rate(http_requests_total[5m]))
- record: global:http_requests_error_rate:ratio_rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))Write a cross-cluster alerting rule
This alert fires only when the global error rate across all clusters exceeds a threshold, something a single cluster's Prometheus cannot evaluate on its own.
groups:
- name: global-alerts
interval: 30s
rules:
- alert: GlobalHighErrorRate
expr: global:http_requests_error_rate:ratio_rate5m > 0.05
for: 10m
labels:
severity: critical
scope: global
annotations:
summary: "Global HTTP error rate above 5% across all clusters"
description: "Aggregate 5xx error rate is {{ $value | humanizePercentage }} across all connected Prometheus clusters."
- alert: ClusterMissingFromQuery
expr: absent(up{job="prometheus", cluster="eu-west-1"})
for: 5m
labels:
severity: warning
scope: global
annotations:
summary: "Cluster eu-west-1 has stopped reporting to Thanos Query"external_labels.cluster value. Without it, series from different clusters collide during query-time deduplication and rule evaluation produces incorrect results.Configuring Thanos Ruler with Thanos Query
Point Ruler at Query endpoints
Ruler evaluates rules by querying one or more Thanos Query instances over HTTP, and can also query multiple endpoints directly for failover.
THANOS_QUERY_ENDPOINTS="--query=203.0.113.10:10904 --query=203.0.113.11:10904"
THANOS_LABELS="--label=ruler_replica=\"ruler-1\" --label=cluster=\"global\""Create the systemd unit
This unit runs Ruler with rules, TSDB block config, and Query discovery. Adjust the IP addresses in --query to match your Thanos Query instances.
[Unit]
Description=Thanos Ruler
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=thanos
Group=thanos
ExecStart=/usr/local/bin/thanos rule \
--data-dir=/var/lib/thanos-ruler \
--rule-file=/etc/thanos/rules/*.yml \
--eval-interval=30s \
--query=203.0.113.10:10904 \
--query=203.0.113.11:10904 \
--alertmanagers.url=http://203.0.113.20:9093 \
--alertmanagers.url=http://203.0.113.21:9093 \
--alert.query-url=http://203.0.113.30:10902 \
--label=ruler_replica="ruler-1" \
--label=cluster="global" \
--objstore.config-file=/etc/thanos/objstore.yml \
--http-address=0.0.0.0:10904 \
--grpc-address=0.0.0.0:10901 \
--tsdb.retention=48h
Restart=on-failure
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.targetThe --alert.query-url flag sets the link embedded in alerts so recipients can jump straight to the query that triggered them in the Thanos Query UI.
Integrating with Alertmanager
Configure Alertmanager targets
Ruler pushes alerts to every Alertmanager listed via --alertmanagers.url. It automatically deduplicates alerts sent to multiple Alertmanager replicas because Alertmanager's own gossip protocol handles that layer.
Verify the alert route accepts global-scope alerts
Add a route in Alertmanager's config that matches the scope: global label used in the rule file above, so these alerts can be routed to a different receiver (e.g. a platform team channel) than per-cluster alerts.
route:
receiver: default
routes:
- match:
scope: global
receiver: platform-team
group_by: ['alertname']
group_wait: 30s
receivers:
- name: default
slack_configs:
- channel: '#alerts'
- name: platform-team
slack_configs:
- channel: '#platform-global-alerts'For webhook or PagerDuty routing patterns, see Configure Prometheus Alertmanager with custom webhook integrations for Slack, Microsoft Teams, and PagerDuty notifications.
Setting up the object storage backend
Create an S3 or MinIO bucket for rule evaluation blocks
Ruler uploads completed TSDB blocks the same way Sidecar does, so it needs its own bucket path or a shared bucket with a distinct prefix.
mc alias set thanosminio https://minio.example.com minioadmin 'ChangeThisStrongSecret123!'
mc mb thanosminio/thanos-ruler-blocks
mc anonymous set none thanosminio/thanos-ruler-blocksWrite the object storage config file
This file is shared in structure with the one used by Thanos Query's Store Gateway and Compactor.
type: S3
config:
bucket: thanos-ruler-blocks
endpoint: minio.example.com:443
access_key: minioadmin
secret_key: ChangeThisStrongSecret123!
insecure: false
signature_version2: false
http_config:
tls_config:
insecure_skip_verify: falsesudo chown thanos:thanos /etc/thanos/objstore.yml
sudo chmod 640 /etc/thanos/objstore.ymlIf you're running MinIO for this backend, review Setup MinIO monitoring with Prometheus and Grafana dashboards to keep an eye on bucket usage and latency.
Start Ruler
sudo systemctl daemon-reload
sudo systemctl enable --now thanos-ruler
sudo systemctl status thanos-rulerDeploying Thanos Ruler in high availability mode
Run two identical Ruler replicas
Deploy a second Ruler instance on a separate host with the same rule files but a different ruler_replica label. Both replicas evaluate the same rules independently against the same Query endpoints.
rsync -avz --delete /etc/thanos/rules/ thanos-ruler-2:/etc/thanos/rules/On the second host, change only the replica label in the systemd unit:
--label=ruler_replica="ruler-2" \
--label=cluster="global" \Enable deduplication at Thanos Query
Thanos Query deduplicates series with matching labels except for the replica label you specify with --query.replica-label. This means alerts and recording rule outputs from both Ruler replicas collapse into a single series when read back through Query.
thanos query \
--http-address=0.0.0.0:10902 \
--grpc-address=0.0.0.0:10901 \
--query.replica-label=ruler_replica \
--query.replica-label=replica \
--endpoint=203.0.113.30:10901 \
--endpoint=203.0.113.31:10901Both Ruler replicas will independently fire the same alert and push it to Alertmanager. Alertmanager's own deduplication and grouping, based on the alert's label set minus the replica label, collapses duplicate notifications into one.
Register Ruler as a StoreAPI in Query
Add each Ruler's gRPC address as an endpoint on Thanos Query so historical rule evaluation results become queryable through the same Query layer.
thanos query \
--endpoint=203.0.113.30:10901 \
--endpoint=203.0.113.31:10901 \
--endpoint=203.0.113.40:10901 \
--endpoint=203.0.113.41:10901Here the first two endpoints are Rulers and the last two are Sidecars or Receivers from your existing clusters.
Monitoring Thanos Ruler performance
Scrape Ruler's own metrics
Ruler exposes Prometheus metrics on its HTTP address, including rule evaluation duration, failures, and alert send errors.
scrape_configs:
- job_name: thanos-ruler
static_configs:
- targets:
- 203.0.113.30:10904
- 203.0.113.31:10904Alert on rule evaluation failures
These meta-alerts catch cases where Ruler itself is unhealthy, which is critical since a broken Ruler means silent gaps in your global alerting.
groups:
- name: thanos-ruler-health
rules:
- alert: ThanosRuleEvaluationFailures
expr: rate(prometheus_rule_evaluation_failures_total[5m]) > 0
for: 10m
labels:
severity: warning
annotations:
summary: "Thanos Ruler is failing to evaluate rules"
- alert: ThanosRuleAlertmanagerSendFailures
expr: rate(thanos_alert_sender_alerts_dropped_total[5m]) > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Thanos Ruler cannot deliver alerts to Alertmanager"
- alert: ThanosRuleHighRuleEvaluationLatency
expr: histogram_quantile(0.99, rate(prometheus_rule_evaluation_duration_seconds_bucket[5m])) > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Rule evaluation p99 latency exceeds 5 seconds"For dashboarding these metrics, reuse the panel patterns from Configure advanced Grafana dashboards and alerting with Prometheus integration.
Verify your setup
curl -s http://203.0.113.30:10904/-/ready
curl -s http://203.0.113.30:10904/api/v1/rules | head -c 500
curl -s http://203.0.113.30:10902/api/v1/query?query=global:http_requests_error_rate:ratio_rate5m
mc ls thanosminio/thanos-ruler-blockssudo systemctl status thanos-ruler --no-pager
sudo journalctl -u thanos-ruler -n 50 --no-pagerCommon issues
| Symptom | Cause | Fix |
|---|---|---|
| Rules never evaluate, no series produced | Ruler cannot reach any Thanos Query endpoint | Check --query flags and confirm network reachability with curl 203.0.113.10:10904/-/ready |
| Duplicate Slack notifications for the same alert | Query is not deduplicating on the ruler_replica label | Add --query.replica-label=ruler_replica to the Thanos Query command |
| Alerts fire but never reach Alertmanager | Wrong or unreachable --alertmanagers.url | Verify Alertmanager is listening on 9093 and check firewall rules between Ruler and Alertmanager hosts |
| Blocks never appear in the bucket | Invalid or missing objstore.yml credentials | Check journalctl -u thanos-ruler for upload errors and confirm credentials in objstore.yml |
| Permission denied reading objstore.yml | File owned by root but Ruler runs as thanos user | Run sudo chown thanos:thanos /etc/thanos/objstore.yml, never chmod 777 |
| Recording rule values look wrong or double counted | Missing or duplicate external_labels.cluster on source Prometheus instances | Ensure every Prometheus has a unique cluster label before querying through Thanos Query |
| High rule evaluation latency | Query fan-out across too many Store Gateways per evaluation | Reduce eval-interval scope or add more Query replicas behind a load balancer |
Next steps
- Set up Thanos Query and Compactor for distributed metrics querying
- Implement Alertmanager high availability clustering with automatic failover and load balancing
- Implement Thanos multi-cluster federation for global Prometheus metrics aggregation
- Configure Thanos Receiver clustering for high availability and load distribution
- Build Grafana dashboards for Thanos Ruler evaluation metrics