| |

Beating the Bot (Without Cheating)

How an innocent uptime check turned into a practical lesson in Cloudflare’s order of operations, free‑tier realities, and why understanding constraints matters more than fighting them.


The Original Goal (Simple, Right?)

The task sounded trivial:

“Stand up Uptime Kuma on a remote cloud host so it can monitor my self‑hosted lab from the outside.”

I deployed Uptime Kuma on an AWS Lightsail instance using Docker Compose, pointed it at my services, and waited for the comforting green glow of successful health checks.

Instead, I was greeted with a sea of red.
Every check failed with a 403 Forbidden.
Cloudflare, it seemed, had opinions.


The Initial Diagnosis

All of my services sit behind Cloudflare Tunnel, protected by:

  • Cloudflare Access (Zero Trust)
  • WAF rules
  • Bot protections

From Cloudflare’s perspective, Uptime Kuma looked like:

  • A non‑browser
  • An automated client
  • Definitely not a human

Which, to be fair, is accurate.

Cloudflare was doing exactly what it was designed to do: stop bots. Unfortunately, my bot was on my side.


What I Tried First (The Sensible Stuff)

The obvious attempts came first:

  • IP allow rules
  • Custom WAF exceptions
  • Access policy tweaks

On paper, these should have worked.

In practice, they didn’t.

Why? Because on the Cloudflare Free tier, Bot Fight Mode runs before:

  • Custom WAF rules
  • Access policies
  • Any clever logic you might hope to apply

Which means your carefully written allow rules never even get a chance to execute.

Bot Fight Mode has already rendered its verdict.


Reading the Fine Print (And Then the Smaller Print)

After digging through documentation, community posts, and more than a few frustrated threads, the key realization surfaced:

  • Super Bot Fight Mode (paid) allows granular bypass rules
  • Bot Fight Mode (Free) is global, blunt, and unapologetic

On the Free tier:

  • You cannot bypass Bot Fight Mode per IP
  • You cannot scope it per hostname
  • You cannot change the evaluation order

The choice was binary:

  1. Accept broken external monitoring
  2. Disable Bot Fight Mode globally

Neither option felt particularly satisfying.


The Workaround (a.k.a. Thinking Sideways)

Instead of fighting a control I couldn’t influence, I removed it and replaced its intent.

Here’s what the final setup looks like:

  1. Bot Fight Mode disabled globally
  2. Custom WAF rule added:
  • If traffic is NOT from my AWS Lightsail IPs
  • AND does NOT present a specific custom header
  • Route the request to a JS Challenge page

In plain English:

If you’re not my monitoring system, and you don’t know the secret handshake, prove you’re a real browser.

Uptime Kuma:

  • Comes from a known IP
  • Sends the correct header
  • Never sees the JS challenge

Everyone else:

  • Gets JavaScript
  • Must behave like a browser
  • Is politely shown the door if they can’t

Is This Perfect? No. Is It Good Enough? Yes.

Let’s be honest about the trade‑offs.

This approach:

  • Blocks basic bots and scanners
  • Stops non‑browser automation
  • Preserves external monitoring
  • Works entirely on the Free tier

It does not:

  • Stop sophisticated headless browsers
  • Replace paid bot intelligence
  • Magically turn Free into Enterprise

But for a tunneled, authenticated, non‑public homelab, it strikes a pragmatic balance.


Lessons Learned

  • Constraints are part of the system, not obstacles to ignore. The Free tier isn’t broken — it’s opinionated. Effective leaders design within constraints rather than burning cycles fighting them.

  • Understand intent, not just implementation. Bot Fight Mode wasn’t the real requirement. Preventing abusive automation was. Once that distinction was clear, alternative designs became obvious.

  • Security decisions are business decisions. Paying for a feature, disabling a control, or implementing a workaround all carry cost, risk, and complexity. The “right” answer depends on context, not dogmatic rule sets.

  • Good enough beats perfect when perfect isn’t available. Waiting for an ideal solution would have meant no external monitoring at all — a worse outcome than an informed compromise.

  • Engineering maturity shows in trade‑off transparency. Knowing why a control exists, what it protects against, and what it doesn’t is more valuable than blindly enabling every checkbox.


The Takeaway

This wasn’t about breaking Cloudflare’s rules.

It was about understanding how the system actually works, accepting the limits of the platform, and making deliberate, informed choices inside those limits.

Sometimes security engineering isn’t about disabling protections or throwing money at a problem.

Sometimes it’s about bending the rules just enough — not to break them — but to make them work for you.

Think of it less like cheating…

…and more like solving the maze by realizing the walls don’t go all the way to the ceiling.


Written by David and his AI assistant.1

  1. There is a lot of work here and I can tend to go down a technical rabbit hole. I have used AI to help keep me more on track and keep things at a higher level. ↩︎