I was reviewing our security posture this week and realized we had a gap: while Cloudflare's WAF dashboard shows aggregate data, we didn't have a durable, queryable log of every individual request that was blocked. I wanted this for auditing and to fine-tune our rules without guesswork.
Turns out, you don't need an Enterprise plan or a complex setup. A single Cloudflare Workers script can log every blocked request (WAF, Firewall Rules, etc.) directly to an R2 bucket. The key is using the `request.cf.botManagement` and `request.cf.securityEvents` properties available in the Workers runtime. If a request is blocked, these contain the relevant details.
Here's the core idea of the script:
* It runs as a firewall-only Worker on your zone.
* On every request, it checks if `request.cf.securityEvents` exists and has length.
* If it does, that means Cloudflare's security layer blocked the request.
* The script then constructs a log object (with timestamp, client IP, hostname, action, security event details, and bot score) and writes it as a JSON line to your R2 bucket.
The main benefits I've found:
- **Cost-effective:** R2 storage is very cheap compared to some logging services.
- **Centralized:** All blocks (WAF, firewall rules, rate limiting, etc.) go to one place.
- **Usable:** You can analyze the logs with your own tools, or even use Workers to query/search them.
A couple of gotchas to watch for:
- Be mindful of log volume. You might want to add a sampling mechanism or filter only for certain high-severity events if you have a huge site.
- Remember to set up your R2 bucket's lifecycle rules to automatically delete or archive old logs if needed.
This has been a game-changer for our team's visibility. Has anyone else built a similar logging pipeline? I'm curious if you've added other data points or set up automated analysis from these logs.
Keep it real
That's a clever use of the securityEvents property! I've used similar patterns for logging to analytics platforms, but R2 for raw logs is smart. Have you thought about the object namespace structure in your bucket? A flat dump of JSON lines works, but for any real volume, you'll probably want to partition by date/hour to make querying with something like Athena or DuckDB easier later.
One caveat: watch out for Workers invocation costs if you have a very high-traffic zone with a lot of blocked requests. The firewall-only execution helps, but it's not free. Might be worth adding a quick sample rate check if the numbers get wild.