Set up Thanos Query and Compactor for distributed metrics querying

Advanced 75 min Aug 31, 2026 194 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Deploy Thanos Sidecar, Query, and Compactor to unify multiple Prometheus instances into a single global query view with long-term S3-backed storage. Covers deduplication, TLS, and production troubleshooting.

Prerequisites

  • Two or more existing Prometheus instances with local TSDB storage
  • Root or sudo access on all Thanos and Prometheus hosts
  • An S3-compatible object storage backend such as MinIO
  • Basic familiarity with systemd service management
  • Internal CA or Vault PKI for issuing TLS certificates

What this solves

A single Prometheus instance cannot scale beyond one cluster or retain years of metrics cheaply. Thanos adds a global query layer across multiple Prometheus servers and offloads long-term storage to S3-compatible object storage with automated downsampling and compaction.

This tutorial deploys Thanos Sidecar, Query, Store Gateway, and Compactor across two Prometheus instances, backed by MinIO object storage, with TLS between components and Prometheus metrics for the Thanos processes themselves.

Understanding Thanos architecture

Thanos Sidecar runs next to each Prometheus instance and exposes its local TSDB blocks over gRPC, while also uploading completed blocks to object storage. Thanos Query (Querier) talks to all Sidecars and Store Gateways via the Store API, merges results, and deduplicates data from replicated Prometheus pairs. Store Gateway serves historical blocks directly from object storage without touching local Prometheus disks. Compactor runs as a singleton process that merges, downsamples, and applies retention rules to blocks in the bucket.

Note: Only one Compactor instance should ever run against a given bucket. Running two in parallel corrupts block metadata.

Step-by-step installation

Provision the object storage backend

Thanos needs an S3-compatible bucket for blocks. This example uses MinIO on a dedicated host at 203.0.113.20. If you already run MinIO, you can reuse it, see Install and configure MinIO object storage with SSL and clustering for a full setup.

mc alias set myminio https://203.0.113.20:9000 thanosadmin 'Str0ngM1nioSecretKey!' 
mc mb myminio/thanos-metrics
mc admin user add myminio thanos-svc 'An0therStr0ngSecretKey!'
mc admin policy attach myminio readwrite --user thanos-svc

Create the object storage config file

Every Thanos component that talks to object storage needs this file. Keep it out of version control since it contains credentials.

type: S3
config:
  bucket: "thanos-metrics"
  endpoint: "203.0.113.20:9000"
  access_key: "thanos-svc"
  secret_key: "An0therStr0ngSecretKey!"
  insecure: false
  signature_version2: false
  http_config:
    tls_config:
      ca_file: /etc/thanos/certs/minio-ca.pem
sudo mkdir -p /etc/thanos/certs
sudo chown -R thanos:thanos /etc/thanos
sudo chmod 640 /etc/thanos/bucket.yml

Create the thanos system user and download binaries

Run Thanos as a dedicated unprivileged user, never as root.

sudo useradd --system --no-create-home --shell /usr/sbin/nologin thanos
sudo apt update && sudo apt install -y curl tar
curl -L -o /tmp/thanos.tar.gz https://github.com/thanos-io/thanos/releases/download/v0.36.1/thanos-0.36.1.linux-amd64.tar.gz
sudo tar -xzf /tmp/thanos.tar.gz -C /opt
sudo ln -s /opt/thanos-0.36.1.linux-amd64/thanos /usr/local/bin/thanos
sudo useradd --system --no-create-home --shell /sbin/nologin thanos
sudo dnf install -y curl tar
curl -L -o /tmp/thanos.tar.gz https://github.com/thanos-io/thanos/releases/download/v0.36.1/thanos-0.36.1.linux-amd64.tar.gz
sudo tar -xzf /tmp/thanos.tar.gz -C /opt
sudo ln -s /opt/thanos-0.36.1.linux-amd64/thanos /usr/local/bin/thanos

Deploy Thanos Sidecar alongside each Prometheus instance

The sidecar needs read access to Prometheus's data directory and its config reload endpoint. Run this on each Prometheus host (203.0.113.11 and 203.0.113.12 in this example).

[Unit]
Description=Thanos Sidecar
After=network.target prometheus.service

[Service]
User=thanos
Group=thanos
ExecStart=/usr/local/bin/thanos sidecar \
  --tsdb.path=/var/lib/prometheus \
  --prometheus.url=http://127.0.0.1:9090 \
  --grpc-address=0.0.0.0:10901 \
  --http-address=127.0.0.1:10902 \
  --objstore.config-file=/etc/thanos/bucket.yml \
  --grpc-server-tls-cert=/etc/thanos/certs/sidecar.pem \
  --grpc-server-tls-key=/etc/thanos/certs/sidecar-key.pem \
  --grpc-server-tls-client-ca=/etc/thanos/certs/ca.pem
Restart=on-failure
LimitNOFILE=65536

[Install]
WantedBy=multi-user.target

Add the sidecar's user to the prometheus group so it can read the TSDB directory without loosening permissions on other files.

