Here is a rate limiter that appears in a great many codebases:

const ip = req.headers["cf-connecting-ip"]
  ?? req.headers["x-forwarded-for"]?.split(",")[0]?.trim()
  ?? "unknown";

if (overLimit(ip, 3, DAY)) return res.status(429).end();

Three requests a day per address. Except X-Forwarded-For is a request header, which means the client writes it. Rotate the value on each request and the limit does not exist.

The header is not the problem, the topology is

The instinct is to say "never trust X-Forwarded-For". That is too strong. Behind a proxy that overwrites it, it is exactly the right header. The real rule is about position:

A forwarded-for header is trustworthy only if every path to your origin passes through something that overwrites it.

Cloudflare sets CF-Connecting-IP to the real client address and replaces anything the client sent. So through Cloudflare, that header is authoritative. The moment your origin is reachable by any other route, it is a suggestion.

And origins are reachable more often than people think. A tunnelled service that also binds 0.0.0.0 is exposed to the entire local network even though it looks like it is only on the internet through the tunnel.

Fail into one bucket, not into a free pass

The fix is to trust one header, the one your edge guarantees, and to give everything else a shared bucket rather than its own:

const ip = req.headers["cf-connecting-ip"] ?? "direct";

Requests that did not come through the edge now share the single direct bucket. They are collectively capped instead of individually unlimited. Note the difference from the original: the fallback was unknown, which sounds similar, but because X-Forwarded-For was consulted first, a caller could always avoid that bucket by supplying a value.

Check what the limit is protecting

How much this matters depends on what is behind it. A cap on an endpoint that spends money, runs a model, or sends mail is a cost control, and a bypassed cost control is a bill. A cap on a read endpoint is politeness.

Ours guarded runs that each cost several model calls, which puts it firmly in the first category.

The general shape

This is one instance of a broader mistake: deriving an authorisation or accounting decision from data the caller controls. The same bug appears as trusting a client-supplied user ID, an X-Real-IP, an Origin header used for access control, or a price sent up from the browser.

The question to ask of any value you key a limit on: can the person being limited change it? If yes, the limit is advisory. Write down which component is responsible for making each header trustworthy, and if the answer is "none", stop reading it.