CloudFront: a fast edge in front of your app
Put a CDN in front of a private S3 bucket: walk the create-distribution wizard, keep the bucket private with Origin Access Control, add HTTPS with ACM and clear the cache after a deploy.
By the end of this lesson you'll be able to
0 of 6 completeWhy a CDN
Your S3 bucket from lesson 6 lives in Mumbai. A visitor in Mumbai gets files quickly; one in São Paulo waits for every byte to cross the planet. CloudFront is AWS's CDN (content delivery network): hundreds of edge locations worldwide keep cached copies of your files, so each visitor is served from one nearby. It also gives you HTTPS on your own domain, a web application firewall, and a way to keep the bucket private.
visitor → nearest edge location ──(cache hit)──→ file └──(cache miss)──→ origin (private S3 bucket or ALB) → cached for next time
The screenshots below are the real create-distribution wizard in a practice account, walked to the final Review step. That account hadn't yet been verified for CloudFront, which AWS requires before a new account's first distribution, so this lesson stops at Review. If your new account gets the same error, open an Account and billing support case asking AWS to verify it.
Create a distribution
CloudFront → Distributions → Create distribution. The first choice is how to pay:
Flat-rate plans bundle the CDN, WAF, DNS and logging at a fixed monthly price with no overage charges. Pay as you go bills each request and GB separately: cheapest at very low volume, but with no spending cap. For a learning project or a small site, Free is the safe choice.
The origin
The origin is where CloudFront fetches files it hasn't cached yet: an S3 bucket, a load balancer, API Gateway, a Lambda function, or any public URL.
Origin Access Control (OAC) is the important part. CloudFront signs its requests to S3, and the wizard adds a
bucket policy that allows s3:GetObject only for this distribution:
{ "Effect": "Allow", "Principal": { "Service": "cloudfront.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-site-bucket/*", "Condition": { "StringEquals": { "AWS:SourceArn": "arn:aws:cloudfront::111122223333:distribution/E1ABCDEF" } } }
Block Public Access stays on (lesson 6), and nobody can skip the CDN and read the bucket directly.
When it's created you get a domain like d1234abcd.cloudfront.net; open it and you get index.html from the
nearest edge. Set the distribution's default root object to index.html so / works.
Caching and invalidations
A behavior maps a path pattern (/api/*, *.js, default *) to an origin and a cache policy: how long
to keep files (TTL) and what makes two requests "the same" (the cache key: path, plus any headers, cookies or
query strings you include). Keep the cache key small; every extra header splits the cache.
| Content | Typical policy |
|---|---|
app.3f9c2e1.js, images with versioned names | cache for a year: the name changes when the file does |
index.html | short TTL, or invalidate on every deploy |
/api/* to an ALB | CachingDisabled, forward the headers and cookies the app needs |
After a deploy, clear old copies with an invalidation:
$ aws s3 sync ./build s3://my-site-bucket/ --delete $ aws cloudfront create-invalidation --distribution-id E1ABCDEF --paths "/index.html"
aws s3 sync ./build s3://my-site-bucket/ --deleteawsthe AWS command line
Response headers tell you what happened: X-Cache: Hit from cloudfront means an edge served it, Miss from cloudfront means it went to the origin.
Your own domain and HTTPS
To use www.example.com instead of the cloudfront.net name:
- Request a public certificate in ACM in us-east-1 (N. Virginia) for the domain. CloudFront only reads certificates from that Region, wherever your origin is.
- Prove you own the domain with the DNS record ACM gives you (one click if the domain is in Route 53).
- Add the domain as an alternate domain name on the distribution and pick the certificate.
- Point the domain at the distribution with a Route 53 alias record (lesson 8).
Code at the edge
| CloudFront Functions | Lambda@Edge | |
|---|---|---|
| Runs | at every edge location, under 1 ms | at regional edge caches, up to seconds |
| Language | JavaScript only | Node.js or Python |
| Can | rewrite URLs, add headers, redirect, simple auth checks | call other services, read the body, generate responses |
| Cost | very low, per million invocations | higher, like Lambda |
A typical function adds security headers or rewrites /about to /about/index.html for a static site.
Signed URLs, geo restriction and origin failover
CloudFront signed URLs grant one path for a short time; signed cookies grant a set of paths to a browser. Geo restriction is a coarse country allow or deny list, not application authorisation. Origin failover switches to a secondary origin for configured errors, so the fallback must contain compatible content.
$ aws cloudfront get-distribution-config --id "$DISTRIBUTION_ID" \ --query 'DistributionConfig.{Origins:Origins.Items[].DomainName,Restrictions:Restrictions}'
aws cloudfront get-distribution-config --id "$DISTRIBUTION_ID" \awsthe AWS command line
A private bucket behind a CDN
Create a private S3 bucket with an index.html, walk CloudFront's create-distribution wizard with that bucket as the origin and private access allowed, and stop at Review to check every setting. If your account is verified for CloudFront, create it, load the page through the cloudfront.net domain, then disable and delete the distribution and the bucket.
A Review page showing the Free plan, your S3 origin and private bucket access; if created, your page served from a cloudfront.net URLReveal one safe solution
$ BUCKET=academy-cf-$(aws sts get-caller-identity --query Account --output text)-$RANDOM $ aws s3api create-bucket --bucket $BUCKET --region ap-south-1 --create-bucket-configuration LocationConstraint=ap-south-1 $ printf '<h1>Served by CloudFront</h1>\n' > index.html $ aws s3 cp index.html s3://$BUCKET/index.html --content-type text/html $ echo "Origin: $BUCKET.s3.ap-south-1.amazonaws.com" $ # Console: CloudFront → Create distribution → Free → name → S3 origin above, $ # Allow private S3 bucket access → Next → Next → Review (→ Create distribution) $ # If created: curl -sI https://<distribution>.cloudfront.net/index.html | grep -i x-cache $ # Clean up: disable the distribution, wait for Deployed, delete it; then: $ aws s3 rb s3://$BUCKET --force
aws s3api create-bucket --bucket $BUCKET --region ap-south-1 --create-bucket-configuration LocationConstraint=ap-south-1awsthe AWS command line--regionwhich Region
printf '<h1>Served by CloudFront</h1>\n' > index.html>write output to a file, replacing it
echo "Origin: $BUCKET.s3.ap-south-1.amazonaws.com"echoprint text
# If created: curl -sI https://<distribution>.cloudfront.net/index.html | grep -i x-cache|pipe: send output to the next commandgrepfind lines that match a pattern-iignore upper/lower case
The first curl after creating says Miss from cloudfront, the second Hit from cloudfront: the second request
never reached your bucket.
Try it yourself
0 of 6 steps doneRun each task in your own account or terminal, then tick it off.
Quick check
Why put CloudFront in front of an S3 website instead of serving the bucket directly?
Official reading for this lesson
Or get every quiz answer right and it completes itself.