sudo usermod -aG prometheus thanos
sudo chmod 750 /var/lib/prometheus
sudo systemctl daemon-reload
sudo systemctl enable --now thanos-sidecar

Enable Prometheus remote-write flags for the sidecar

Prometheus must run with a minimum retention that overlaps with the compactor's upload window, and expose the admin API for block cleanup.

[Service]
ExecStart=
ExecStart=/usr/local/bin/prometheus \
  --config.file=/etc/prometheus/prometheus.yml \
  --storage.tsdb.path=/var/lib/prometheus \
  --storage.tsdb.min-block-duration=2h \
  --storage.tsdb.max-block-duration=2h \
  --web.enable-lifecycle
sudo systemctl daemon-reload
sudo systemctl restart prometheus
Warning: setting min-block-duration equal to max-block-duration disables local compaction so Thanos Compactor owns it exclusively. Do not run Prometheus's own compaction alongside Thanos Compactor on the same blocks.

Deploy Thanos Query for the global view

Query connects to both sidecars directly by gRPC address and, optionally, a Store Gateway for historical data. Run this on a dedicated query node at 203.0.113.30.

[Unit]
Description=Thanos Query
After=network.target

[Service]
User=thanos
Group=thanos
ExecStart=/usr/local/bin/thanos query \
  --http-address=0.0.0.0:10904 \
  --grpc-address=0.0.0.0:10903 \
  --query.replica-label=replica \
  --store=203.0.113.11:10901 \
  --store=203.0.113.12:10901 \
  --store=203.0.113.20:10905 \
  --grpc-client-tls-cert=/etc/thanos/certs/query-client.pem \
  --grpc-client-tls-key=/etc/thanos/certs/query-client-key.pem \
  --grpc-client-tls-ca=/etc/thanos/certs/ca.pem
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now thanos-query

Configure deduplication and service discovery

If you run Prometheus in HA pairs, label each replica with a distinct replica label at scrape time so Query can deduplicate identical series. For dynamic environments, use file-based service discovery instead of hardcoded --store flags.

global:
  external_labels:
    replica: A
    cluster: prod-eu-west
[
  {
    "targets": ["203.0.113.11:10901", "203.0.113.12:10901"]
  }
]

Replace the static --store flags with --store.sd-files=/etc/thanos/stores.json and --store.sd-interval=30s so Query picks up new sidecars without a restart. This pairs well with existing service discovery patterns described in Configure HAProxy with Consul for dynamic service discovery if you already run Consul.

Deploy Thanos Store Gateway

Store Gateway serves compacted historical blocks from object storage so Query does not need to hit every sidecar for old data.

[Unit]
Description=Thanos Store Gateway
After=network.target

[Service]
User=thanos
Group=thanos
ExecStart=/usr/local/bin/thanos store \
  --data-dir=/var/lib/thanos-store \
  --objstore.config-file=/etc/thanos/bucket.yml \
  --grpc-address=0.0.0.0:10905 \
  --http-address=127.0.0.1:10906 \
  --grpc-server-tls-cert=/etc/thanos/certs/store.pem \
  --grpc-server-tls-key=/etc/thanos/certs/store-key.pem \
  --grpc-server-tls-client-ca=/etc/thanos/certs/ca.pem
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo mkdir -p /var/lib/thanos-store
sudo chown thanos:thanos /var/lib/thanos-store
sudo chmod 750 /var/lib/thanos-store
sudo systemctl daemon-reload
sudo systemctl enable --now thanos-store

Deploy Thanos Compactor for downsampling and retention

Run exactly one Compactor instance against the bucket. It handles vertical compaction, downsampling to 5m and 1h resolutions, and retention enforcement.

[Unit]
Description=Thanos Compactor
After=network.target

[Service]
User=thanos
Group=thanos
ExecStart=/usr/local/bin/thanos compact \
  --data-dir=/var/lib/thanos-compact \
  --objstore.config-file=/etc/thanos/bucket.yml \
  --http-address=127.0.0.1:10907 \
  --retention.resolution-raw=30d \
  --retention.resolution-5m=180d \
  --retention.resolution-1h=730d \
  --wait \
  --compact.concurrency=2
Restart=on-failure

[Install]
WantedBy=multi-user.target
sudo mkdir -p /var/lib/thanos-compact
sudo chown thanos:thanos /var/lib/thanos-compact
sudo chmod 750 /var/lib/thanos-compact
sudo systemctl daemon-reload
sudo systemctl enable --now thanos-compact
Note: the retention flags apply per resolution level. Raw data is pruned after 30 days but downsampled 1h data survives for 2 years, keeping long-range dashboards fast and cheap to store.

Generate TLS certificates for gRPC communication

All Thanos gRPC traffic between Sidecar, Query, and Store Gateway should be encrypted with mutual TLS in production. Use a private CA rather than self-signed certs per node.

