Skip to main content

OpenClaw Monitoring & Observability

TL;DR

  • OpenClaw >= 2.3.0 exposes a /metrics endpoint in Prometheus format
  • Add Prometheus + Grafana to your Docker Compose stack (20 extra lines in the Compose file)
  • Key metrics: openclaw_sessions_active, openclaw_llm_latency_seconds, openclaw_skill_errors_total
  • Alert on: session spike, LLM P95 latency > 10s, skill error rate > 5%

Why observability matters for self-hosted AI

An unmonitored OpenClaw instance fails silently in ways that confuse users: LLM API key quotas exhaust and the assistant starts returning empty responses; a skill crashes repeatedly and users just see "OpenClaw is thinking…"; Redis fills up and session state goes stale. Prometheus + Grafana catches all three in minutes.

Prerequisites

  • The Docker Compose stack already running (OpenClaw + Redis + Caddy)
  • OpenClaw >= 2.3.0 (check with docker compose exec openclaw openclaw --version)
  • Ports 9090 (Prometheus) and 3000 (Grafana) available on the host (or proxied through Caddy)

Extended docker-compose.yml

Add these three services to your existing docker-compose.yml:

prometheus:
image: prom/prometheus:v2.51.0
restart: unless-stopped
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.retention.time=30d"
- "--web.enable-lifecycle"
networks:
- openclaw_net

grafana:
image: grafana/grafana:10.4.2
restart: unless-stopped
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-changeme}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana_data:/var/lib/grafana
- ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
- ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources:ro
depends_on:
- prometheus
networks:
- openclaw_net

alertmanager:
image: prom/alertmanager:v0.27.0
restart: unless-stopped
volumes:
- ./monitoring/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
networks:
- openclaw_net

Add volumes:

prometheus_data:
grafana_data:

Add to your Caddyfile if you want Grafana behind HTTPS:

grafana.yourdomain.com {
reverse_proxy grafana:3000
}

prometheus.yml

Create monitoring/prometheus.yml:

global:
scrape_interval: 15s
evaluation_interval: 15s

alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]

