## Intro

MinIO powers S3-compatible object storage across Docker and Kubernetes. To keep it reliable and fast in production, you need clear visibility into availability, capacity, performance, and data safety, plus a small set of well-tuned alerts and concise runbooks.

This guide focuses on the signals that matter, shows concrete Prometheus queries and starter alert rules, and adds a signal-to-action table, alert tuning tips, a compact incident runbook, and a post-change verification checklist. It ends with a local pilot you can validate before rolling out to production.

What you will get:

- A prioritized monitoring scope and signal-to-action table

- Prometheus queries and copy-ready alert rules with tuning guidance

- Practical log and trace patterns for early warning

- A compact incident runbook and a post-change checklist

- A safe, measurable local pilot plan

## What to monitor in MinIO

Focus on user impact first, then root causes and operator actions.

- Availability and API health:

- up{job="minio"} , S3 request rate and error ratio, request duration percentiles (p50, p95, p99)

- Capacity and growth:

- Cluster total/used bytes, free headroom, burn rate, hot buckets/prefixes (use mc admin bucket top to identify hot buckets by request count)

- Cluster and disk health:

- Offline nodes and drives, healing activity/backlog, disk I/O errors

- Performance:

- Read/write throughput, concurrency by operation, cache hit rate (if enabled)

- Security and access:

- AccessDenied spikes, admin operations, TLS handshake errors

- Data workflows:

- Replication lag and failures, batch/ETL throughput changes

- Host saturation (via node exporter or platform metrics):

- CPU, memory pressure, disk latency, network saturation

## Signal-to-action table

Use this table to turn top-line signals into first checks and actions. Treat thresholds as starting points; baseline your own environment before enforcing.

<div class="my-stack-md overflow-x-auto">
<table class="w-full min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Area</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Primary symptoms (what users see)</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Useful metrics/logs</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">First checks</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Immediate actions</th></tr></thead>
<tbody><tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Availability</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Intermittent 5xx, timeouts</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">up{job=&quot;minio&quot;}</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_s3_requests_total</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_s3_requests_errors_total</code>, logs: connection timeout</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Are any targets down? Recent deploy? Network/pod restarts?</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Restore down pods/nodes, roll back unsafe changes, validate DNS/service endpoints</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Capacity</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Uploads fail, slow writes, healing storms after restarts</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_cluster_capacity_used_bytes</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_cluster_capacity_total_bytes</code>, growth of used bytes</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Trend of usage vs. headroom, large bucket/prefix growth, lifecycle rules</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Enforce lifecycle, clean failed multipart, add/expand disks or nodes</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Latency</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Slow GET/PUT, client retries</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_s3_request_duration_seconds_bucket</code> (p95/p99), throughput per node</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Is latency localized to ops or nodes? Any host I/O spikes?</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Reduce concurrency, isolate I/O, cache hot prefixes, scale out</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Errors</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Spikes in 4xx/5xx, auth failures</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_s3_requests_errors_total</code> by code, logs: AccessDenied, SignatureDoesNotMatch</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Recent key/policy changes? Client-side changes?</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Restore credentials/policies, rate-limit abusive clients, confirm clock sync</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Replication</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Objects lagging across sites</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">e.g., <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_bucket_replication_pending_count</code> or <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">minio_bucket_replication_lag_seconds</code> (verify exact names in your version), logs: replication failures</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Backlog size and age, network path, target site health</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Unthrottle replication, fix credentials/permissions, pause noisy jobs</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Certificates</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">TLS handshake failures, clients cannot connect</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Logs: TLS handshake errors, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">blackbox_exporter probe_ssl_earliest_cert_expiry</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Expiry date, full chain installed, clocks in sync</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Renew/deploy certs, reload MinIO, verify full chain and SANs</td></tr>
<tr><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Host saturation</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">All ops slower, occasional timeouts</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant"><code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">node_cpu_seconds_total</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">node_disk_io_time_seconds_total</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">node_load1</code>, <code class="font-mono text-[0.9em] bg-surface-container px-1 py-0.5 rounded">node_network*</code></td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">CPU steal/waits, disk busy time, packet drops</td><td class="border border-outline-variant px-4 py-3 align-top text-body-md text-on-surface-variant">Move noisy neighbors, add IOPS/CPU, tune queues, rebalance traffic</td></tr></tbody>
</table>
</div>