openssl genrsa -out /etc/thanos/certs/ca-key.pem 4096
openssl req -x509 -new -nodes -key /etc/thanos/certs/ca-key.pem -sha256 -days 3650 \
  -out /etc/thanos/certs/ca.pem -subj "/CN=thanos-internal-ca"
openssl genrsa -out /etc/thanos/certs/query-client-key.pem 2048
openssl req -new -key /etc/thanos/certs/query-client-key.pem \
  -out /etc/thanos/certs/query-client.csr -subj "/CN=thanos-query"
openssl x509 -req -in /etc/thanos/certs/query-client.csr -CA /etc/thanos/certs/ca.pem \
  -CAkey /etc/thanos/certs/ca-key.pem -CAcreateserial -out /etc/thanos/certs/query-client.pem -days 825 -sha256
sudo chown -R thanos:thanos /etc/thanos/certs
sudo chmod 600 /etc/thanos/certs/*-key.pem
sudo chmod 644 /etc/thanos/certs/*.pem
Never use chmod 777. Private keys must stay readable only by the thanos user, other Thanos components need only the CA certificate to verify peers. Set 600 on private keys and 640 or 644 on certificates, and use chown to grant access to the correct service account instead of opening permissions to everyone.

If you already manage internal PKI, see Set up Vault as a PKI certificate authority for issuing and rotating these certificates automatically instead of managing openssl by hand.

Put Query behind a reverse proxy with authentication

Thanos Query's HTTP UI and API have no built-in authentication. Front it with a reverse proxy that enforces TLS and basic auth or OAuth2 for anyone outside the internal network.

server {
    listen 443 ssl;
    server_name thanos.example.com;

    ssl_certificate     /etc/letsencrypt/live/thanos.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/thanos.example.com/privkey.pem;

    auth_basic "Thanos Query";
    auth_basic_user_file /etc/nginx/.htpasswd-thanos;

    location / {
        proxy_pass http://127.0.0.1:10904;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

For certificate automation, follow Implement Nginx SSL certificate automation with Let's Encrypt using Certbot.

Monitoring Thanos Query and Compactor performance

Each Thanos component exposes its own Prometheus metrics on its HTTP address. Scrape them from a dedicated monitoring Prometheus instance separate from the ones being federated.

scrape_configs:
  - job_name: thanos-query
    static_configs:
      - targets: ['203.0.113.30:10904']
  - job_name: thanos-compact
    static_configs:
      - targets: ['203.0.113.20:10907']
  - job_name: thanos-store
    static_configs:
      - targets: ['203.0.113.20:10906']

Key metrics to alert on: thanos_compact_group_compactions_failures_total for failed compactions, thanos_query_concurrent_gate_queries_max for query concurrency saturation, and thanos_compact_halted which is 1 when the compactor has hit a fatal block error and stopped processing. Build alert rules following the pattern in Configure Prometheus alerting with AlertManager notifications and webhook integration.

Verify your setup

sudo systemctl status thanos-sidecar thanos-query thanos-store thanos-compact
curl -s http://127.0.0.1:10904/-/healthy
curl -s http://127.0.0.1:10904/api/v1/stores | python3 -m json.tool

Open the Query UI at https://thanos.example.com/stores and confirm both sidecars and the store gateway show as UP. Run a test query spanning both Prometheus instances to confirm the global view merges results correctly.

mc ls myminio/thanos-metrics/ | head
mc ls myminio/thanos-metrics/debug/ 2>/dev/null

Common issues

SymptomCauseFix
Query shows store as downgRPC TLS handshake failure between Query and SidecarVerify CA cert matches on both sides with openssl verify -CAfile ca.pem sidecar.pem
Duplicate data points in graphsMissing or mismatched replica labelSet unique external_labels.replica per Prometheus and pass --query.replica-label=replica
Compactor halted with block overlap errorTwo compactors ran against the same bucket concurrentlyStop the duplicate process, manually inspect overlapping blocks with thanos tools bucket verify, run repair if needed
Sidecar not uploading blocksPrometheus min/max block duration set to default (2h auto-compact), or wrong bucket credentialsConfirm both duration flags are equal and check sidecar logs for S3 auth errors
Store gateway high memory usageIndex cache too large for available RAMTune --index-cache-size and --chunk-pool-size to match available memory
Query returns 503 under loadToo many concurrent queries against limited gateIncrease --query.max-concurrent carefully and add caching layer in front

Next steps

Running this in production?

Want this handled for you? Running Thanos at scale adds a second layer of work: bucket lifecycle policies, compactor capacity planning, certificate rotation across every component, and being paged when compaction halts at 3am. Talk to an engineer if you'd rather hand the ops side off.

Automated install script

Run this to automate the entire setup

इसे खुद मैनेज नहीं करना चाहते?

हम उन businesses के लिए infrastructure संभालते हैं जो uptime पर निर्भर हैं। Fully managed, एक fixed contact के साथ जो आपके setup को जानता है।

आपको एक निश्चित contact मिलता है जो आपके setup को जानता है

रॉटरडैम में उनकी डेस्क पर 11:53 · एक message में पहुंचें, कोई ticket form नहीं