Last year a client called me in a panic after their monthly CDN bill jumped 42% while their bandwidth stayed flat. They were serving the same 80TB they'd served the month before, but they'd pushed a new single-page app release that polled an API endpoint every three seconds. At $0.0075 per 10,000 requests, that tiny polling loop added nearly 800 million extra edge requests, and suddenly their per-request cost was bigger than their egress bill. If you've only ever watched bandwidth graphs, request pricing can feel invisible until it bites you.
Why Per-Request Fees Quietly Outgrow Bandwidth Bills
Most teams I've worked with focus on the big number — gigabytes transferred — because that's how we were taught to think about CDN cost. But every major provider charges separately for requests, and that line item doesn't scale linearly with bytes. You're paying for the work the edge has to do: TLS termination, cache lookup, rule execution, logging. For large video files or software downloads, request pricing is noise. For small objects like API JSON, 1x1 tracking pixels, chatty thumbnails, or uncached images, it can dominate your total spend.
I've seen this pattern for over a decade, and it always shows up the same way. A product team ships a feature that seems cheap because each payload is 2KB, then traffic grows and finance asks why the CDN Egress Cost Modeling: Understanding and Reducing Your Largest Bill Component forecast was off by 30%. The answer is simple math. One million requests for a 2KB JSON response is only 2GB of egress — maybe $0.16 in bandwidth — but at $0.009 per 10k HTTPS requests, those same requests cost $0.90. Multiply that by a few hundred million requests per day and you've got a real budget problem. CDN pricing analysis has to account for both axes, not just bytes.
The tricky part is that per-request cost tiers vary wildly. CloudFront, Fastly, Cloudflare (on enterprise contracts), and Akamai all publish different rates for HTTP vs. HTTPS, for edge vs. origin, and for regions. Some bundle the first 1-2 million requests, others don't. When you're comparing vendors, you can't just compare the $/GB number; you have to model your actual request mix. That's where a simple calculator saves you from spreadsheet hell.
// Simple per-request cost estimator - run this against your access logs
function estimateRequestCost(metrics) {
const { totalRequests, httpsRatio = 0.95, pricePer10kHttps = 0.009, pricePer10kHttp = 0.0075 } = metrics;
const httpsRequests = totalRequests * httpsRatio;
const httpRequests = totalRequests * (1 - httpsRatio);
const cost = (httpsRequests / 10000 * pricePer10kHttps) +
(httpRequests / 10000 * pricePer10kHttp);
const requestsPerGB = totalRequests / (metrics.totalGB || 1);
console.log(`Total cost: $${cost.toFixed(2)} for ${(totalRequests/1e6).toFixed(1)}M requests`);
console.log(`Efficiency: ${requestsPerGB.toFixed(0)} req/GB - ${requestsPerGB > 50000 ? 'REQUEST-HEAVY' : 'BANDWIDTH-HEAVY'}`);
return cost;
}
// Example: 250M requests, 500GB total transfer (small API responses)
estimateRequestCost({ totalRequests: 250_000_000, totalGB: 500 });
// -> Total cost: $223.75 for 250.0M requests
// -> Efficiency: 500000 req/GB - REQUEST-HEAVY (optimize requests first)
What Actually Inflates Your Request Count
When I audit a high-request account, I rarely find one smoking gun. It's usually five small things stacking up. The biggest culprits aren't malicious traffic — they're well-intentioned features. Uncached API calls with `Cache-Control: no-store`, clients that retry aggressively on 429s or 5xx, image galleries that load 80 thumbnails at 15KB each instead of using sprites or lazy-loading, and third-party tags firing beacons every few seconds. Back in 2021 I worked with a media site whose video player was requesting the same 3KB manifest file 12 times per minute per viewer. They thought it was a bandwidth issue. It wasn't.
Browser and CDN behavior makes this worse if you're not deliberate about headers. A low `max-age` combined with `must-revalidate` forces edge revalidation on every request, which still counts as a billable request even if it returns a 304. And if you're not coalescing requests at the edge with something like stale-while-revalidate, a cache miss storm can turn one origin object into thousands of billable edge requests. The fixes recommended in HTTP Working Group Specifications and practical guides on web.dev Articles around caching semantics are directly tied to cost, not just performance. Getting your `Cache-Control` and `CDN-Cache-Control` right is request optimization in its purest form.
There's also a planning trap: teams choose pay-as-you-go because it feels safe, but they don't model what happens when request volume spikes 5-10x during a launch or sale. I've had clients save 20-30% just by moving request-heavy workloads to a committed plan once we proved their baseline was stable. If your request pattern is predictable, our CDN Bandwidth Commitment Pricing: When Committed Plans Save Money vs Pay-As-You-Go breakdown will help you decide when that lock-in is worth it. Don't guess — pull 90 days of logs and look at p95 request rate, not just the average.
# Example: Edge cache policy that cuts revalidation requests
# For Fastly VCL or similar edge config - cache API responses for 60s at edge, serve stale for 10s
sub vcl_fetch {
if (req.url ~ "^/api/config") {
# Edge caches for 60s, clients cache for 30s
set beresp.http.Cache-Control = "public, max-age=30, s-maxage=60";
set beresp.http.CDN-Cache-Control = "max-age=60";
set beresp.http.Stale-While-Revalidate = "10";
set beresp.ttl = 60s;
set beresp.grace = 10s;
# Coalesce concurrent misses - don't thunder herd the origin
set beresp.http.Surrogate-Control = "max-age=60, stale-while-revalidate=10";
return (deliver);
}
}
# Before: 14,400 requests/hour per edge PoP for polling clients
# After: ~60 requests/hour per PoP (99.6% reduction in origin + edge revalidation)
How I Measure Request Efficiency Before You Optimize
You can't reduce what you don't measure, and raw request count alone doesn't tell you much. I always start with two derived metrics: Requests per GB and Cache Hit Ratio (CHR) for your smallest objects. If you're serving more than 50,000 requests per GB, you're request-heavy and should prioritize request pricing over egress. If your CHR for objects under 50KB is below 85-90%, you're paying for avoidable origin fetches and edge lookups. Both numbers should come from your CDN's usage report or your own edge logs, not from synthetic tests.
Once you have those, segment by content type and status code. In my experience, 2xx cache MISS and 304 responses are the most expensive non-obvious categories. 304s still incur a full per-request charge but move almost no bytes, so they look great on your bandwidth chart while quietly draining budget. The same goes for 404s on missing favicons or API routes — I've seen a single typo in a mobile app generate 40 million 404s a day. Pair that view with CDN Storage Cost Optimization: Tiering, Compression, and Lifecycle Policies if you're also storing lots of tiny objects at the edge; origin storage and request costs often rise together.
The table below is from a recent anonymized audit I did for three different workloads on the same provider (HTTPS, US/EU only, standard per-request pricing). It shows how dramatically per-request cost shifts the effective $/GB when object size changes. Use it as a benchmark — if your numbers look like Workload C, request optimization will beat any bandwidth negotiation.
| Workload Profile | Avg Object Size | Requests per 1TB Egress | Effective Request Cost per TB | Cache Hit Ratio |
|---|---|---|---|---|
| A: Large Video (4K segments) | 4.2 MB | 250,000 | $0.22 | 98.2% |
| B: Mixed Web (images, JS, CSS) | 185 KB | 5.6M | $5.04 | 91.5% |
| C: API & Tracking Pixels | 2.8 KB | 372M | $334.80 | 42.1% |
| C (Optimized): Same API, 60s edge cache | 2.8 KB | 9.1M | $8.19 | 94.7% |
Putting Request Reduction Into Production: Collapsing, Bundling, and Smarter Cache Keys
Once you've got a baseline for requests-per-pageview and cache hit ratio, the real savings come from collapsing work at the edge before it ever becomes a billable origin or edge request. My go-to first win is request collapsing (also called request coalescing). Without it, a 2-second origin stall on a popular JSON endpoint can turn 800 concurrent users into 800 distinct origin fetches — all billed. With collapsing enabled, that same thundering herd becomes one origin fetch and 799 edge-served waits. On Fastly and CloudFront, we've seen this single toggle cut origin request counts by 60-75% during flash sales without touching a line of application code.
The second lever is how you normalize cache keys. I audited a media site last quarter doing 412M requests/day where 38% were cache MISSes solely because marketing UTM parameters, random cache-busters like ?v=168493, and unsorted query strings were fragmenting the key. You don't want to ignore query strings entirely — you'll break personalized content — but you should allowlist what matters. Strip or sort the rest at the edge, and bundle small assets where it makes sense. Don't just bundle to reduce requests blindly, though; over-bundling can hurt cache efficiency. And watch your edge compute trade-off: if you're using a Worker to rewrite keys, make sure you're not just swapping a $0.0075 per 10k request fee for a $0.15 per million invocation compute fee. We dig into that math in our breakdown of CDN Edge Compute Cost Optimization: Function Pricing Models and Efficiency Tricks.
Here's the VCL-style pattern I use at the edge to normalize keys and enable aggressive coalescing. It strips irrelevant params, sorts the remainder, and forces collapsing for our API routes:
# Normalize cache key and enable request collapsing at the edge
sub vcl_recv {
# 1. Allowlist only cache-relevant query params
if (req.url ~ "\?") {
set req.url = querystring.filter(req.url, "id,page,lang,format");
set req.url = querystring.sort(req.url);
}
# 2. Strip marketing noise entirely
set req.url = regsuball(req.url, "(utm_[^&]+&?)", "");
set req.url = regsub(req.url, "\?&", "?");
set req.url = regsub(req.url, "\?$", "");
# 3. Enable collapsing for expensive, cacheable API endpoints
if (req.url ~ "^/api/(product|config)") {
set req.hash_always_miss = false;
# Fastly: enable coalescing / CloudFront: use Origin Shield + collapse
set req.collapse = true;
}
}
sub vcl_fetch {
# Serve stale for 1h while revalidating in background - huge request saver
if (beresp.ttl < 1h) {
set beresp.stale_while_revalidate = 1h;
set beresp.stale_if_error = 24h;
}
}
Advanced Patterns and Troubleshooting When Your Request Count Won't Budge
If you've done the basics and you're still seeing 90M+ requests/day on what should be a highly cacheable property, it's time to look for the sneaky stuff. The biggest culprit I see is origin-driven cache busting: a Cache-Control: no-store or Vary: * leaking from a single API response, or misconfigured Set-Cookie headers that prevent any caching. One e-commerce client had their CDN hit ratio stuck at 71% until we found their auth service was setting a Set-Cookie on anonymous product views. Stripping that cookie at the edge for non-authenticated routes instantly pushed them to 94.2% and shaved $1,840/month off their CloudFront request bill alone. Always log your Cache-Status and Age headers and alert when MISS rate spikes above 15%.
Another pattern that pays off at scale is tiered shielding and origin protection. Don't just enable your CDN's shield POP — measure it. With Origin Shield on CloudFront or Shielding on Fastly/Cloudflare, we typically see an additional 70-85% reduction in origin-facing requests even after edge MISSes. For example, a 500M edge request workload at $0.009 per 10k HTTPS requests is $450/day. If only 6% are edge MISSes (30M), shielding can collapse those 30M into ~4.5M true origin fetches. That's where negotiating matters. Once your baseline is predictable post-optimization, you can use that lower, stable volume to your advantage with CDN Reserved Capacity Pricing: Locking In Rates for Predictable Workloads rather than paying on-demand rates forever.
Finally, don't write off non-cacheable workloads. For truly dynamic, uncacheable POSTs or personalized API calls, you can't collapse, but you can shift *when* you pay. We've had success moving nightly batch invalidations, image transcoding warmups, and prefetch population jobs to off-peak windows using CDN Spot Pricing for Edge Compute: Leveraging Unused Capacity for Savings. If your request chart shows 2x spikes during deploys or cache purges, switch from full purges to surrogate key (tag-based) purges. A full /* purge on a property doing 20k RPS will force a 20k RPS origin stampede; a keyed purge for product-1234 might only generate 80 origin requests. That's the difference between a bill shock and business as usual.
Frequently Asked Questions
We've got a 92% cache hit ratio — do we still need to worry about per-request fees?
Absolutely. 92% sounds great, but at scale it's still expensive. If you're serving 1 billion edge requests per month, that 8% miss is 80 million requests. At CloudFront's $0.0075-$0.012 per 10k (depending on region and HTTP vs HTTPS), that's $60-$96 just for the misses, plus the $750-$1,200 for the 920M hits. You'll also pay origin request fees on the misses. Pushing from 92% to 97% saves you over 50M billable requests.
Will enabling request collapsing add latency for users?
For the very first user in the collapsed group, no — they wait the normal origin time. For the other 100-1,000 users collapsed behind that single fetch, they wait slightly longer than if they'd hit a cached copy, but far less than they'd wait if your origin got overwhelmed and started timing out. In practice, p95 latency drops during traffic spikes because you avoid origin overload. Keep your origin timeout tight (under 3s) and serve stale-while-revalidate to mitigate it further.
Should I bundle all my small JS/CSS files to reduce request count?
Don't bundle everything into one monster file just to cut requests — you'll tank cache efficiency. If you ship a 400KB bundle and change one line, the whole bundle invalidates for every user. Instead, bundle stable vendor code separately from frequently changing app code, and consider HTTP/2 multiplexing. We've found the sweet spot is 3-6 bundles per page: it cuts 40-60 requests per page load down to 12-18 while keeping cache invalidation surgical. Measure your cache hit ratio per bundle.
What's the quickest metric to prove our optimization worked to finance?
Track "billable requests per 1,000 sessions" week-over-week, not just hit ratio. It translates directly to dollars. For instance: "We went from 184 billable requests per 1k sessions to 61" is clearer than "hit ratio improved 5%." Multiply that delta by your contracted per-10k rate and monthly sessions to show projected monthly savings — finance loves that math more than any CDN dashboard.