Last spring, a client I've worked with for three years sent me a screenshot of their monthly CDN invoice with a single line highlighted: $184,000 in egress. They weren't pushing that much more traffic than the month before, but a single untagged origin bucket and a bad cache rule had quietly doubled their billable bandwidth. If you've managed CDN spend at any real scale, you've probably had that same stomach-drop moment where you realize egress isn't just a line item — it's the line item.
Why Egress Quietly Becomes 70% of Your CDN Bill
When folks first look at CDN Storage Cost Optimization: Tiering, Compression, and Lifecycle Policies or compute, they assume those will dominate spend. They don't. In almost every architecture I've audited over the past decade, from media streaming to large SaaS platforms, egress cost — the fee you pay for bytes leaving the provider's edge to your users — ends up being 65-80% of the total CDN billing. It's not because providers are trying to hide it; it's just that bandwidth pricing scales linearly with success. More users, higher bitrates, and larger assets mean more bytes out, and you pay for every gigabyte after your free tier or commit.
What makes this tricky is that CDN billing isn't flat. Most providers use regional, tiered, or blended bandwidth pricing, and the price per GB can swing dramatically based on where your users are. Serving 100TB out of North America and Europe might cost you $0.04/GB at volume, but that same 100TB split across South America, Australia, and India could easily push past $0.09/GB. I've seen teams model their costs on US list pricing and then get blindsided when their expansion into APAC tripled their average egress rate. You can't model egress with a single number — you've got to break it down by region, by tier, and by cache behavior.
Your cache hit ratio is the multiplier that decides how much of that regional pricing actually hurts. Every request that misses cache doesn't just cost you edge egress; it also triggers origin egress and re-caches the asset at the edge. We had a video platform where just a 6% drop in cache efficiency added $22k in a week. Here's the simple model I use to estimate the real billable egress before we even look at provider discounts:
# Simple egress cost modeling - Python
# Accounts for regional pricing and cache efficiency
def monthly_egress_cost(requests_per_month, avg_asset_mb, cache_hit_ratio, regional_mix):
"""
regional_mix: dict of {region: (pct_of_traffic, price_per_gb)}
e.g., {"us_eu": (0.7, 0.04), "apac": (0.2, 0.08), "sa": (0.1, 0.11)}
"""
total_gb = (requests_per_month * avg_asset_mb) / 1024
# Only cache misses generate origin egress + additional edge fills
billable_edge_gb = total_gb
origin_egress_gb = total_gb * (1 - cache_hit_ratio)
# Weighted average price based on traffic distribution
weighted_price = sum(pct * price for pct, price in regional_mix.values())
edge_cost = billable_edge_gb * weighted_price
# Origin egress is often billed separately at cloud provider rates
origin_cost = origin_egress_gb * 0.087 # e.g., AWS origin egress
return {
"billable_edge_gb": round(billable_edge_gb, 2),
"origin_egress_gb": round(origin_egress_gb, 2),
"estimated_cost": round(edge_cost + origin_cost, 2)
}
# Example: 50M requests, 2.5MB avg, 88% hit ratio
print(monthly_egress_cost(50_000_000, 2.5, 0.88,
{"us_eu": (0.7, 0.04), "apac": (0.2, 0.08), "sa": (0.1, 0.11)}))
Building a Cost Model That Doesn't Fall Apart After Two Weeks
I've built a lot of spreadsheets that looked perfect on day one and were useless by day fifteen. The mistake I kept making early in my career was modeling egress cost as if traffic were uniform. It never is. A realistic model has to account for at least three variables you can't average away: request mix, cache hit ratio variance by content type, and the difference between committed and pay-as-you-go bandwidth pricing. If you're trying to decide between pricing models, our breakdown of CDN Bandwidth Commitment Pricing: When Committed Plans Save Money vs Pay-As-You-Go will save you a lot of what-ifs — but even with the right contract, your model has to handle bursts.
Here's how I approach it now for mid-size platforms doing 50TB to 500TB a month. First, I pull at least 30 days of edge logs and group requests by content type (images, JS/CSS, API JSON, video segments), not just by URL. An API response might be 4KB but uncacheable, so it hits origin every time and bypasses all the nice tiered discounts you get for cacheable static assets. Second, I track hit ratio per PoP, not globally. A 92% global hit ratio can hide a 68% hit ratio in Singapore where you're paying the highest per-GB rate. The HTTP Working Group Specifications on Cache-Control and CDN-Cache-Control are essential here — using stale-while-revalidate and proper s-maxage directives correctly can lift that regional hit ratio by 10-15% without touching your application. I also cross-check with guidance from web.dev Articles on caching best practices for browsers to reduce origin revalidation.
Once you've got that split, you can forecast with much less guesswork. Don't just project a single growth rate; run three scenarios. I usually run a baseline (current traffic + 25% growth), a hit-ratio degradation scenario (-5% hit ratio), and a regional-shift scenario (10% more traffic from high-cost regions). The second one almost always reveals the biggest risk. Below is the quick log-parsing pattern I use to get those per-type hit ratios before feeding them into the forecast. It's saved me from underestimating egress more times than I can count.
-- BigQuery / Athena example: egress modeling inputs per content type
SELECT
content_type,
pop_region,
COUNT(*) AS total_requests,
-- Use CDN cache status header (e.g., HIT, MISS, BYPASS)
COUNTIF(cache_status = 'HIT') / COUNT(*) AS hit_ratio,
SUM(resp_bytes) / POW(1024, 3) AS egress_gb,
AVG(resp_bytes) / 1024 AS avg_kb_per_request
FROM cdn_access_logs
WHERE date BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY) AND CURRENT_DATE()
GROUP BY 1, 2
ORDER BY egress_gb DESC;
-- Feed hit_ratio * egress_gb per region into your pricing tier table
-- Don't forget to separate 4xx/5xx - you often still pay egress for errors!
Where Your Bandwidth Dollars Actually Go
When we do egress audits, the cost rarely comes from where the team expects. It's not your homepage hero image. It's usually three culprits: uncompressed assets that should have been Brotli or Zstd compressed, low-TTL API responses that could be cached for even 10 seconds, and origin shielding that isn't turned on. I worked with an e-commerce company that cut 31% of their monthly egress just by adding proper cache keys for query parameters — their product images were being cached as 40 different objects because of utm_source tags. That's the kind of thing that won't show up in aggregate billing but will dominate your per-GB breakdown.
Another blind spot is request pricing itself. Egress and requests are billed separately, and at scale, small assets can invert your assumptions. If you're serving 800 million tiny 12KB JSON requests, your CDN Request Pricing Analysis: Reducing Per-Request Costs at Scale becomes just as important as the bandwidth side because those requests drive both a high request count and terrible compression ratios. I always model them together now. You should know your cost per 10k requests alongside your cost per GB — otherwise you'll optimize one and inflate the other. Modern image formats and edge compression can cut egress by 20-30%, but they don't change the request fee.
This table is from a recent 200TB/month audit we ran for a streaming + web platform. It shows how different traffic classes contributed to the final bill — and why we prioritized fixes differently than the client initially planned:
| Traffic Class | Monthly Egress (TB) | Avg. Hit Ratio | Blended Price / GB | % of Total Egress Cost |
|---|---|---|---|---|
| Video segments (HLS/DASH) | 118.4 | 94.2% | $0.038 | 52.1% |
| Large images (1-4MB) | 46.7 | 89.5% | $0.041 | 22.3% |
| JS/CSS bundles | 18.9 | 96.8% | $0.039 | 8.6% |
| Uncacheable API (JSON) | 12.1 | 3.1% | $0.058 | 16.2% |
| Thumbnails / icons (<50KB) | 4.2 | 91.0% | $0.042 | 0.8% |
Putting the Model Into Production: From Spreadsheet to Live Controls
Look, your cost model isn't useful if it lives in a Google Sheet that only your FinOps lead updates on Fridays. By the time you spot a 22% egress overrun, you've already burned an extra $18k for the week. The teams I've seen actually control egress bring their model into their observability stack. You don't need a fancy platform — you need daily actuals vs. forecast, by region and by cache status, piped into Slack or PagerDuty.
We've had good luck with a dead-simple approach: pull hourly bytes from your CDN logs (or your provider's usage API), join it against your rate card, and calculate a blended cost-per-GB every night. If your HIT traffic is $0.007/GB and your MISS + origin shield is $0.085/GB, a 2-point drop in cache hit ratio from 93% to 91% on a 500 TB/month property isn't a rounding error — it's roughly $8,600 in extra egress overnight. You want an alert for that, not a line item on next month's invoice. This daily cost layer also makes it way easier to justify CDN Edge Compute Cost Optimization: Function Pricing Models and Efficiency Tricks — because you can prove whether that image optimization function actually saved $0.04/GB in transfer or just moved the cost to compute.
# daily_egress_cost.py - runs via cron, posts to Slack if variance > 10%
import requests
RATE_CARD = {
'hit': 0.007, # $/GB - CDN edge hit (commit-tier)
'miss': 0.02, # $/GB - edge miss / shield
'origin': 0.085 # $/GB - origin egress (AWS/GCP)
}
def get_daily_usage(date):
# Replace with Fastly/Cloudflare/Akamai usage API
resp = requests.get(f"https://api.cdn-provider.com/v1/usage?date={date}")
data = resp.json() # expects {'hit_gb': 15234, 'miss_gb': 1240, 'origin_gb': 890}
return data
def calculate_cost(usage):
hit_cost = usage['hit_gb'] * RATE_CARD['hit']
miss_cost = usage['miss_gb'] * RATE_CARD['miss']
origin_cost = usage['origin_gb'] * RATE_CARD['origin']
total = hit_cost + miss_cost + origin_cost
blended = total / sum(usage.values())
return total, blended
usage = get_daily_usage("2026-10-14")
total, blended = calculate_cost(usage)
forecast = 1850.00 # your model’s daily forecast
if abs(total - forecast) / forecast > 0.10:
print(f"ALERT: Daily egress ${total:,.2f} vs forecast ${forecast:,.2f} | Blended: ${blended:.4f}/GB")
Advanced Patterns: When Your Model Says One Thing and the Bill Says Another
Once your baseline model is running, you'll hit the weird stuff that breaks naïve assumptions. The most common is regional price arbitrage you didn't account for. Don't model a single $0.08/GB global rate. Egress from ap-south-1 or sa-east-1 is often 35-45% more expensive than us-east-1, and your CDN's South America POP surcharge might add another $0.015/GB. We audited a video customer who thought they had a 96% cache hit ratio globally — they did — but 18% of their MISS traffic was coming from India, where origin egress was $0.11/GB. That one region was 31% of their total bill.
Another pattern: cache churn from query strings and personalization cookies. Your model assumes a static object is cached once. In reality, if you're not normalizing ?utm_source= or stripping __cf_bm cookies at the edge, your origin might be serving 8,000 variations of the same 2.3MB hero image. You'll see it as a sustained 84-87% hit ratio that *should* be 94%+. Fixing this at the edge with a simple VCL or Workers rule typically recovers 4-7 points of hit ratio in 48 hours — that's often $12k-$25k/month back on a mid-size property.
Finally, don't try to solve everything with commits on day one. If your workload is spiky — think flash sales or live events where you burst from 40 Gbps to 180 Gbps for three hours — locking 100% into reserved rates will hurt you. For predictable base load (that 50-60% you serve every single day), CDN Reserved Capacity Pricing: Locking In Rates for Predictable Workloads is a no-brainer and usually nets 20-28% off list. For the bursty, ephemeral compute around that traffic, like on-the-fly transcoding or SSR, you're better off keeping it flexible — that's where CDN Spot Pricing for Edge Compute: Leveraging Unused Capacity for Savings can save you 60%+ versus on-demand, if you can tolerate the occasional eviction.
Frequently Asked Questions
Our cache hit ratio is already 95% — is there still egress to save?
Absolutely. At 95%, you're doing well, but the *type* of your 5% miss matters more than the percentage. If that 5% is large video segments or uncompressed 4K images coming straight from S3 in a pricey region, it can still be 40% of your bill. We've seen teams at 96% hit ratio cut another $9k/month just by adding origin shield in one region and enabling tiered caching. Don't stop at the global hit ratio.
Should we model committed use discounts directly in our egress forecast?
Yes, but keep it as a separate layer. Model your gross egress at list prices first, then apply your commit as a discount tier. For example, forecast 400 TB at $0.085/GB = $34k gross, then apply your 25% CUD to get $25.5k net. If you bake the discounted rate in from the start, you won't see when you drop below your commit and suddenly pay list for overflow — which happens more often than you'd think.
Why does our bill spike even when total bandwidth is flat?
Almost always a mix of two things: cache key fragmentation or regional shift. Check your cache status breakdown first — if HIT stays flat but MISS and PASS go up 15%, you've got a purge-happy deploy or a new A/B test header that busts the cache. Second, look at traffic-by-country. A 10% traffic shift from the US to Australia or Brazil can raise egress costs 18-20% even with zero change in total TB.
What's a realistic blended egress target for a high-cache workload?
For a well-tuned static asset workload (images, JS, video-on-demand) with 93-96% hit ratio and origin shield, you should aim for a blended egress of $0.011 to $0.018/GB all-in, including origin. If you're sitting above $0.03/GB blended, you've got either low hit ratio, no shield, or you're paying list origin rates without any commit. That's the first place to look.