## Workflow overview

- Expose metrics from MinIO

- MinIO exposes Prometheus metrics at /minio/v2/metrics/cluster .

- Quick check: curl http://MINIO_HOST:9000/minio/v2/metrics/cluster

- Scrape with Prometheus

- Add a scrape job targeting your MinIO endpoints.

# prometheus.yml
scrape_configs:
 - job_name: minio
 metrics_path: /minio/v2/metrics/cluster
 static_configs:
 - targets: ["minio:9000\]
 labels:
 cluster: demo 

- Visualize with Grafana

- Build top-row panels for availability, latency, error ratio, and capacity. Use repeating panels by node for distributed clusters.

- Alert on risks

- Start with capacity, nodes/targets down, high error ratio, and high latency. Expand to replication, cert expiry, and host saturation.

- Add logs and traces

- Forward JSON logs to your log platform. Use mc admin trace for live S3 call sampling during incidents.

- Respond with runbooks

- For each alert, document checks, likely causes, and safe mitigations.

## Prometheus queries you can reuse

 Metric names vary by MinIO version and build. In Prometheus, type minio_ to browse metrics and adjust filters (for example, job="minio" ).

- Cluster capacity percent used

100 * minio_cluster_capacity_used_bytes / minio_cluster_capacity_total_bytes 

- S3 request rate by method (5m)

 sum by (method) (rate(minio_s3_requests_total[5m])) 

- 5xx error ratio (5m)

 sum(rate(minio_s3_requests_errors_total{code=~"5.."}[5m]))
/ ignoring(code)
sum(rate(minio_s3_requests_total[5m])) 

- P95 request latency (seconds)

 histogram_quantile(
 0.95,
 sum by (le) (rate(minio_s3_request_duration_seconds_bucket[5m]))
) 

- Offline nodes (if available)

 minio_cluster_nodes_offline_total 

- Offline disks (if available)

 minio_disks_offline_total 

- Replication backlog (example; verify names in your cluster)

 sum(rate(minio_bucket_replication_pending_count[5m])) 
 *Note: Metric names vary by MinIO version; if minio_bucket_replication_pending_count is absent, try minio_bucket_replication_pending_total .

- Certificate expiry (via blackbox exporter scraping MinIO endpoint)

(probe_ssl_earliest_cert_expiry - time()) / 86400 

- Host saturation (node exporter)

 # CPU busy ratio
1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

# Disk busy time
avg by (instance, device) (rate(node_disk_io_time_seconds_total[5m])) 

## Starter alert rules

 Begin with a minimal, actionable set. Tune thresholds to your SLOs and baseline, not universal values.

# minio.alerts.yml
groups:
- name: minio.alerts
 rules:
 - alert: MinIOClusterCapacityHigh
 expr: (minio_cluster_capacity_used_bytes / minio_cluster_capacity_total_bytes) > 0.85
 for: 15m
 labels:
 severity: warning
 annotations:
 summary: MinIO capacity is high
 description: Capacity exceeds target headroom. Plan cleanup or expansion.

 - alert: MinIOTargetsDown
 expr: count(up{job="minio"} == 0) > 0
 for: 5m
 labels:
 severity: critical
 annotations:
 summary: One or more MinIO scrape targets are down
 description: Investigate node/pod health and restore redundancy.

 - alert: MinIOHigh5xxErrorRate
 expr: (sum(rate(minio_s3_requests_errors_total{code=~"5.."}[5m])) / sum(rate(minio_s3_requests_total[5m]))) > 0.02
 for: 10m
 labels:
 severity: critical
 annotations:
 summary: Elevated 5xx error ratio
 description: Sustained server errors indicate user impact.

 - alert: MinIOHighLatencyP95
 expr: histogram_quantile(0.95, sum by (le) (rate(minio_s3_request_duration_seconds_bucket[5m]))) > 0.5
 for: 10m
 labels:
 severity: warning
 annotations:
 summary: High p95 S3 latency
 description: Requests may be slow due to I/O or network contention.

 - alert: MinIOReplicationLag
 expr: sum(rate(minio_bucket_replication_pending_count[5m])) > 0
 for: 30m
 labels:
 severity: warning
 annotations:
 summary: Replication backlog is growing
 description: Check network path, credentials, target site health.

 - alert: MinIOTLSCertExpiringSoon
 expr: (probe_ssl_earliest_cert_expiry - time()) / 86400 < 14
 for: 0m
 labels:
 severity: warning
 annotations:
 summary: MinIO TLS certificate expires soon
 description: Renew and deploy certificates before expiry.

 - alert: HostSaturationCPU
 expr: (1 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m]))) > 0.9
 for: 10m
 labels:
 severity: warning
 annotations:
 summary: Host CPU saturation
 description: High CPU can degrade MinIO latency and throughput. 
 Tip: If minio_cluster_nodes_offline_total is not available, use the up metric and known target counts to detect down targets.

