Why Query-String URLs Waited for Three Requests Before Caching
Cloudflare’s CDN has long cached static assets after the first request, serving subsequent requests from the data center closest to the visitor. But for URLs containing query strings—such as https://example.com/image.jpg?width=500—the behavior was different. The asset had to be requested three times before it would be stored in the cache. Until recently, that quirk had been in place since 2010.
Here’s what that looked like in practice. Requesting the same image five times without a query string yields cache misses followed by hits:
❯ for i in {1..5}; do curl -svo /dev/null example.com/image.jpg 2>&1 | grep -e 'CF-Cache-Status'; sleep 3; done
< CF-Cache-Status: MISS
< CF-Cache-Status: HIT
< CF-Cache-Status: HIT
< CF-Cache-Status: HIT
< CF-Cache-Status: HIT
Repeating the request with ?query=val appended changes the picture: three consecutive MISS responses means two extra trips to the origin server before the asset is finally cached:
❯ for i in {1..5}; do curl -svo /dev/null example.com/image.jpg\?query\=val 2>&1 | grep -e 'CF-Cache-Status'; sleep 3; done
< CF-Cache-Status: MISS
< CF-Cache-Status: MISS
< CF-Cache-Status: MISS
< CF-Cache-Status: HIT
< CF-Cache-Status: HIT
Why the Original Design Made Sense
The rationale for forcing three misses on query-string URLs was rooted in protecting against unnecessary disk writes. Many query strings are effectively one-off requests:
- Some pass data to the origin that doesn’t change the response—for example, browser metadata—so the same response gets stored behind many unique cache keys.
- Others are deliberate “cache busting” attempts, where randomized query strings are appended to force an origin fetch, a technique used by some ad managers for impression counting.
One-hit wonders weren’t a new problem. Cloudflare already addressed them with an in-memory transient cache that stores single-use requests without ever writing them to disk. That mechanism alone reduced disk writes by 20–25% for the subset of traffic where it was enabled:

Re-evaluating a Decade-Old Decision
By the time the query-string behavior was re-examined, Cloudflare’s network had grown from 10,000 to 25 million Internet properties. The question was whether the old rule was still worth its cost. Metrics showed only about 3.5% of requests used the default cache level with query strings, so the expected impact on disk writes was small. Cache hit rate wasn’t expected to improve dramatically either.
To get real-world numbers, Cloudflare ran an A/B test in a single data center, with half the machines using the old behavior and half caching query-string URLs on the first request. The transient cache was disabled during the experiment to observe the upper bound of I/O costs. After two days, the results matched the hypotheses:
- Disk writes per request increased by about 2.5%—acceptable.
- Cache hit rate rose roughly 3% on average for Enterprise customers.
- Total bytes served from origin dropped by 5%, representing real bandwidth savings.

What the Data Revealed
The hit-rate improvements were telling. If most query-string requests were true one-hit wonders, caching them earlier shouldn’t have changed the hit rate at all. The fact that hit rates rose—across all plan types, with the biggest gains for customers serving images with varied query parameters like ?resize=100px:*&output-quality=60—suggested the old behavior was over-defensive. A meaningful portion of query-string URLs were popular enough that caching them on first request delivered measurable benefits.
The experiment confirmed that the 2010-era caution wasn’t nearly as effective at preventing disk writes as newer mechanisms like the transient cache. Weighing the modest I/O cost against the hit-rate and bandwidth gains, the decision was clear.

The three-request rule has now been retired. Query-string URLs are cached on the first request, just like everything else.



