How to get 1TB free egress for Amazon S3 via CloudFront
S3 offers free egress bandwidth of 100GB per month, while CloudFront pay-as-you-go has 1TB for free. This creates an incentive to add a CloudFront distribution and route read traffic to the bucket via that.
There are already a couple of valid reasons to use CloudFront with S3: hosting public files via HTTPS, taking advantage of routing to put everything under one domain, using signed cookies to control access to a set of files together, or just for plain performance reasons like caching or the shorter roundtrips for connections.
But with the difference in free egress traffic, even if you don't need any of the above, it still makes sense to add a distribution in front.
Routing S3 signatures via CloudFront
Usually, getting an object from S3 needs a request signed with an IAM identity for the GetObject operation. A URL looks like this:
https://blog-s3cf-test-bucket-ojaw9naktxk9.s3.eu-west-1.amazonaws.com/none/exists.json?
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20261103%2Feu-west-1%2Fs3%2Faws4_request&
X-Amz-Date=20261103T181218Z&
X-Amz-Expires=3600&
X-Amz-SignedHeaders=host&
X-Amz-Security-Token=<TOKEN>&
X-Amz-Signature=d76e0e1309f8b87824c849c59e59d79396ed889ce50927fafbdd0f6d02dbf292
This URL goes directly to S3 and downloads the file, thus consuming the 100GB/month free egress.
While one usually encounters signed URLs when needing a way for clients to download a file directly from S3, this is a generic pattern across AWS and all operations can be presigned. The above just happens to be a GetObject that downloads an object.
The signature protects all parts of the URL, which means that no part can be changed without invalidating it. And if a request is invalid, AWS will return an error.
CloudFront is on a different domain than S3. An auto-generated URL for a distribution looks like this:
https://ds8wpcvkec7cr.cloudfront.net
When CloudFront gets a request it forwards it to an origin with the option to change parts of it, for example to forward only some query parameters or change the request path.
The trick to tunnel S3 signed requests via CloudFront is to sign the request to the S3 URL but send the request to the CloudFront domain. Then CloudFront will change the domain back to the S3 domain, so the signature becomes valid again.
This goes through CloudFront, so it will consume its 1TB/month free egress. And since there is no bandwidth fee between S3 and CloudFront it won't touch S3's lower allowance. It is also secure as CloudFront acts as an independent consumer and does not need any special access to the bucket, the bucket is still private and S3 checks the request signatures.
Configuring the distribution
The first configuration is the origin domain CloudFront sends the request to. This is the regional domain name of the bucket
(blog-s3cf-test-bucket-ojaw9naktxk9.s3.eu-west-1.amazonaws.com).
Resources:
Reads:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: bucket
DomainName: !GetAtt Bucket.RegionalDomainName
S3OriginConfig:
OriginAccessIdentity: ""
Then caching should be disabled as all requests are signed and thus should not be served from the cache (Managed-CachingDisabled caching policy). And all
query parameters need to be forwarded so that the signature is valid while the Host header needs to be overwritten (Managed-AllViewerExceptHostHeader origin
request policy).
Resources:
Reads:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
# ...
DefaultCacheBehavior:
# Managed-CachingDisabled
CachePolicyId: 4135ea2d-6df8-44a3-9df3-4b5a84be39ad
# Managed-AllViewerExceptHostHeader
OriginRequestPolicyId: b689b0a8-53d0-40ab-baf2-68738e2966ac
Caveats
While usually the egress cost is the big one, both S3 and CloudFront have per-request charges. Adding a distribution brings an extra price dimension along with an extra component that needs to be managed and can potentially be misconfigured.
Also, it needs the clients to opt in to using CloudFront as they need to rewrite the domain in the URL.