## Alert tuning and validation

Avoid one-size-fits-all thresholds. Tune with real traffic:

- Baseline first: capture p50/p95 latencies, headroom, and normal error ratios for at least a week. Set warning at the edge of normal, critical beyond sustained abnormal.

- Choose stable windows: use 5–15 minute rates for request/error metrics and a meaningful for: duration to ignore brief blips.

- Add hysteresis: warn at a lower threshold and clear at a slightly better level (for example, warn at 80% capacity, clear at 75%).

- Validate in Prometheus: run each alert expression in the graph UI and verify it returns series when your synthetic tests trigger it.

- Group alerts: group by cluster or instance to reduce duplicate pages. Inhibit downstream symptoms (for example, suppress latency alerts if targets are down).

- Review monthly: compare alerts against incident notes and adjust thresholds and durations.

## Log signals that catch issues early

MinIO emits structured JSON logs. Capture them centrally and watch for:

- level:error or level:panic

- err fields containing disk I/O errors or network timeouts

- Messages containing healing , disk not found , permission denied

- Spikes of AccessDenied , SignatureDoesNotMatch , InvalidAccessKeyId

Local examples:

# Follow errors in container logs
docker logs -f minio | jq -r 'select(.level=="error\)) | .time + " " + .msg'

# Count error types by .err
docker logs minio 2>&1 | jq -r 'select(.level=="error\)) | .err' | sort | uniq -c | sort -nr | head

# Live trace of S3 calls (slow/failing)
mc alias set local http://localhost:9000 MINIO_ROOT_USER MINIO_ROOT_PASSWORD
mc admin trace --verbose local 
 Consider adding a log-based alert for frequent disk write failures or repeated healing restarts within a short window.

## A practical dashboard layout

Organize panels by user impact first, then root causes.

- User impact

- Requests per second: sum(rate(minio_s3_requests_total[5m]))

- 4xx and 5xx ratio over total requests

- p50, p95, p99 latency using histogram_quantile

- Capacity and growth

- Percent used: 100 * used_bytes / total_bytes

- 7-day growth: increase(used_bytes[7d])

- Top buckets by size (if available)

- Health

- Offline nodes and disks, healing operations in progress

- Scrape freshness and up by target

- Performance

- Read and write throughput, concurrency by operation

## Compact incident runbook

Keep runbooks short and actionable. Distinguish symptoms (what you see) from causes (why it happens).

- Capacity high

- Symptoms: uploads fail, slow PUTs, frequent healing

- Checks: percent used, 7-day growth, top buckets/prefixes

- Likely causes: backup/analytics spikes, lifecycle misconfig

- Actions: enforce lifecycle, clean failed multipart, add/expand storage

- Commands: mc admin info local , mc ls --recursive --summarize local/bucket , mc ilm rule ls local/bucket

- Nodes or targets down

- Symptoms: 5xx spikes, timeouts

- Checks: up{job="minio"} , platform/pod status, node reachability

- Likely causes: host reboot, disk detach, network partition

- Actions: restore connectivity, restart pods, verify and mount disks

- Commands: mc admin info local , kubectl get pods -l app=minio -A , systemctl status minio || docker ps

- High 5xx error ratio

- Symptoms: server errors visible to clients

- Checks: error ratio and latency trends, logs for timeouts/internal errors

- Likely causes: saturated disks/CPU, network issues, partial outage

- Actions: reduce load or scale out, fix network path, retry with backoff

