Skip to main content
Guides Last updated: 21 March 2026

Cost Protection

cf-monitor exists because of a real billing incident. This guide explains how it prevents billing surprises on your Cloudflare account.

cf-monitor exists because of a real billing incident. This guide explains how it prevents billing surprises on your Cloudflare account.

The $4,868 story

In January 2026, two bugs on a Cloudflare Workers project produced a combined $4,868 in overage charges:

  • An infinite D1 write loop ran for 3 days before anyone noticed, inserting 4.8 billion rows ($3,434 in D1 write charges)
  • A deployment bug caused a worker to restart repeatedly, each restart triggering a fresh data sync ($910 in additional writes)

The monitoring system at the time was centralised — it collected telemetry from all accounts into a single D1 database. When the monitored projects moved to dedicated Cloudflare accounts, the monitoring broke, and the bugs went undetected.

cf-monitor was built to ensure this never happens again.

Five layers of protection

Layer 1: Per-invocation limits

What: hard limits on binding operations per single worker invocation.

When: checked synchronously, on every proxied method call. A RequestBudgetExceededError is thrown the moment a limit is exceeded.

Default limits: d1Writes: 1000, d1Reads: 5000, kvWrites: 200, kvReads: 1000, aiRequests: 50, r2ClassA: 100, queueMessages: 500.

Why it works: the infinite write loop from January 2026 would have been stopped on its very first invocation at row 1,001.

Layer 2: Hourly budget enforcement

What: daily and monthly budget limits per feature, checked every hour.

When: the budget-check cron runs at 0 * * * *, compares accumulated usage in KV against configured limits.

Thresholds: Slack warning at 70%, critical at 90%, circuit breaker trip at 100%.

Why it works: even if per-invocation limits are generous, this catches sustained overuse within hours rather than discovering it on the invoice.

Layer 3: Circuit breakers

What: automatic kill switches at feature, account, and global levels.

When: tripped by budget enforcement (Layer 2). All subsequent requests to the affected feature return 503. Auto-resets after TTL (default: 1 hour).

Why it works: once a budget is hit, the feature stops immediately. No human intervention needed.

Layer 4: Cost spike detection

What: anomaly detection comparing current hourly costs to a 24-hour baseline.

When: runs every 15 minutes via the cost-spike cron. Alerts when usage exceeds 200% of baseline (configurable).

Why it works: catches unusual patterns that fall within budget limits but are still abnormal — like a sudden 10x increase in D1 reads from a new code path.

Layer 5: Fail-open design

What: if cf-monitor itself has an error, your worker keeps running normally.

When: always. Every internal operation is wrapped in try-catch.

Why it works: monitoring should never become the problem. A KV outage shouldn’t bring down your production workers.

Cloudflare pricing reference

cf-monitor tracks costs using these per-unit prices (Workers Paid plan):

ResourcePrice per unitFree tier
D1 reads$0.25 / 1M queries5B rows/month
D1 writes$0.75 / 1M queries50M rows/month
KV reads$0.50 / 1M10M/month
KV writes$5.00 / 1M1M/month
R2 Class A (mutations)$0.0015 / 1K1M/month
R2 Class B (reads)$0.01 / 1M10M/month
AI neurons$0.011 / 1K10K/day free
Queue messages$0.40 / 1M
DO requests$0.15 / 1M
Vectorize queries$0.01 / 1K30M queried dimensions/month

cf-monitor’s own cost

cf-monitor is designed to add negligible cost to your account:

OperationPer eventTypical daily total
Tail handler~1 KV read + ~1 KV write per unique error< 100 ops
Cron handler~10 KV reads + ~5 AE writes per hourly run~360 ops
AE writesFree (all metrics go to Analytics Engine)0 cost
Total KV ops< 1,000/day

At $0.50/M reads and $5/M writes, cf-monitor costs well under $0.01/day even on busy accounts.

Analytics Engine writes are free up to 100M/month — cf-monitor typically uses a few thousand per day.

Budget configuration examples

Conservative (free plan)

budgets:
  daily:
    d1_writes: 10000
    d1_reads: 100000
    kv_writes: 500
    kv_reads: 10000
  monthly:
    d1_writes: 200000
    kv_writes: 10000

Standard (paid plan)

budgets:
  daily:
    d1_writes: 100000
    d1_reads: 1000000
    kv_writes: 5000
    kv_reads: 100000
    ai_requests: 5000
    r2_class_a: 10000
  monthly:
    d1_writes: 2000000
    kv_writes: 100000

Per-invocation (in worker code)

monitor({
  limits: {
    d1Writes: 100,       // Very tight for a simple API
    kvWrites: 20,
    aiRequests: 5,
  },
  fetch: handler,
});

Further reading

Was this helpful?

Related Articles