Skip to content

AWS CloudFront

CloudFront is a cache in front of your origin, not a faster copy of it. A viewer’s request goes to a nearby edge location, which builds a cache key from the request. Same key, cached copy: served without touching the origin. Anything you leave out of the cache key is ignored when deciding what to serve, so one cached object answers every request that differs only in those parts.

PieceRole
DistributionThe deployment; gets a *.cloudfront.net domain
OriginWhere content comes from: S3, ALB, API, any HTTP server
BehaviorA path pattern mapped to one origin and its settings
Default behaviorThe * pattern; matches when nothing else does

A request is matched against behaviors in precedence order, and the first matching path pattern wins. Put specific patterns such as /api/* above general ones. Each behavior picks its origin, allowed methods, cache policy, viewer protocol, and attached functions. Changes take a few minutes to deploy to all edges.

Terminal window
aws cloudfront list-distributions \
--query "DistributionList.Items[].[Id,DomainName]"
aws cloudfront get-distribution-config --id E123

Gotcha: The default root object applies only to /. A request for /docs/ does not return /docs/index.html; rewrite it with a function.

StepWhat happens
Viewer requestEdge receives it, runs viewer-request code
Cache lookupCache key built; hit returns here
Origin requestOn a miss, edge fetches from the origin
Origin responseCached per TTL, then returned to the viewer

Check the X-Cache response header: Hit from cloudfront, Miss from cloudfront, RefreshHit from cloudfront (revalidated with the origin), or Error from cloudfront. The Age header is seconds spent in cache, and X-Amz-Cf-Pop names the edge that answered.

Terminal window
curl -sI https://dxxxx.cloudfront.net/app.js \
| grep -iE "x-cache|age|x-amz-cf-pop"
Managed policyBehavior
CachingOptimizedNo query strings or cookies in the key; 1 s min, 24 h default, 1 y max
CachingDisabledTTLs all 0; nothing cached
UseOriginCacheControlHeadersDefault TTL 0; the origin’s Cache-Control decides

A cache policy sets which headers, cookies, and query strings join the cache key, plus the minimum, default, and maximum TTL. If the origin sends Cache-Control: max-age, CloudFront uses it, clamped between your min and max. With no header, the default TTL applies. Include only what changes the response: every extra key part lowers the hit ratio.

CachingOptimized
658327ea-f89d-4fab-a63d-7e88639e58f6
CachingDisabled
4135ea2d-6df8-44a3-9df3-4b5a84be39ad

Gotcha: CachingOptimized has a 1 s minimum TTL, so CloudFront caches responses even when the origin sends no-store, no-cache, or private. For dynamic paths use CachingDisabled.

Origin request policy: forward without caching on it

Section titled “Origin request policy: forward without caching on it”
Policy partEffect
Cache policy valuesSent to the origin and used in the key
Origin request policy valuesSent to the origin, not in the key
AllViewerForward all viewer headers, cookies, query strings
AllViewerExceptHostHeaderSame, but the origin sees its own host

Use an origin request policy when the origin needs a value but the response does not vary by it, such as an auth header or CloudFront-Viewer-Country. A cache policy sets what varies the cache; an origin request policy only adds what is sent.

Gotcha: Forwarding the viewer Host header to an S3 origin or an API Gateway endpoint breaks the request, usually as 403 or SignatureDoesNotMatch. Use AllViewerExceptHostHeader.

ItemValue
MechanismOrigin access control (OAC) signs requests with SigV4
Bucket policy principalcloudfront.amazonaws.com
Scoped byAWS:SourceArn = the distribution ARN
LegacyOrigin access identity (OAI); use OAC for new work

Keep the bucket private and let only your distribution read it. Add s3:ListBucket for the distribution too, or a missing object returns 403 instead of 404.

{
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {"StringEquals": {
"AWS:SourceArn":
"arn:aws:cloudfront::ACCT:distribution/E123"
}}
}

Gotcha: A static website endpoint (bucket.s3-website-...) is a custom origin, and OAC does not work with it. Use the bucket’s REST endpoint (bucket.s3.region.amazonaws.com).

ApproachUse for
Versioned file namesAssets: app.3f9a1c.js
Invalidate a pathA file that must change now
Invalidate /*Last resort for a full refresh
Short TTL on HTMLEntry pages that reference hashed assets

AWS recommends file versioning as the primary way to update content. A new name is a new cache key, so there is nothing to purge, and browser caches are bypassed too. Invalidation removes objects from edge caches only. Invalidation paths must start with /, and * is allowed only at the end.

Terminal window
aws cloudfront create-invalidation \
--distribution-id E123 \
--paths "/index.html" "/img/*"

Gotcha: An invalidation does not clear the viewer’s browser cache. If you sent max-age=31536000, users keep the old file; this is why versioned names beat invalidating.

CloudFront FunctionsLambda@Edge
Runs onViewer request, viewer responseViewer and origin, request and response
LanguageJavaScript (CloudFront JS runtime)Node.js, Python
Size limit10 KB, 2 MB memory50 MB zip
Network callsNoYes, 30 s timeout

Pick Functions for cheap, fast request work: redirects, URL rewrites, header edits, cache-key normalization. Pick Lambda@Edge when you need network access, a larger runtime, or origin-side events. Lambda@Edge functions are authored in us-east-1 and replicated to edges.

function handler(event) {
var req = event.request;
if (req.uri.endsWith("/")) {
req.uri += "index.html";
}
return req;
}

Gotcha: A viewer-request function runs on every request, including cache hits. An origin-request function runs only on a miss, so use it for slow work.

SettingDetail
Custom error responseMap 403/404 to a page and a status
Error caching min TTLHow long CloudFront caches an error
ACM certificateMust be issued in us-east-1
Alternate domain nameAdd the CNAME plus a matching certificate

A single-page app on S3 needs 403 and 404 mapped to /index.html with response code 200, so client-side routes load. Point your DNS name at the distribution with a Route 53 alias record, not a plain CNAME at the zone apex. Set the viewer protocol policy to redirect-to-https.

Gotcha: Mapping errors to /index.html also masks real failures, such as a bad deploy that returns 403 for every file. Test with curl -I against the cloudfront.net domain.

OptionUse for
Signed URLOne file, or a client that cannot send cookies
Signed cookiesMany files, such as a whole video stream
Trusted key groupHolds the public keys CloudFront verifies against
Custom policyAdds an IP range or a start time

You create a key pair, upload the public key to a key group, then require signed access on the behavior. Your app signs with the private key; CloudFront checks the signature and expiry. It is separate from S3 presigned URLs and works with any origin.

Note: Signed access controls who may fetch from CloudFront. It does not stop a hot link from a valid, unexpired URL.

LimitDefault
Distributions per account500
Behaviors per distribution75
Max cacheable GET response50 GB
Origin response timeout1–120 s
Compressed file sizes1,000 to 10,000,000 bytes
URL length8,192 bytes

Most are raisable through Service Quotas. Compression covers only files in the listed size range and needs the cache policy’s compressed-objects setting on. The managed policies above enable it.