You put CloudFront in front of an S3 bucket or an API to speed things up and take load off the origin, and for months the line item sat somewhere between "a few dollars" and "not worth checking." Then a product launch shipped a heavier hero video, or a mobile release started pulling larger payloads in a dozen countries, and the CloudFront charge jumped without anyone touching a setting that looked like a cost control. The per-GB rate everyone quotes when they explain CDN pricing is real. It is also one line of a bill that has several others stacked quietly behind it.
Why does CloudFront cost anything at all?
CloudFront charges on three independent meters, and knowing which one moved is most of the diagnosis:
- Data transfer out — what you pay to serve bytes to viewers, billed per GB and tiered by both volume and the region the viewer connects from.
- Requests — every HTTP or HTTPS request CloudFront answers, billed per 10,000, with HTTPS costing more than HTTP.
- Edge features — invalidations, CloudFront Functions, Lambda@Edge, Origin Shield, and real-time logs, each metered separately and only when you turn it on.
At the entry tier — United States, Mexico, and Canada — the base rates look like this:
| What you pay for | Rate |
|---|---|
| Data transfer out, next 9 TB after the free tier | $0.085 per GB |
| HTTP requests | $0.0075 per 10,000 requests |
| HTTPS requests | $0.0100 per 10,000 requests |
| Free tier, every month, no 12-month expiry | 1 TB transfer + 10M requests + 2M Function invocations |
These are standard on-demand rates at the time of writing, rounded for clarity. CloudFront's data transfer rate varies by the region a viewer connects from, and AWS changes prices — check the CloudFront pricing page before you build a budget on these numbers.
One thing worth knowing if you sized your free tier expectations a while ago: the 1 TB / 10 million request allowance is no longer a first-12-months perk. AWS made it a standing monthly allowance for every account, so a small distribution can genuinely run on the free tier indefinitely rather than losing it a year after signup.
Why is my bill so much higher than the per-GB rate suggests?
The $0.085 headline rate explains the smallest bill you could have. Several multipliers stack on top of it.
Where your viewers are. $0.085/GB is the rate for the United States, Mexico, Canada, Europe, Israel, and Türkiye. Every other region costs more: roughly $0.109–$0.072/GB in India, $0.114–$0.080/GB in Australia and New Zealand, and up to $0.120/GB in the rest of Asia Pacific, before volume discounts bring any of those down. A distribution set to the Price Class All setting serves every viewer from the nearest edge location — which is good for latency and bad for the bill if a meaningful slice of your traffic is outside the cheap tier and nobody chose that on purpose.
HTTPS overhead. HTTPS requests cost about 33% more than HTTP in every region, because of the TLS handshake. Almost everyone terminates HTTPS by default now, so this isn't usually a lever — it's just worth knowing it's there when you're reconciling the request-count math against the dollar figure.
Invalidations past the free allowance. The first 1,000 invalidation paths a month are free; each one after that is $0.005. A deploy pipeline that invalidates a long list of specific paths on every push, rather than one wildcard pattern or a versioned filename, burns through that allowance fast — and it's easy not to notice because $0.005 at a time never triggers anyone's attention.
Lambda@Edge running where a CloudFront Function would do. Lambda@Edge has no free tier at all: $0.60 per million requests plus $0.00005001 per GB-second of compute, charged on every single invocation — including a function that only rewrites one header. CloudFront Functions do that same lightweight job for $0.10 per million invocations, with 2 million included free every month. The catch is capability, not just price: CloudFront Functions can't make network calls, read a filesystem, or run longer than about a millisecond, and they only run on viewer request/response, not origin request/response. A function doing real work — an origin-request rewrite, a call to an external auth service — genuinely needs Lambda@Edge. A function that only sets a header or does a simple redirect is paying compute pricing for something that fits in the cheaper tier.
Origin Shield. An additional $0.0075 per 10,000 requests, on top of the base request fee, for a feature that's easy to turn on while debugging a cache-miss problem and easy to forget is still running once the debugging is done.
How do I find out what's actually driving the number?
Start with Cost Explorer, filtered to CloudFront and grouped by Usage Type. That view splits data transfer, HTTP requests, HTTPS requests, invalidations, and any Lambda@Edge or Origin Shield usage into separate line items in one screen, which tells you immediately which meter is actually moving.
To confirm the current price class and traffic volume for a specific distribution:
aws cloudfront get-distribution-config --id E1A2B3C4D5E6F7 \
--query 'DistributionConfig.PriceClass'
aws cloudwatch get-metric-statistics \
--namespace AWS/CloudFront \
--metric-name BytesDownloaded \
--dimensions Name=DistributionId,Value=E1A2B3C4D5E6F7 Name=Region,Value=Global \
--start-time "$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)" \
--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--period 86400 \
--statistics Sum \
--region us-east-1 \
--output table
CloudFront always reports its metrics to us-east-1, regardless of where the distribution actually serves traffic — that's a common source of "empty output" when someone points the CLI at their usual working region. Swap BytesDownloaded for Requests to see request volume, or for CacheHitRate to see how much of that traffic is actually reaching your origin: a low hit ratio means every one of those cache misses is billed at the CloudFront meter and whatever your origin charges for serving it.
How do I bring the CloudFront bill down without breaking anything?
Don't replace a Lambda@Edge function with a CloudFront Function without reading what it does first. CloudFront Functions cannot make network calls, access a filesystem, or run on origin-request/origin-response events — only viewer-request/viewer-response. A function doing authentication against an external service or rewriting the request before it reaches your origin will simply fail if you move it, not run cheaper.
Pick the price class that matches your actual audience. If your traffic is mostly North America and Europe, Price Class 100 caps your data transfer at the cheapest regional tier and avoids paying the India/Asia-Pacific/Australia rates for occasional international requests. The trade-off is real: viewers outside the price class get routed to a farther edge location, so this is a cost-vs-latency call, not a free win.
Raise your cache hit ratio. Longer TTLs, forwarding fewer query strings, cookies, and headers to the origin, and normalizing URLs at the edge (a good use for a CloudFront Function) all reduce how often CloudFront has to fetch from your origin — which means fewer origin-fetch requests and less duplicate data transfer for content that shouldn't have changed.
Move lightweight Lambda@Edge functions to CloudFront Functions. Anything that only inspects or rewrites a header, path, or query string on the viewer side is a candidate, at roughly a sixth of the per-request price and no compute charge at all.
Batch or eliminate invalidations. Versioned filenames or cache-busting query parameters mean new content is simply a new cache key — no invalidation call needed at all, which is cheaper than even the free allowance because it also skips the multi-minute propagation delay invalidations have.
Turn off Origin Shield on distributions that don't need it. It earns its keep when a popular object gets requested from many edge locations at once and Origin Shield collapses those into one origin fetch; on a distribution with modest or well-cached traffic, it's a per-request fee with nothing to show for it.
What else should I check while I'm in here?
Two things worth a look while you have the console open.
CloudFront distributions protecting sensitive endpoints are usually paired with an AWS WAF Web ACL, and a Web ACL isn't attached to the distribution the way a security group is attached to an instance — it's a separate resource with its own lifecycle. Decommission the CloudFront distribution and the Web ACL keeps billing its base fee and every rule fee for a distribution that no longer exists. What AWS WAF actually costs per month walks through that fee structure and how it stacks.
The second is the origin behind the distribution. If it's a custom origin pointing at an EC2 instance or load balancer by IP, decommissioning that server doesn't clean up after itself either — the Elastic IP it was using stays allocated to your account and starts billing at the unattached rate the moment nothing is associated with it. What AWS charges you for an idle public IPv4 address covers exactly that leftover charge.
The third is the DNS record pointing at the distribution in the first place. An alias record to CloudFront resolves for free, but the hosted zone it lives in bills its flat monthly fee regardless — and if the distribution ever had a Route 53 health check watching its origin, that check keeps running and billing long after the distribution is gone. How Route 53 pricing adds up breaks down that fee structure.
More broadly, a CDN in front of one service is rarely the only place a growing AWS bill is quietly adding up; the full sweep across every service that quietly adds up works through the rest of an account in the order worth checking.
How do I catch this before it grows further?
CloudFront spend rarely spikes in one obvious jump — it climbs one region added to the price class, one Lambda@Edge function nobody swapped, one deploy script invalidating on every push, and each addition looks small enough on its own that nobody flags it. The moment worth catching is when the trend changes, not when the total finally gets big enough to notice.
Connect your AWS account read-only, and Parsivex's daily anomaly checks compare every service's spend — CloudFront included — against its own trailing baseline, so a jump from a price-class change, a Lambda@Edge function billing per request, or an invalidation-heavy deploy gets flagged the next morning instead of buried in next month's bill. For how those daily checks and severity thresholds work, see cost anomaly alerts, or read how scans work for what a connected account scan reads across the rest of your bill.