- Commands: mc admin trace --errors local

- High p95 latency

- Symptoms: slow operations, client-side retries

- Checks: latency histogram by op and node, host I/O/CPU

- Likely causes: noisy neighbors, DNS/TLS overhead, slow disks

- Actions: isolate I/O, cache hot prefixes, tune concurrency and timeouts

- Replication lag

- Symptoms: delayed objects at target site

- Checks: backlog size/age, replication worker status, network path

- Likely causes: target site down, credentials/policy issue, bandwidth limits

- Actions: restore target, fix access, temporarily unthrottle or reshard workers

- Certificate near expiry or invalid

- Symptoms: TLS handshake failures

- Checks: days to expiry, full chain, SANs match hostnames

- Actions: renew and deploy certs, reload MinIO, verify with openssl s_client

- Host saturation

- Symptoms: general slowdown, timeouts across ops

- Checks: CPU busy, disk busy time, packet drops

- Actions: add CPU/IOPS, rebalance traffic, fix noisy workloads on shared hosts

## Local pilot plan

Make the first step narrow, measurable, and easy to inspect locally.

- Bring up MinIO and observability stack

# docker-compose.yml
version: "3.8"
services:
 minio:
 image: minio/minio:latest
 command: server --console-address \"\:9001\" /data
 ports:
 - "9000:9000"
 - "9001:9001"
 environment:
 - MINIO_ROOT_USER=minioadmin
 - MINIO_ROOT_PASSWORD=minioadmin123
 volumes:
 - ./data:/data

 prometheus:
 image: prom/prometheus:latest
 command: [\"--config.file=/etc/prometheus/prometheus.yml\\)
 volumes:
 - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
 ports:
 - "9090:9090"

 grafana:
 image: grafana/grafana-oss:latest
 ports:
 - "3000:3000" 
 # prometheus.yml
global:
 scrape_interval: 15s
scrape_configs:
 - job_name: minio
 metrics_path: /minio/v2/metrics/cluster
 static_configs:
 - targets: ["host.docker.internal:9000\) # use "minio:9000" if same network 

- Seed data and verify metrics

 mc alias set local http://localhost:9000 minioadmin minioadmin123
mc mb local/demo
mc cp /etc/hosts local/demo/hosts-copy
curl -s http://localhost:9000/minio/v2/metrics/cluster | head 

- Install the starter alerts

- Load the alert rules above. For local testing, use lower thresholds (for example, capacity at 5%, latency p95 at 200 ms, error ratio at 1%).

- Induce safe test signals

 # Capacity: upload a 50 MB file (repeat as needed)
base64 /dev/zero | head -c 52428800 > bigfile.bin
mc cp bigfile.bin local/demo/

# Target down: stop MinIO container for 2 minutes
docker stop minio && sleep 120 && docker start minio

# Error ratio: send bad auth to produce 4xx (temporarily alert on 4xx)
curl -s -o /dev/null -w "%{http_code}\\n" http://localhost:9000/demo/hosts-copy 

- Validate and record outcomes

- Ensure alerts fire with actionable descriptions and auto-clear after recovery.

- Note any metric name differences and update dashboards/alerts.

 Exit criteria:

- Three core alerts tested end-to-end

- Dashboard shows capacity, latency, and error trends

- Runbooks drafted for each alert

## Post-change verification checklist

Run this after upgrades, config changes, or scaling events:

- API health: p50/p95 latency and error ratio back to baseline

- Capacity: used%, free headroom, growth trend normal

- Targets healthy: up{job="minio"} == 1 for all; scrape latency stable

- Logs clean: no new error types or repeated healing loops

- Replication: backlog steady or decreasing

- Certificates: days-to-expiry above internal target, handshake OK

- Host: CPU and disk busy within expected range

- Alerts: no flapping; silences removed after tests

## Conclusion

You now have a production-focused approach to MinIO monitoring: prioritize availability, capacity, latency, and error signals; back them with clear Prometheus queries, small and actionable alerts, and concise runbooks; and verify everything with a safe local pilot and a post-change checklist. Keep tuning thresholds to your real traffic and SLOs, expand dashboards as you scale, and continuously fold incident lessons back into your alerts and runbooks.