The Problem: Data Fragmentation Between Edge and Origin Servers
Most modern systems deploy Cloudflare as a front-line reverse proxy to leverage its CDN, DNS, and WAF for DDoS protection. Everything runs smoothly until an incident strikes. While the default Cloudflare dashboard is intuitive, it remains entirely decoupled from your backend server infrastructure.
Picture a familiar scenario: users start complaining about slow load times or 502 Bad Gateway errors. You find yourself juggling multiple tabs—one on Cloudflare inspecting incoming requests, another on Grafana monitoring CPU and RAM, and a terminal window grepping through Nginx access logs. Context switching between three or four different tools easily adds 10 to 15 minutes to your incident response time. Furthermore, free-tier accounts only retain detailed analytics for 24 to 72 hours, making it nearly impossible to review last week’s data to investigate a bot scraping campaign.
The most effective solution is to ingest all metrics from Cloudflare’s GraphQL Analytics API into Prometheus via cloudflare-exporter. By bringing edge network metrics and origin server telemetry together onto a single Grafana dashboard, you can pinpoint within seconds whether an issue stems from edge network bottlenecks or an overloaded backend.
Deploying Cloudflare Exporter
Cloudflare Exporter is a lightweight Go-based service that acts as an intermediary adapter. It periodically queries Cloudflare’s GraphQL API for telemetry and formats it into standard Prometheus metrics exposed at the /metrics endpoint.
Step 1: Create a Cloudflare API Token
For the exporter to access your metrics, you should provision an API token with read-only analytics permissions rather than using your Global API Key (which carries excessive privileges and security risks):
- Log in to the Cloudflare dashboard and navigate to My Profile > API Tokens.
- Click Create Token > select the Read analytics and logs template (or select Create Custom Token).
- Configure the minimum required permissions for the token:
- Zone > Zone > Read
- Zone > Analytics > Read
- Under Zone Resources: Select Include > Specific zone and choose the target domain to enforce least privilege (or select All zones if managing multiple domains).
- Click Continue to summary > Create Token, then copy the generated token and store it securely in a password manager.
Step 2: Run Cloudflare Exporter with Docker Compose
Docker Compose provides the cleanest approach to container management. Create a working directory on your monitoring server:
mkdir -p /opt/cloudflare-exporter && cd /opt/cloudflare-exporter
nano docker-compose.yml
Paste the following configuration into your docker-compose.yml file:
version: '3.8'
services:
cloudflare-exporter:
image: ghcr.io/lablabs/cloudflare-exporter:latest
container_name: cloudflare_exporter
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
- CF_API_TOKEN=your_cloudflare_token_here
- CF_ZONES=example.com,app.example.com
- SCRAPE_INTERVAL=60s
- METRICS_DENYLIST=cloudflare_zone_colocation_.*
Tip: The METRICS_DENYLIST directive discards per-colocation/datacenter metrics. This configuration eliminates up to 80% of unnecessary time-series data, preventing Prometheus memory bloat.
Start the container:
docker compose up -d
Verify that the exporter has successfully connected to Cloudflare:
curl -s http://127.0.0.1:8080/metrics | grep cloudflare_zone_requests_total
If the terminal returns entries like cloudflare_zone_requests_total{zone="example.com"} 15420 with an increasing count, the exporter is working as expected.
Configuring Prometheus to Scrape Metrics
Once the exporter is running reliably, register it as a target in Prometheus to start ingesting time-series data.
Adding the Scrape Job to prometheus.yml
Open your Prometheus configuration file:
sudo nano /etc/prometheus/prometheus.yml
Append the following job to the scrape_configs section:
scrape_configs:
- job_name: 'cloudflare'
scrape_interval: 60s
scrape_timeout: 30s
static_configs:
- targets: ['127.0.0.1:8080']
labels:
env: 'production'
Technical note: Cloudflare Analytics API aggregates metrics in 1-minute blocks. Setting scrape_interval below 60s (e.g., 15s) provides no additional data resolution and risks hitting Cloudflare API rate limits (1,200 requests per 5 minutes).
Apply the new configuration (requires Prometheus to be running with the --web.enable-lifecycle flag):
curl -X POST http://localhost:9090/-/reload
# Or restart via systemd if the lifecycle API is not enabled:
# sudo systemctl reload prometheus
Core Metrics Overview
Here are the 6 most critical metrics to focus on:
cloudflare_zone_requests_total: Total incoming requests hitting the Cloudflare edge.cloudflare_zone_requests_status: Request volume broken down by HTTP response status code (200, 301, 403, 502, etc.).cloudflare_zone_requests_cached: Number of requests served directly from the CDN cache (Cache Hits).cloudflare_zone_bandwidth_total: Total bandwidth consumed (in bytes).cloudflare_zone_threats_total: Number of malicious requests mitigated by WAF and IP Firewall rules.cloudflare_zone_pool_health_status: Origin server health status if you utilize Cloudflare Load Balancing.
Building an Intuitive Grafana Dashboard
With metrics streaming reliably into Prometheus, you can now construct visual dashboards to monitor system health in real time.
1. Essential PromQL Queries
Navigate to Grafana > Explore and test the following three PromQL queries:
Cache Hit Ratio (%) — Measuring origin server offload efficiency:
(
sum(rate(cloudflare_zone_requests_cached[5m]))
/
sum(rate(cloudflare_zone_requests_total[5m]))
) * 100
For e-commerce or content-heavy news sites, this ratio should ideally stay between 70% and 85%. If it drops below 40%, review your Cache Rules and the Cache-Control HTTP headers configured on Nginx.
Backend HTTP Error Rate (502/504):
sum by (status) (rate(cloudflare_zone_requests_status{status=~"5.."}[5m]))
Set an alert rule for this query: if 502/504 error rates exceed 5 req/s continuously for 2 minutes, trigger an immediate notification to Telegram or Slack for the on-call backend engineering team.
Hourly WAF Blocked Threats:
sum by (zone) (increase(cloudflare_zone_threats_total[1h]))
Under normal conditions, this metric typically fluctuates between dozens and a few hundred spam requests. A sudden spike to 50,000–100,000 in an hour strongly indicates an active vulnerability scan or a Layer 7 DDoS attack.
2. Practical Dashboard Layout
A battle-tested production monitoring layout typically features four core panels:
- Top row: Two gauge panels displaying Cache Hit Ratio and Current RPS (Requests Per Second).
- Second row: A graph comparing Edge Bandwidth vs Origin Bandwidth to highlight how much traffic Cloudflare is offloading from your origin servers.
- Third row: A time-series chart showing the HTTP Status Breakdown (segmented into 2xx, 4xx, and 5xx ranges).
- Bottom row: A bar chart displaying Top WAF Threats blocked per domain.
If you prefer not to build the dashboard from scratch, import the community dashboard with ID 13133 into Grafana and connect it to your Prometheus data source.
Once integrated, a single unified screen allows you to instantly correlate edge traffic with backend performance—verifying whether a traffic surge impacted backend CPU load or was cleanly mitigated at the edge by the WAF. Pinpointing and resolving incidents becomes significantly faster and more intuitive.