rule_files:
- /etc/prometheus/rules/*.yml

scrape_configs:
- job_name: "openclaw"
static_configs:
- targets: ["openclaw:18789"]
metrics_path: "/metrics"

- job_name: "redis"
static_configs:
- targets: ["redis:9121"]

- job_name: "caddy"
static_configs:
- targets: ["caddy:2019"]
metrics_path: "/metrics"

Note: Redis metrics require the redis_exporter sidecar. Add it to your Compose file if you want Redis metrics beyond what OpenClaw reports indirectly.

Key metrics

OpenClaw's /metrics endpoint exposes:

MetricTypeDescription
openclaw_sessions_activeGaugeCurrently open sessions across all platforms
openclaw_messages_totalCounterTotal messages processed; label: platform, status
openclaw_llm_latency_secondsHistogramTime from message received to LLM response; label: provider, model
openclaw_llm_errors_totalCounterLLM API errors; label: provider, error_type
openclaw_skill_invocations_totalCounterSkill invocations; label: skill_name, status
openclaw_skill_errors_totalCounterFailed skill invocations; label: skill_name, error_type
openclaw_redis_operations_totalCounterRedis read/write ops; label: operation
openclaw_context_tokens_totalCounterTokens sent to LLM API; label: provider, model

Alert rules

Create monitoring/rules/openclaw.yml:

groups:
- name: openclaw
rules:
- alert: HighLLMLatency
expr: histogram_quantile(0.95, rate(openclaw_llm_latency_seconds_bucket[5m])) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "OpenClaw LLM P95 latency > 10s for 5 minutes"
description: "Check LLM provider status and network connectivity from the VPS."

- alert: HighSkillErrorRate
expr: rate(openclaw_skill_errors_total[5m]) / rate(openclaw_skill_invocations_total[5m]) > 0.05
for: 2m
labels:
severity: warning
annotations:
summary: "OpenClaw skill error rate > 5%"
description: "Skill {{ $labels.skill_name }} is failing. Check docker compose logs openclaw."

- alert: SessionSpike
expr: openclaw_sessions_active > 50
for: 1m
labels:
severity: info
annotations:
summary: "OpenClaw active sessions > 50"
description: "Unusual session spike. Check for bot traffic or integration misconfiguration."

- alert: LLMAPIKeyExhausted
expr: increase(openclaw_llm_errors_total{error_type="rate_limit"}[10m]) > 10
for: 1m
labels:
severity: critical
annotations:
summary: "LLM API rate limit errors spiking"
description: "API key may be exhausted or rate limited. Check your Anthropic/OpenAI dashboard."

alertmanager.yml

route:
group_by: ["alertname"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: "slack"

receivers:
- name: "slack"
slack_configs:
- api_url: "${SLACK_WEBHOOK_URL}"
channel: "#openclaw-alerts"
title: "OpenClaw Alert: {{ .CommonLabels.alertname }}"
text: "{{ range .Alerts }}{{ .Annotations.description }}{{ end }}"

Add SLACK_WEBHOOK_URL and GRAFANA_ADMIN_PASSWORD to your .env.

Grafana datasource provisioning

Create monitoring/grafana/datasources/prometheus.yml:

apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
url: http://prometheus:9090
isDefault: true
editable: false

Useful PromQL queries

# LLM P95 latency (last 5 minutes)
histogram_quantile(0.95, rate(openclaw_llm_latency_seconds_bucket[5m]))

# Token spend rate (tokens/hour)
rate(openclaw_context_tokens_total[1h]) * 3600

# Top 5 failing skills
topk(5, rate(openclaw_skill_errors_total[10m]))

# Messages per platform (last hour)
sum by (platform) (increase(openclaw_messages_total[1h]))

Runtime footprint of the monitoring stack

Measured 2026-05-01 on local Docker (4 vCPU / 4 GB RAM). The monitoring stack is designed to run on the same VPS as OpenClaw without requiring an upgrade:

ServiceIdle RAM
prometheus80 MB
grafana120 MB
alertmanager25 MB
Total monitoring225 MB

This is acceptable on a 4 GB VPS (total stack: ~460 MB idle with monitoring). If you add a local Ollama backend, move to an 8 GB VPS.

When this monitoring setup isn't enough

  • Multi-instance OpenClaw. Use a shared Prometheus with federation or Thanos for horizontal scale.
  • Long-term metric retention > 30 days. Configure remote_write to a hosted Prometheus (Grafana Cloud free tier works well).
  • Compliance audit trail. Metrics don't satisfy HIPAA/SOC audit requirements. For regulated deployments, add structured log shipping (Loki or Elasticsearch) and retain logs per your compliance policy. See the HIPAA deployment guide.
Frequently Asked Questions

Either add a Caddy reverse proxy entry (as shown above) or use an SSH tunnel: ssh -L 3000:localhost:3000 user@your-vps. The tunnel is sufficient for solo use; the Caddy proxy is better for teams.

Yes. Add OpenClaw's /metrics endpoint to your existing Prometheus scrape config and import the dashboard JSON into your Grafana. Skip the prometheus, grafana, and alertmanager services in the Compose file.

It lets you estimate your LLM API spend. Multiply by the per-token price for your model and provider. Example: at 500k tokens/day on claude-sonnet-4-6 (~$3 per million input tokens), you'd see roughly $1.50/day before output tokens. Track this metric to catch runaway usage before your bill surprises you.

Common mistakes and fixes

Prometheus shows 'No data' for openclaw_* metrics.

OpenClaw must be running version >= 2.3.0 for the /metrics endpoint to exist. Check the image version with: docker compose exec openclaw openclaw --version. If the endpoint returns 404, upgrade OPENCLAW_VERSION in your .env and run docker compose pull openclaw && docker compose up -d.

Grafana dashboard shows no panels after import.

The dashboard JSON references a Prometheus data source named 'Prometheus'. Check Settings → Data Sources in Grafana and confirm the data source name matches exactly. If it's named differently, edit the JSON and replace all 'Prometheus' references with your source name.

Alert Manager fires alerts but no notification arrives.

Confirm your alertmanager.yml receiver config is correct. For Slack: test the webhook URL with curl -X POST -H 'Content-type: application/json' --data '{"text":"test"}' YOUR_WEBHOOK_URL. For email: check SMTP auth errors in the Alertmanager container logs: docker compose logs alertmanager.

Apify Affiliate Banner 728x90Apify Affiliate Banner 728x90Apify Affiliate Banner 300x50Apify Affiliate Banner 300x50