A CDN (Content Delivery Network) caches your content at edge locations close to users, so a visitor in Karachi is served from a nearby node instead of your origin across the world. The result: faster pages, less origin load, and lower bandwidth cost. This guide explains the concepts and sets up both Google Cloud CDN and AWS CloudFront.
Prerequisites
- An origin to put a CDN in front of: a bucket of static files, a load balancer, or any HTTP server.
- A cloud account (GCP and/or AWS) and its CLI.
- A domain if you want a custom hostname with HTTPS.
Why a CDN helps
- Latency: content travels a shorter distance โ faster first byte.
- Offload: cached responses never touch your origin โ it handles far more traffic.
- Resilience & security: the edge absorbs spikes and adds DDoS protection and TLS.
Caching concepts that actually matter
- Cache key โ what makes a request "the same" (usually host + path, sometimes query string).
- TTL โ how long the edge keeps a copy, controlled by
Cache-Control: max-age=...from your origin (or CDN settings). - Hit vs miss โ a hit is served from the edge; a miss fetches from origin, then caches it. Your goal is a high hit ratio.
- Invalidation โ purging cached content after a deploy so users get the new version.
- Static vs dynamic โ static assets (images, JS, CSS) cache beautifully; personalized/dynamic responses need care (cache by cookie/header, or don't cache).
Google Cloud CDN
Cloud CDN is enabled on a Google Cloud external HTTP(S) Load Balancer โ the LB is the front door, Cloud CDN is a checkbox on its backend:
gcloud compute backend-services update web-backend \
--enable-cdn --global \
--cache-mode=CACHE_ALL_STATIC \
--default-ttl=3600 --max-ttl=86400
# invalidate after a deploy:
gcloud compute url-maps invalidate-cdn-cache web-map --path="/*"
It uses Google's global network, honors Cache-Control, and supports invalidation and signed URLs.
Natural fit when your origin is already on GCP behind a load balancer.
AWS CloudFront
CloudFront is a standalone distribution in front of an origin (S3, an ALB, or any HTTP server):
- Define origins and behaviors (path patterns โ cache policies).
- Attach a free ACM certificate for HTTPS on your domain.
- Add WAF for filtering and CloudFront Functions / Lambda@Edge to tweak requests at the edge.
# invalidate after a deploy:
aws cloudfront create-invalidation --distribution-id E123ABC --paths "/*"
S3 + CloudFront is the classic static-site pattern (private bucket, served only via CloudFront).
Cloud CDN vs CloudFront
| Cloud CDN | CloudFront | |
|---|---|---|
| Shape | Feature of the GCP load balancer | Standalone distribution |
| Best when | Origin already on GCP | Origin on AWS/S3, or anywhere |
| Edge compute | Limited | CloudFront Functions / Lambda@Edge |
| Both | Global edge, TLS, Cache-Control, invalidation, signed URLs |
same |
Serving private content, and caching dynamic responses
A CDN isn't only for public static files โ two patterns come up constantly.
Signed URLs / signed cookies let you cache and serve private content at the edge without making it public. You keep the origin bucket private and hand each authorized user a time-limited signed URL; the CDN validates the signature and serves the cached object. It's how you deliver paid downloads, private video, or per-user reports fast and securely โ the content is cached globally, but only holders of a valid signature can fetch it. Both Cloud CDN and CloudFront support this.
Caching dynamic and personalized responses โ carefully. The instinct to mark everything dynamic as
no-store leaves a lot of speed on the table. Instead:
- Cache API responses that are the same for everyone (a product catalog, config) with a short TTL โ even 10 seconds of edge caching absorbs a traffic spike off your origin.
- For per-user pages, vary the cache key on the right signal (an auth cookie or an
Accept-Languageheader) so user A never sees user B's cached page โ and never cache anything keyed on a secret. - Use stale-while-revalidate so users get an instant cached copy while the CDN refreshes it in the background โ fast pages without stale content lingering.
The mental model: the CDN is a shared cache in front of your origin. Anything safe to reuse across requests (or across a short window) belongs there; anything unique-and-secret does not. Get that line right and you offload the majority of traffic while staying correct.
Security at the edge: TLS, WAF, DDoS & rate limiting
A CDN isn't only about speed โ it's your first line of defense, because all traffic passes through it before reaching your origin. Layer these:
- TLS termination at the edge โ the CDN serves HTTPS with a managed certificate (ACM for CloudFront, Google-managed certs for Cloud CDN's load balancer), and modern protocols (HTTP/2, HTTP/3) are handled for you.
- A WAF (AWS WAF on CloudFront, Cloud Armor on GCP) filters malicious requests โ SQL injection, XSS, bad bots โ at the edge, far from your servers, using managed rule sets plus your own.
- DDoS absorption โ the edge's scale soaks up volumetric attacks (AWS Shield, Google's edge) that would flatten a single origin.
- Rate limiting / bot control โ cap requests per IP and challenge suspicious clients (Cloud Armor rate rules, WAF rate-based rules) to blunt scraping and credential-stuffing.
- Origin protection โ lock the origin so it only accepts traffic from the CDN (origin access control / a shared secret header), so attackers can't bypass the edge and hit it directly.
Putting the WAF and rate limiting at the CDN means attacks are dropped before they cost you origin capacity โ security and performance from the same layer.
Edge compute: functions at the edge
Sometimes you need logic to run at the edge, close to users, without a round trip to your origin. CloudFront Functions (lightweight, sub-millisecond, for header rewrites, redirects, auth checks) and Lambda@Edge (heavier, full runtime, for more complex logic) let you do this on AWS; GCP offers similar capabilities on its load balancer. Common uses: rewrite/normalize URLs, add security headers, do A/B routing, validate a JWT before a request ever reaches your app, or serve a personalized redirect. It's the same idea as a CDN cache โ do the work near the user โ applied to computation, not just content.
Delivering video and large files
CDNs are essential for media. Large downloads and video streaming rely on HTTP range requests (the client fetches byte ranges, so seeking in a video doesn't re-download it), which CDNs cache efficiently. For adaptive streaming (HLS/DASH), the video is chopped into small segments at multiple bitrates; the CDN caches those segments so thousands of viewers are served from the edge and the origin transcodes once. The result is smooth playback that adapts to each viewer's bandwidth without hammering your origin โ the reason no serious video product runs without a CDN.
Measuring & debugging cache behavior
You can't improve what you don't measure. To see how well the cache is working:
- Inspect headers.
curl -Ian asset twice and read the cache-status header (x-cache: Hit from cloudfront, or GCP's cache-status /age). Two hits in a row with low latency = working. - Watch the hit-ratio metric. Both clouds report cache hit ratio and origin request volume โ the numbers that tell you whether the CDN is actually offloading your origin.
- Enable access logs (CloudFront/Cloud CDN logs) to see per-request hit/miss, and add Real User Monitoring to measure actual page-load times from real visitors' locations.
- Debug misses methodically: is the origin sending
no-store/private? Is the cache key varying on a query string or cookie it shouldn't? Is TTL too short? TheCache-Controlfrom the origin is usually the answer.
Common setups: static site, SPA & API
Three patterns cover most real-world CDN use:
- Static site / assets. Put your files in a private bucket (S3/GCS) and serve them only through
the CDN (CloudFront with Origin Access Control, or Cloud CDN on a backend bucket). Long
max-ageon hashed assets, short TTL onindex.html. The origin is never public and never touched for cached hits. - Single-page app (SPA). Same as above, plus a rule that routes unknown paths back to
index.html(so client-side routing works), and never cacheindex.htmlfor long โ it references the hashed JS bundles, so a staleindex.htmlpoints users at old code. Cache the hashed bundles forever; keep the entry point fresh. - Dynamic API. Front the API with the CDN for TLS, WAF, and edge locations even if you cache little.
Cache safe, shared GET responses (a public catalog) with a short TTL and appropriate
Vary; mark everything user-specificno-store. Even 10โ30s of edge caching on hot public endpoints hugely reduces origin load during spikes.
Invalidation strategy without stale bugs
The two hard problems in caching are "why is it stale?" and "why is it a miss?". A clean strategy avoids both:
- Prefer content-hashed filenames (
app.9f3a12.js) for assets โ a new build produces new names, so there's nothing to invalidate; old and new coexist and browsers fetch the new ones automatically. This is the single best practice for SPAs and asset pipelines. - Reserve explicit invalidation for the few files with stable names (
index.html, a config JSON). After deploy,create-invalidation --paths "/index.html"โ narrow, not"/*"(broad invalidations are slower and, on some plans, costly). - Use short TTLs on things that change but can't be hashed, accepting a brief staleness window.
- Version your API cache by changing a query param or path (
/v2/...) rather than purging.
Get this right and deploys are instant and correct: users get new code immediately, and your origin stays offloaded.
Multi-CDN & origin failover
At higher scale or reliability targets, two more patterns appear. Origin failover configures a backup origin so that if the primary returns errors or times out, the CDN automatically fetches from the secondary โ turning an origin outage into a non-event for cached and even some uncached content (CloudFront origin groups; GCP backend failover). Multi-CDN puts two providers behind DNS-based traffic steering for resilience against a whole-CDN outage and for better regional performance โ powerful but complex, and only worth it when a CDN outage would be genuinely business-critical. Most teams don't need multi-CDN; nearly everyone benefits from configuring origin failover and health checks.
Lifting your hit ratio
- Set long
max-ageon static assets and use content-hashed filenames (app.9f3a.js) so you cache forever and bust by name. - Keep the cache key minimal โ don't vary on cookies/query strings you don't need.
- Invalidate on deploy (or rely on hashed names, which need no invalidation).
- Measure the hit ratio (both clouds report it) and tune.
Verify it worked
curl -I https://yourdomain/asset.jstwice: the second response shows a cache HIT (x-cache: Hit from cloudfrontor GCP'sage:/cache-status header) and low latency.- Origin request logs drop sharply once caching is on.
- After an invalidation, the next request is a MISS then HIT again.
Troubleshooting
- Everything is a MISS โ your origin sends
Cache-Control: no-store/private, or the cache key varies on a changing query string/cookie. Fix origin headers and the cache policy. - Users see stale content after deploy โ you didn't invalidate, and TTL is long. Invalidate or use hashed filenames.
- HTTPS errors on custom domain โ certificate not attached/validated (ACM must be in
us-east-1for CloudFront), or the domain isn't listed as an alternate name. - Dynamic/personalized pages cached wrong โ exclude them from caching or vary the cache key on the right header/cookie.
Where to go next
Our free course puts a CDN in front of a real origin on both clouds: enable Cloud CDN on a GCP load balancer, stand up a CloudFront distribution over S3, set cache headers, and watch the hit ratio climb โ cutting load time and origin cost in a guided lab.