On 26 July 2026 Cloudflare sent me a polite email to let me know I was spending more than double what I was allowed. A public site I maintain, a side project away from the store, had been saying the same thing in its own way, with less courtesy: it went down every so often. A badly placed cache wrote on every visit, even when nothing had changed: 2,400 writes a day against a limit of 1,000 a day.
On 25 July, one day earlier, we had moved that site off the VPS onto the free plan and I was sleeping well: no server to patch, no cron job on a machine somebody has to remember to restart. How much we were consuming, I had no idea.
Every resource in the plan has its own limit
I thought of the free plan as one single tank, and that was my mistake. They are separate pockets, each with its own limit: KV writes (the small key and value store, 1,000 a day), Worker invocations, rows read in D1, R2 storage, Workers AI usage, cron triggers and GitHub Actions minutes.
It works like a prepaid phone plan with separate balances. You run out of data and the phone stops telling you where your combi is (the minibus everyone takes across Lima), even with all your call minutes still sitting there. Running out in one pocket takes the site down while the others sit nearly empty. We blew the smallest one of all.
The site going down every so often
Me sleeping fine with no server to patch
The meter runs every day at the same hour
A script with no dependencies runs once a day on GitHub Actions: it reads the real usage of each resource and compares it against its free limit. From 70% up it warns, and past 90% it fails the build. In the same run it checks that the pages respond and that the site's data is fresh.
We had tried a daily email in red for a problem no automation could fix, and in a week all of us learned to ignore it. Then we tried the opposite, a quiet warning with the build still green, which hid the quota problem for days. Now the status lives in a GitHub issue that gets updated, instead of a new email every morning.
The quota stayed at 756 after my guess
With the cache fixed, the meter left KV writes projected at 756 of 1,000 a day, still in warning territory. The explanation took me two seconds: we were missing conditional writes, writing only when the value changes. We went to the code and they had been implemented all along. Guessing cost me an afternoon, and the quota stayed at 756, just as firm.
Two keys nobody ever asked for
We put a meter on each pipe: one log line for every write to KV, and we let the site run for 19 minutes to see which one was ticking. Two internal bookkeeping keys showed up on that list, the cursors where two processes that bring data into the site note how far they got, and they took 30% of the daily quota. No browser ever reads them.
We moved the two cursors to D1, the SQL database on the same plan, which allows 100,000 writes a day. The same work went from 30% of the small quota to 0.3% of the big one. We left a test in CI, the automated review that runs with every change, that fails if a cursor goes back to KV, so nobody has to remember the rule.
What the meter costs and what it doesn't solve
The meter also spends from a pocket: about 2 minutes per run, close to 60 of the 2,000 free Actions minutes a month. By that same arithmetic, another scheduled job on the site went from running every 15 minutes to every 30: it burned the most Actions minutes and its pocket was the tightest one of all.
The meter only warns. If the site's traffic grows for good, no script is going to save us: we pay, or we move the work to another pocket.
When something consumes more than it should, I look first at which meter is ticking, and we don't migrate anything without its log line. Cloudflare hasn't written to me again, and the site isn't complaining either.
