Export Cloudflare metrics to Prometheus. Built on Cloudflare Workers with Durable Objects for stateful metric accumulation.
- 58 Prometheus metrics - requests, bandwidth, threats, workers, load balancers, SSL certs, and more
- Cloudflare Workers - serverless edge deployment
- Durable Objects - stateful counter accumulation for proper Prometheus semantics
- Background refresh - alarms fetch data every 60s; scrapes return cached data instantly
- Rate limiting - 40 req/10s with exponential backoff
- Multi-account - automatically discovers and exports all accessible accounts/zones
- Runtime config API - change settings without redeployment via REST endpoints
- Configurable - zone filtering, metric denylist, label exclusion, custom metrics path, and more
Click the deploy button above. Configure CLOUDFLARE_API_TOKEN as a secret after deployment.
git clone https://github.com/cloudflare/cloudflare-prometheus-exporter.git
cd cloudflare-prometheus-exporter
bun install
wrangler secret put CLOUDFLARE_API_TOKEN
bun run deployConfiguration is resolved in order: KV overrides β env vars β defaults. Use the Runtime Config API for dynamic changes without redeployment.
Set in wrangler.jsonc or via wrangler secret put:
| Variable | Default | Description |
|---|---|---|
CLOUDFLARE_API_TOKEN |
- | Cloudflare API token (secret) |
QUERY_LIMIT |
10000 | Max results per GraphQL query |
SCRAPE_DELAY_SECONDS |
300 | Delay before fetching metrics (data propagation) |
TIME_WINDOW_SECONDS |
60 | Query time window |
METRIC_REFRESH_INTERVAL_SECONDS |
60 | Background refresh interval |
LOG_LEVEL |
info | Log level (debug/info/warn/error) |
LOG_FORMAT |
json | Log format (pretty/json) |
ACCOUNT_LIST_CACHE_TTL_SECONDS |
600 | Account list cache TTL |
ZONE_LIST_CACHE_TTL_SECONDS |
1800 | Zone list cache TTL |
SSL_CERTS_CACHE_TTL_SECONDS |
1800 | SSL cert cache TTL |
HEALTH_CHECK_CACHE_TTL_SECONDS |
10 | Health check cache TTL |
EXCLUDE_HOST |
false | Exclude host labels from metrics |
CF_HTTP_STATUS_GROUP |
false | Group HTTP status codes (2xx, 4xx, etc.) |
DISABLE_UI |
false | Disable landing page (returns 404) |
DISABLE_CONFIG_API |
false | Disable config API endpoints (returns 404) |
METRICS_DENYLIST |
- | Comma-separated list of metrics to exclude |
CF_ACCOUNTS |
- | Comma-separated account IDs to include (default: all) |
CF_ZONES |
- | Comma-separated zone IDs to include (default: all) |
CF_FREE_TIER_ACCOUNTS |
- | Comma-separated account IDs using free tier (skips paid-tier metrics) |
METRICS_PATH |
/metrics | Custom path for metrics endpoint |
Quick setup: Create token with pre-filled permissions
Manual setup:
| Permission | Access | Required |
|---|---|---|
| Zone > Analytics | Read | Yes |
| Account > Account Analytics | Read | Yes |
| Account > Workers Scripts | Read | Yes |
| Zone > SSL and Certificates | Read | Optional |
| Zone > Firewall Services | Read | Optional |
| Zone > Load Balancers | Read | Optional |
| Account > Logpush | Read | Optional |
| Account > Magic Transit | Read | Optional |
| Path | Method | Description |
|---|---|---|
/ |
GET | Landing page (disable: DISABLE_UI) |
/metrics |
GET | Prometheus metrics |
/health |
GET | Health check ({"status":"healthy"}) |
/config |
GET | Get all runtime config (disable: DISABLE_CONFIG_API) |
/config |
DELETE | Reset all config to env defaults (disable: DISABLE_CONFIG_API) |
/config/:key |
GET | Get single config value (disable: DISABLE_CONFIG_API) |
/config/:key |
PUT | Set config override (persisted in KV) (disable: DISABLE_CONFIG_API) |
/config/:key |
DELETE | Reset config key to env default (disable: DISABLE_CONFIG_API) |
scrape_configs:
- job_name: 'cloudflare'
scrape_interval: 60s
scrape_timeout: 30s
static_configs:
- targets: ['your-worker.your-subdomain.workers.dev']Override configuration at runtime without redeployment. Overrides persist in KV and take precedence over wrangler.jsonc env vars.
| Key | Type | Description |
|---|---|---|
queryLimit |
number | Max results per GraphQL query |
scrapeDelaySeconds |
number | Delay before fetching metrics |
timeWindowSeconds |
number | Query time window |
metricRefreshIntervalSeconds |
number | Background refresh interval |
accountListCacheTtlSeconds |
number | Account list cache TTL |
zoneListCacheTtlSeconds |
number | Zone list cache TTL |
sslCertsCacheTtlSeconds |
number | SSL cert cache TTL |
healthCheckCacheTtlSeconds |
number | Health check cache TTL |
logFormat |
"json" | "pretty" |
Log format |
logLevel |
"debug" | "info" | "warn" | "error" |
Log level |
cfAccounts |
string | null | Comma-separated account IDs (null = all) |
cfZones |
string | null | Comma-separated zone IDs (null = all) |
cfFreeTierAccounts |
string | Comma-separated free tier account IDs |
metricsDenylist |
string | Comma-separated metrics to exclude |
excludeHost |
boolean | Exclude host labels |
httpStatusGroup |
boolean | Group HTTP status codes |
# Get all config
curl https://your-worker.workers.dev/config
# Get single value
curl https://your-worker.workers.dev/config/logLevel
# Set override
curl -X PUT https://your-worker.workers.dev/config/logLevel \
-H "Content-Type: application/json" \
-d '{"value": "debug"}'
# Filter to specific zones
curl -X PUT https://your-worker.workers.dev/config/cfZones \
-H "Content-Type: application/json" \
-d '{"value": "zone-id-1,zone-id-2"}'
# Reset to env default
curl -X DELETE https://your-worker.workers.dev/config/logLevel
# Reset all overrides
curl -X DELETE https://your-worker.workers.dev/config| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_requests_total |
counter | zone |
cloudflare_zone_requests_cached |
gauge | zone |
cloudflare_zone_requests_ssl_encrypted |
counter | zone |
cloudflare_zone_requests_content_type |
counter | zone, content_type |
cloudflare_zone_requests_country |
counter | zone, country, region |
cloudflare_zone_requests_status |
counter | zone, status |
cloudflare_zone_requests_browser_map_page_views_count |
counter | zone, family |
cloudflare_zone_requests_ip_class |
counter | zone, ip_class |
cloudflare_zone_requests_ssl_protocol |
counter | zone, ssl_protocol |
cloudflare_zone_requests_http_version |
counter | zone, http_version |
cloudflare_zone_requests_origin_status_country_host |
counter | zone, origin_status, country, host |
cloudflare_zone_requests_status_country_host |
counter | zone, edge_status, country, host |
cloudflare_zone_request_method_count |
counter | zone, method |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_bandwidth_total |
counter | zone |
cloudflare_zone_bandwidth_cached |
counter | zone |
cloudflare_zone_bandwidth_ssl_encrypted |
counter | zone |
cloudflare_zone_bandwidth_content_type |
counter | zone, content_type |
cloudflare_zone_bandwidth_country |
counter | zone, country |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_threats_total |
counter | zone |
cloudflare_zone_threats_country |
counter | zone, country |
cloudflare_zone_threats_type |
counter | zone, type |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_pageviews_total |
counter | zone |
cloudflare_zone_uniques_total |
counter | zone |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_colocation_visits |
counter | zone, colo, host |
cloudflare_zone_colocation_edge_response_bytes |
counter | zone, colo, host |
cloudflare_zone_colocation_requests_total |
counter | zone, colo, host |
cloudflare_zone_colocation_visits_error |
counter | zone, colo, host, status |
cloudflare_zone_colocation_edge_response_bytes_error |
counter | zone, colo, host, status |
cloudflare_zone_colocation_requests_total_error |
counter | zone, colo, host, status |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_firewall_events_count |
counter | zone, action, source, rule, host, country |
cloudflare_zone_firewall_bots_detected |
counter | zone, bot_score, detection_ids |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_health_check_events_origin_count |
counter | zone, health_status, origin_ip, region, fqdn, failure_reason |
cloudflare_zone_health_check_events_avg |
gauge | zone |
cloudflare_zone_health_check_rtt_ms |
gauge | zone, origin_ip, fqdn |
cloudflare_zone_health_check_ttfb_ms |
gauge | zone, origin_ip, fqdn |
cloudflare_zone_health_check_tcp_conn_ms |
gauge | zone, origin_ip, fqdn |
cloudflare_zone_health_check_tls_handshake_ms |
gauge | zone, origin_ip, fqdn |
| Metric | Type | Labels |
|---|---|---|
cloudflare_worker_requests_count |
counter | script_name |
cloudflare_worker_errors_count |
counter | script_name |
cloudflare_worker_cpu_time |
gauge | script_name, quantile |
cloudflare_worker_duration |
gauge | script_name, quantile |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_pool_health_status |
gauge | zone, lb_name, pool_name |
cloudflare_zone_pool_requests_total |
counter | zone, lb_name, pool_name, origin_name |
cloudflare_zone_lb_pool_rtt_ms |
gauge | zone, lb_name, pool_name |
cloudflare_zone_lb_steering_policy_info |
gauge | zone, lb_name, policy |
cloudflare_zone_lb_origins_selected_count |
gauge | zone, lb_name, pool_name |
cloudflare_zone_lb_origin_weight |
gauge | zone, lb_name, pool_name, origin_name |
| Metric | Type | Labels |
|---|---|---|
cloudflare_logpush_failed_jobs_account_count |
counter | account, job_id, destination_type |
cloudflare_logpush_failed_jobs_zone_count |
counter | zone, job_id, destination_type |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_customer_error_4xx_rate |
counter | zone, status, country, host |
cloudflare_zone_customer_error_5xx_rate |
counter | zone, status, country, host |
cloudflare_zone_edge_error_rate |
gauge | zone, status |
cloudflare_zone_origin_error_rate |
gauge | zone, status |
cloudflare_zone_origin_response_duration_ms |
gauge | zone, status, country, host |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_cache_hit_ratio |
gauge | zone |
cloudflare_zone_cache_miss_origin_duration_ms |
gauge | zone, country, host |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_bot_request_by_country |
counter | zone, country |
| Metric | Type | Labels |
|---|---|---|
cloudflare_magic_transit_active_tunnels |
gauge | account |
cloudflare_magic_transit_healthy_tunnels |
gauge | account |
cloudflare_magic_transit_tunnel_failures |
gauge | account |
cloudflare_magic_transit_edge_colo_count |
gauge | account |
| Metric | Type | Labels |
|---|---|---|
cloudflare_zone_certificate_validation_status |
gauge | zone, type, issuer, status |
| Metric | Type | Labels |
|---|---|---|
cloudflare_exporter_up |
gauge | - |
cloudflare_exporter_errors_total |
counter | account_id, error_code |
cloudflare_accounts_total |
gauge | - |
cloudflare_zones_total |
gauge | - |
cloudflare_zones_filtered |
gauge | - |
cloudflare_zones_processed |
gauge | - |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WORKER ISOLATE β
β ββββββββββββββββββ β
β β Worker.fetch βββββ HTTP /metrics, /health, /config β
β β (HTTP handler) β β
β βββββββββ¬βββββββββ β
β β β
β β RPC (stub.export()) β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CONFIG_KV: Runtime config overrides (merged with env defaults) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β
βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DURABLE OBJECT ISOLATES β
β β
β Each DO runs in its own V8 isolate with: β
β - Own CloudflareMetricsClient instance (per-isolate singleton) β
β - Own persistent storage β
β - Own alarm scheduler β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β MetricCoordinator (1 global instance) β β
β β ID: "metric-coordinator" β β
β β State: accounts[], lastAccountFetch β β
β β Cache TTL: 600s (account list) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β RPC β
β ββββββββββββββΌβββββββββββββ β
β βΌ βΌ βΌ β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β AccountMetric β β AccountMetric β β AccountMetric β β
β β Coordinator β β Coordinator β β Coordinator β β
β β account:acct1 β β account:acct2 β β account:acct3 β β
β β Alarm: 60s β β Alarm: 60s β β Alarm: 60s β β
β β Zone TTL: 1800s β β Zone TTL: 1800s β β Zone TTL: 1800s β β
β βββββββββ¬ββββββββββ βββββββββ¬ββββββββββ βββββββββ¬ββββββββββ β
β β RPC β β β
β ββββββββ΄ββββββ ββββββββ΄ββββββ ββββββββ΄ββββββ β
β βΌ βΌ βΌ βΌ βΌ βΌ β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β βExprtβ βExprtβ βExprtβ βExprtβ βExprtβ βExprtβ β
β β(13) β .. β(N) β β(13) β .. β(N) β β(13) β .. β(N) β β
β βacct β βzone β βacct β βzone β βacct β βzone β β
β βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ β
β β
β MetricExporter DOs (per account): β
β - Account-scoped (13): worker-totals, logpush-account, magic-transit, β
β http-metrics, adaptive-metrics, edge-country-metrics, colo-metrics, β
β colo-error-metrics, request-method-metrics, health-check-metrics, β
β load-balancer-metrics, logpush-zone, origin-status-metrics β
β - Zone-scoped (N per account, 1 per zone): ssl-certificates β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β CloudflareMetricsClient (per-isolate) β β
β β - urql Client (GraphQL) β β
β β - Cloudflare SDK (REST) β β
β β - DataLoader: firewallRulesLoader (batches Promise.all calls) β β
β β - Global Rate limiter: 40 req/10s with exponential backoff β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββ GET /metrics ββββββββββ
βPrometheusββββββββββββββββββΆβ Worker β
β Server β β .fetch β
ββββββββββββ βββββ¬βββββ
β
ββββββββββββββββββββββββ΄βββββββββββββββββββββββ
β MetricCoordinator β
β β
β 1. Check account cache (TTL: 600s) β
β 2. If stale β getAccounts() β
β 3. Fan out to AccountMetricCoordinators β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββ
β
ββββββββββββββββββββββββββΌβββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ
β AccountMetric β β AccountMetric β β AccountMetric β
β Coordinator β β Coordinator β β Coordinator β
β (Account A) β β (Account B) β β (Account C) β
β β β β β β
β 1. Check if β β β β β
β refresh() β β (parallel) β β (parallel) β
β needed β β β β β
β 2. Fan out to β β β β β
β exporters β β β β β
βββββββββ¬βββββββββ βββββββββ¬βββββββββ βββββββββ¬βββββββββ
β β β
βββββββ΄ββββββ βββββββ΄ββββββ βββββββ΄ββββββ
βΌ βΌ βΌ βΌ βΌ βΌ
βββββββ βββββββ βββββββ βββββββ βββββββ βββββββ
βExprtβ...βExprtβ βExprtβ...βExprtβ βExprtβ...βExprtβ
β13+N β β β β13+N β β β β13+N β β β
β β β β β β β β β β β β
β ret β β ret β β ret β β ret β β ret β β ret β
βcacheβ βcacheβ βcacheβ βcacheβ βcacheβ βcacheβ
ββββ¬βββ ββββ¬βββ ββββ¬βββ ββββ¬βββ ββββ¬βββ ββββ¬βββ
β β β β β β
ββββββ¬βββββ ββββββ¬βββββ ββββββ¬βββββ
β β β
ββββββββββββββββββββββΌβββββββββββββββββββββ
β
βΌ
βββββββββββββββββββ
β FAN-IN: Merge β
β all metrics + β
β serialize to β
β Prometheus fmt β
ββββββββββ¬βββββββββ
β
βΌ
βββββββββββββββββββ
β HTTP Response β
β text/plain β
βββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β NOTE: Request path is FAST - just reads cached metrics β
β No network calls to Cloudflare API during scrape β
β (unless account list cache is stale) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββ
β ALARM TRIGGERS β
β AccountMetricCoordinator: every 60s β
β MetricExporter: every 60s + 1-5s fixed jitterβ
ββββββββββββββββββββββββββββββββββββββββββββββββ
AccountMetricCoordinator.alarm()
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AccountMetricCoordinator.refresh() β
β β
β 1. Check zone cache (TTL: 1800s / 30 min) β
β β
β 2. If stale: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β REST: getZones(accountId) β β
β β βββΊ DataLoader batches if multiple calls same tick β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β REST: getFirewallRules(zoneId) Γ N zones (parallel) β β
β β βββΊ DataLoader batches parallel calls β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 3. Push context to MetricExporter DOs: β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Account-scoped (13 exporters): β β
β β exporter.updateZoneContext(accountId, accountName, zones) β β
β β β β
β β Zone-scoped (N exporters, 1 per zone): β β
β β exporter.initializeZone(zone, accountId, accountName) β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β 4. Schedule next alarm (60s) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MetricExporter.alarm()
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MetricExporter.refresh() for account-scoped queries β
β β
β Query Types (13 total): β
β βββ ACCOUNT-LEVEL (single account per query, 3): β
β β βββ worker-totals β
β β βββ logpush-account β
β β βββ magic-transit β
β β β
β βββ ZONE-LEVEL (all zones batched in one query, 10): β
β βββ http-metrics β
β βββ adaptive-metrics β
β βββ edge-country-metrics β
β βββ colo-metrics β
β βββ colo-error-metrics β
β βββ request-method-metrics β
β βββ health-check-metrics β
β βββ load-balancer-metrics β
β βββ logpush-zone β
β βββ origin-status-metrics β
β β
β After fetch: Process counters β Cache metrics β Schedule next alarm β
β Jitter: 1-5s fixed (tighter clustering for time range alignment) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
bun install # Install dependencies
bun run dev # Run locally (port 8787)
bun run check # Lint + format check
bun run deploy # Deploy to Cloudflare- Hono - Web framework
- urql - GraphQL client
- gql.tada - Type-safe GraphQL
- Zod - Schema validation
- DataLoader - Request batching
- Cloudflare SDK - REST API client
- Cloudflare KV - Runtime config persistence
MIT
