CDN Caching Explained: Cache-Control, TTL, Cache Keys, HIT vs MISS and Edge Caching Architecture
Caching is one of the most powerful—and frequently misunderstood—technologies behind modern web performance.
A website may run on extremely powerful servers, use a high-performance database, and connect through a global cloud network, yet still deliver unnecessary requests to its origin infrastructure because its caching architecture is poorly designed.
A well-designed cache changes the request path from:
User
│
▼
Internet
│
▼
Application Server
│
▼
Database
│
▼
Application Server
│
▼
User
into:
User
│
▼
Nearby CDN
│
▼
CACHE HIT
│
▼
Response
The difference can be enormous.
Caching can reduce:
- application latency;
- origin traffic;
- database queries;
- serverless function executions;
- bandwidth between infrastructure layers;
- compute consumption;
- infrastructure cost;
- load during traffic spikes.
But caching also introduces difficult engineering questions.
How long should content remain cached?
What exactly identifies one cached object?
Should cookies affect the cache?
What happens when content changes?
Should stale content be served while a new response is generated?
How do you prevent personalized data from entering a shared CDN cache?
And how can a poorly designed cache key create either terrible performance or a security vulnerability?
This guide explains CDN caching architecture, browser caching, Cache-Control, TTL, cache keys, cache HIT vs MISS, stale-while-revalidate, stale-if-error, cache invalidation, cache purging, cache poisoning, personalized content, dynamic APIs, and practical caching strategies for AWS CloudFront, Cloudflare, Vercel, and Netlify.
For the broader distributed-computing layer surrounding caching, start with our Edge Computing Explained guide.
What Is Caching?
Caching stores a reusable copy of data so that future requests can be answered without repeating the complete operation required to generate the original response.
Imagine an application that generates a product page.
Without caching:
Request
│
▼
Application
│
▼
Database Query
│
▼
Template Rendering
│
▼
Response
Every visitor repeats this workflow.
With caching:
First Request
│
▼
Application
│
▼
Database
│
▼
Generate Response
│
▼
Store in Cache
Later requests become:
Request
│
▼
Cache
│
▼
Response
The expensive work has already been performed.
Caching is therefore not simply about storing files.
It is about avoiding repeated work.
Where Can Caching Exist?
A modern application may contain several caches simultaneously.
User
│
▼
Browser Cache
│
▼
CDN Cache
│
▼
Reverse Proxy Cache
│
▼
Application Cache
│
▼
Database Cache
│
▼
Database
Each layer solves a different problem.
For example:
Browser Cache
→ Avoid network requests entirely
CDN Cache
→ Avoid requests reaching the origin
Reverse Proxy Cache
→ Avoid application execution
Application Cache
→ Avoid expensive calculations
Database Cache
→ Avoid expensive data retrieval
This means “the cache” is rarely one thing.
Production applications frequently contain a cache hierarchy.
How CDN Caching Works
A CDN distributes cached content across geographically distributed infrastructure.
Without a CDN:
Europe User
│
│ Long Network Path
▼
US Origin
With a CDN:
Origin
│
Global CDN
┌──────────┼──────────┐
▼ ▼ ▼
Europe Asia America
│ │ │
Users Users Users
When a request arrives, the CDN determines whether it already has a valid copy of the requested object.
Conceptually:
Request
│
▼
CDN Edge
│
▼
Cache Lookup
│
├── HIT ─────────────► Response
│
└── MISS
│
▼
Origin
│
▼
Response
│
▼
Cache Copy
│
▼
User
AWS describes this behavior directly: when CloudFront finds a valid object corresponding to the generated cache key, the request becomes a cache hit and can be served from the edge rather than the origin.
This reduces both viewer latency and origin load.
Browser Cache vs CDN Cache
These two caches should not be confused.
A browser cache exists on the user’s device:
Browser
│
▼
Local Cache
A CDN cache exists on shared network infrastructure:
Many Users
│
▼
CDN Edge Cache
This difference matters because browser and shared-cache lifetimes often need to be different.
For example, you may want:
Browser Cache = 60 seconds
CDN Cache = 1 hour
Why?
Because you may want the CDN to reuse one generated response for thousands of users while still allowing individual browsers to check the CDN relatively frequently.
That distinction leads directly to max-age and s-maxage.
Cache HIT vs Cache MISS
Two of the most important caching terms are:
HIT
and:
MISS
A cache HIT means the cache already contains a valid response for the request.
Request
│
▼
Cache
│
▼
HIT
│
▼
Response
A cache MISS means the requested object is not currently available as a valid cached response.
Request
│
▼
Cache
│
▼
MISS
│
▼
Origin
The origin must then perform the work.
Why Cache HITs Matter
Suppose a dynamic page requires:
Application Processing = 80 ms
Database Query = 100 ms
API Call = 120 ms
Ignoring other overhead, generating the response may already require around:
300 ms
If the CDN can serve a cached copy in a fraction of that time, the entire application path changes.
More importantly, the backend does no work for that request.
One HIT can therefore save:
Compute
+
Database Work
+
API Work
+
Origin Bandwidth
Multiply this by millions of requests and caching becomes an infrastructure strategy rather than merely a frontend optimization.
What Is Cache Hit Ratio?
The cache hit ratio measures the proportion of eligible requests served from cache.
A simplified formula is:
Cache Hit Ratio =
Cache Hits
──────────────
Total Cache Requests
× 100
Suppose:
1,000,000 requests
produce:
850,000 HITs
150,000 MISSes
The cache hit ratio is:
85%
This means the origin avoids serving 850,000 of those requests directly.
AWS exposes CloudFront’s CacheHitRate as a metric representing the percentage of cacheable requests served from cache.
Why a Low Cache Hit Ratio Can Be Expensive
Consider two otherwise identical systems.
System A
1,000,000 requests
90% cache hit ratio
Origin requests:
≈ 100,000
System B
1,000,000 requests
20% cache hit ratio
Origin requests:
≈ 800,000
System B may require far more:
- compute;
- database capacity;
- bandwidth;
- function execution;
- application scaling.
Improving caching can therefore sometimes deliver more value than adding servers.
What Is TTL?
TTL means:
Time To Live.
It defines how long a cached object can remain fresh before it requires expiration or revalidation.
Example:
TTL = 3600 seconds
means:
Cache object considered fresh
for 1 hour
During that period:
Request → Cache → HIT
After expiration, the cache may need to:
Revalidate
or:
Fetch New Response
depending on the caching strategy.
Long TTL vs Short TTL
Long TTL:
Long TTL
│
├── More cache HITs
├── Less origin traffic
└── Greater stale-content risk
Short TTL:
Short TTL
│
├── Fresher content
├── More revalidation
└── More origin traffic
Caching architecture therefore balances:
PERFORMANCE
↕
FRESHNESS
The correct balance depends on the content.
Cache-Control Explained
HTTP provides the Cache-Control response header to communicate caching behavior.
Example:
Cache-Control: public, max-age=3600
This tells compatible caches that the response can be cached and considered fresh for 3,600 seconds.
But Cache-Control supports much more than one TTL.
Important directives include:
public
private
max-age
s-maxage
no-cache
no-store
must-revalidate
immutable
stale-while-revalidate
stale-if-error
Understanding these directives is fundamental to CDN architecture.
public
Example:
Cache-Control: public, max-age=3600
public indicates that a response may be stored by shared caches.
That can include:
CDN
Proxy Cache
Other Shared Cache
This is suitable for responses intended to be shared across users.
private
Example:
Cache-Control: private, max-age=60
This indicates that the response is intended for a private cache such as the user’s browser rather than a shared CDN cache.
This is important for personalized content.
For example:
/dashboard
might contain:
Hello, Alice
Account Balance
Private Messages
That response must not accidentally become:
Shared CDN Cache
│
▼
Another User
Personalized responses require careful caching rules.
no-store
Example:
Cache-Control: no-store
This tells caches not to store the response.
It can be appropriate for highly sensitive or immediately changing content.
For example:
Sensitive Financial Response
Authentication Response
One-Time Data
Not every dynamic page requires no-store, but truly sensitive responses should not enter shared caches accidentally.
no-cache Is Frequently Misunderstood
The name sounds like:
Never cache this.
That is not necessarily the correct interpretation.
no-cache generally means a stored response must be validated before being reused.
This is different from:
no-store
which means the response should not be stored.
That distinction matters when debugging HTTP caching.
max-age
max-age defines the freshness lifetime in seconds.
Example:
Cache-Control: public, max-age=300
means:
Fresh for 5 minutes
A browser or compatible cache may reuse the object during that window.
s-maxage
s-maxage is particularly important for CDN architecture because it targets shared caches.
Example:
Cache-Control: public, max-age=60, s-maxage=3600
Conceptually:
Browser:
60 seconds
Shared CDN:
3600 seconds
This lets architects control the two layers separately.
Vercel, for example, documents s-maxage as the duration a response remains fresh in its CDN cache.
A Practical max-age and s-maxage Strategy
Consider a blog article.
You might use:
Cache-Control: public, max-age=60, s-maxage=3600
The result is:
Browser
│
└── Reuse for 60 seconds
CDN
│
└── Reuse for 1 hour
This provides:
- short browser caching;
- aggressive shared caching;
- reduced origin load.
The exact values should follow your publishing requirements.
immutable
Content-addressed assets are ideal candidates for extremely long caching.
For example:
app.8f912c.js
styles.a9321.css
If the content changes, the filename changes.
Therefore:
app.8f912c.js
can safely remain cached for a long time because a future deployment might generate:
app.5bc912.js
A common strategy is:
Cache-Control: public, max-age=31536000, immutable
This effectively says:
This exact URL should not change.
Vercel currently recommends this type of strategy for immutable hashed static assets.
stale-while-revalidate
One of the most useful modern caching patterns is:
stale-while-revalidate
Traditional expiration can create this sequence:
Cache Expires
│
▼
Next User Waits
│
▼
Origin Generates Response
│
▼
Cache Updated
stale-while-revalidate changes the behavior:
Cache Becomes Stale
│
▼
Request Arrives
│
├── Serve Stale Response Immediately
│
└── Refresh Cache in Background
This can dramatically improve perceived performance.
stale-while-revalidate Example
Consider:
Cache-Control: public, s-maxage=60, stale-while-revalidate=300
Conceptually:
0–60 seconds
│
└── Fresh
60–360 seconds
│
├── Can serve stale
└── Revalidate in background
Netlify documents support for this pattern, allowing stale content to be returned while the cached response is refreshed in the background.
Vercel also supports stale-while-revalidate in its CDN caching model.
Why stale-while-revalidate Is Powerful
Suppose an API requires five seconds to generate a response.
Without SWR:
Cache Expired
│
▼
User waits 5 seconds
With SWR:
Cache Expired
│
├── User receives stale response quickly
│
└── Cache updates in background
This can work well for:
- blog indexes;
- product catalogs;
- documentation;
- public API responses;
- semi-static pages.
It is less suitable when every response must reflect immediate state.
stale-if-error
Another useful directive is:
stale-if-error
Imagine:
CDN
│
▼
Origin
│
X
500 Error
Without fallback:
User → 500
With an appropriate stale-if-error policy:
Origin Failure
│
▼
Serve Older Cached Response
This turns caching into an availability mechanism as well as a performance mechanism.
Vercel currently supports stale-if-error for applicable CDN responses.
AWS also documents stale directives as part of cache-hit optimization strategies.
Cache Keys Explained
A cache cannot simply ask:
Do I have this page?
It needs a precise identifier.
That identifier is the cache key.
Conceptually:
Request
│
▼
Generate Cache Key
│
▼
Look Up Object
For example:
https://example.com/products
might produce one cache key.
But what about:
https://example.com/products?category=laptops
or:
https://example.com/products?category=phones
Should these produce the same cached response?
Probably not.
The cache key determines the answer.
Cache Key Components
Depending on platform and configuration, a cache key may involve:
Scheme
+
Hostname
+
Path
+
Query String
+
Headers
+
Cookies
But including everything is usually a mistake.
Why?
Because every additional dimension can fragment the cache.
Cache Key Explosion
Suppose a page is identical for everyone.
But you include:
User-Agent
in the cache key.
Different browsers produce many different User-Agent values.
Instead of:
/page
│
▼
ONE cached object
you may create:
/page + Chrome
/page + Firefox
/page + Safari
/page + Chrome Mobile
/page + Edge
/page + thousands of variations
The result:
Cache Fragmentation
│
▼
Lower HIT Ratio
│
▼
More Origin Traffic
AWS specifically warns that highly variable headers such as User-Agent can generate many unnecessary cached copies and reduce cache efficiency.
The Golden Rule of Cache Keys
A useful principle is:
Include a request attribute in the cache key only when it can legitimately change the response.
If:
utm_source=facebook
does not change page content, there may be little value in creating a separate cached object for it.
If:
language=fr
does change content, language needs to be represented somehow.
Good cache architecture minimizes unnecessary variation without serving incorrect content.
Query Strings and Caching
Consider:
/article?id=100
and:
/article?id=200
If id changes the article, it must influence caching.
But tracking parameters may not:
/article?id=100&utm_source=google
/article?id=100&utm_source=newsletter
If both generate identical content but separate cache entries, cache efficiency falls.
Normalization can convert both into the logical equivalent:
/article?id=100
for caching purposes.
Cloudflare’s current Cache Rules allow query-string components to be included or excluded when constructing custom cache keys.
Cloudflare Cache Keys
Cloudflare currently documents a default cache key that includes the full URL—including scheme, host, URI and query string—plus several relevant request headers.
Cloudflare also supports custom cache keys that can control dimensions including:
Query Strings
Headers
Cookies
Host
User
This provides powerful control, but Cloudflare warns that additional variation can reduce the cache hit rate through cache sharding.
This is a fundamental CDN engineering trade-off:
More Specific Cache
│
├── More Accurate Variants
└── Fewer HITs
versus:
Simpler Cache
│
├── More HITs
└── Greater Risk of Incorrect Sharing
AWS CloudFront Cache Keys
AWS CloudFront also bases caching decisions on a cache key.
CloudFront’s default cache key uses the distribution domain and requested object path, while cache policies allow architects to include selected:
Query Strings
Headers
Cookies
when required.
AWS recommends including only the values needed to distinguish responses because unnecessary variation reduces the cache hit ratio.
That principle should guide CDN design regardless of provider.
Cookies and Caching
Cookies are particularly dangerous for cache efficiency.
Suppose every visitor has:
session_id=UNIQUE_VALUE
and that cookie becomes part of the cache key.
You effectively create:
User 1 → Cache Object 1
User 2 → Cache Object 2
User 3 → Cache Object 3
...
At that point:
Shared CDN Cache
starts behaving like:
Per-User Storage
and much of the CDN benefit disappears.
Worse, incorrect cookie handling can create privacy problems.
Personalized Content
Suppose:
/dashboard
generates:
Hello Alice
for Alice.
If that response enters a shared cache under only:
/dashboard
Bob could potentially receive Alice’s cached response.
This is catastrophic.
Therefore personalized content must use strategies such as:
private
or:
no-store
or carefully designed cache segmentation.
Never assume dynamic automatically means safe.
Caching rules must explicitly match the data model.
Separate Shared and Personalized Content
A better architecture can divide a page into:
Shared Content
+
Personalized Content
For example:
Product Page
│
├── Product Description → CDN Cache
├── Images → CDN Cache
├── Reviews → API Cache
└── User Cart → Private/Dynamic
This allows most of the page to benefit from caching while sensitive state remains private.
This pattern is often better than disabling caching for the entire page.
Dynamic API Caching
APIs can also be cached.
Suppose:
GET /api/weather?city=london
takes:
500 ms
because it calls an external service.
If the response can tolerate 60 seconds of staleness:
Cache-Control: public, s-maxage=60
can transform:
10,000 API requests
│
▼
10,000 backend executions
into something closer to:
10,000 requests
│
├── Mostly CDN HITs
│
└── Occasional origin refresh
This can significantly reduce both latency and backend cost.
Do Not Cache Every API
An API such as:
GET /api/products
may be cacheable.
An API such as:
GET /api/my-bank-account
should obviously not be stored in a public shared cache.
Similarly:
POST /api/payment
is not a normal CDN-cache candidate.
Cache design must follow request semantics.
Cache Invalidation
Caching introduces one of computer science’s famous difficult problems:
How do you remove data when it is no longer valid?
Suppose:
/article
is cached for one hour.
You publish an update after five minutes.
Without invalidation:
Origin = New Article
CDN = Old Article
Users may continue seeing stale content.
Several strategies exist.
Strategy 1: Wait for TTL Expiration
Simple:
Publish
│
▼
Wait
│
▼
TTL Expires
│
▼
New Version Fetched
Easy, but updates are delayed.
Strategy 2: Purge
Tell the CDN:
Delete cached /article
The next request becomes a MISS:
Request
│
▼
MISS
│
▼
Origin
│
▼
New Content
This provides faster updates.
Strategy 3: Versioned URLs
Static assets can use content hashes:
style.a123.css
When content changes:
style.b456.css
No purge is required because the URL itself changes.
This is one of the strongest caching patterns for immutable assets.
Strategy 4: Cache Tags
Some platforms allow related objects to be tagged.
Example:
Tag: product-123
could identify:
/products/123
/api/products/123
/category/laptops
When the product changes, the application can invalidate related cache objects by tag rather than clearing everything.
This makes invalidation more precise.
Purge Everything Is Not Always a Good Strategy
Imagine a CDN containing:
1,000,000 cached objects
and only one article changes.
Purging the entire cache can create:
Mass Cache MISS
│
▼
Traffic Spike to Origin
This can cause an origin-load surge known conceptually as a cache stampede.
Granular invalidation is usually preferable.
What Is a Cache Stampede?
Suppose a very popular cached object expires.
Immediately:
10,000 Requests
│
▼
Expired Cache
│
▼
Origin
If all requests independently regenerate the same response, the origin may be overwhelmed.
A better architecture performs request collapsing or revalidation:
10,000 Requests
│
▼
Cache
│
├── One Refresh → Origin
│
└── Others → Cached/Stale Response
Modern CDN systems use several techniques to mitigate this class of problem.
AWS CloudFront Caching Architecture
A simplified AWS architecture is:
Users
│
▼
CloudFront Edge
│
├── HIT ─────────► Response
│
└── MISS
│
▼
Origin
The origin could be:
Amazon S3
Application Load Balancer
EC2
API
External Server
AWS CloudFront cache policies control important caching characteristics such as:
- TTL;
- query strings;
- cookies;
- request headers;
- compression settings.
AWS also exposes cache-hit statistics and origin-latency metrics through its monitoring stack.
For broader AWS edge execution architecture, see our Edge Computing Explained guide.
Cloudflare Caching Architecture
Cloudflare places its cache in front of proxied origins:
User
│
▼
Cloudflare
│
├── HIT ───────► User
│
└── MISS
│
▼
Origin
Cloudflare currently caches common static resources by default, while dynamic HTML is generally not cached by default unless configured through caching rules.
This distinction matters.
Installing a CDN does not automatically mean every HTML page is being cached.
Caching behavior must be verified.
Cloudflare provides Cache Rules for controlling which resources are eligible and how they should be cached.
Cloudflare Tiered Caching
A basic CDN can produce:
Edge A ──► Origin
Edge B ──► Origin
Edge C ──► Origin
A tiered architecture introduces an intermediate cache layer:
Edge A ──┐
Edge B ──┼──► Upper-Tier Cache ──► Origin
Edge C ──┘
Now multiple edge locations can reuse upstream cached content instead of independently contacting the origin.
Cloudflare currently provides Tiered Cache as a mechanism for reducing origin traffic by organizing cache access through multiple caching layers.
This is particularly valuable for origins with expensive or limited capacity.
Vercel CDN Caching
Vercel’s current CDN architecture automatically caches static files and allows dynamic responses—including applicable server-rendered pages and API responses—to be controlled using caching headers.
A simplified path is:
User
│
▼
Vercel CDN
│
├── HIT
│ │
│ ▼
│ Response
│
└── MISS
│
▼
Function / Origin
Vercel supports:
Cache-Control
CDN-Cache-Control
Vercel-CDN-Cache-Control
for different caching scopes.
This makes it possible to separate:
Browser TTL
from:
Generic CDN TTL
and:
Vercel-specific TTL
when required.
Vercel Targeted Cache Control
For example:
Cache-Control: max-age=10
CDN-Cache-Control: max-age=60
Vercel-CDN-Cache-Control: max-age=3600
can conceptually establish different policies for:
Browser
│
└── 10 seconds
Other CDN
│
└── 60 seconds
Vercel CDN
│
└── 3600 seconds
Vercel’s current documentation defines Vercel-CDN-Cache-Control as its highest-priority targeted cache-control header, followed by CDN-Cache-Control, then standard Cache-Control.
This level of control becomes useful in multi-layer delivery architectures.
Netlify Caching
Netlify also distinguishes between static and dynamic caching.
Static deployment assets are cached globally and automatically invalidated when deployments change them.
Responses generated by Functions, Edge Functions, and proxies are not cached by default, but caching can be enabled using response headers.
Netlify supports:
Netlify-CDN-Cache-Control
CDN-Cache-Control
Cache-Control
along with directives including:
s-maxage
max-age
stale-while-revalidate
This allows application developers to control shared and browser caching independently.
A Practical Netlify Example
Netlify documents a pattern similar to:
Netlify-CDN-Cache-Control: public, max-age=60, stale-while-revalidate=120
The architecture becomes:
First Request
│
▼
Generate
│
▼
Cache
then:
Fresh Window
│
▼
Serve Cache
and later:
Stale Window
│
├── Serve Existing Response
└── Refresh in Background
This is particularly useful for expensive API responses that can tolerate limited staleness.
CDN Caching vs Application Caching
Do not confuse:
CDN Cache
with:
Application Cache
A CDN cache stores HTTP responses.
Application caching might store:
Database Query Result
Rendered Fragment
Session Data
Computed Object
API Result
For example:
Request
│
▼
CDN MISS
│
▼
Application
│
▼
Redis HIT
│
▼
Response
Even though the CDN missed, the application still avoided a database query.
Multiple cache layers can work together.
CDN Cache vs Redis
These technologies solve different problems.
CDN
│
└── HTTP response delivery near users
while:
Redis
│
└── Fast data access near applications
A common architecture is:
User
│
▼
CDN
│
▼
Application
│
▼
Redis
│
▼
Database
Each layer progressively protects the one behind it.
Reverse Proxy Caching
A reverse proxy such as Nginx can also cache responses.
Architecture:
Internet
│
▼
CDN
│
▼
Nginx
│
▼
Application
Caching can potentially exist at:
CDN
+
Nginx
+
Application
This can be powerful but increases complexity.
You need to understand:
Which layer owns freshness?
Which layer receives invalidation?
Which headers control each layer?
Otherwise, stale-content problems become difficult to diagnose.
Cache Poisoning
Caching can become a security vulnerability when an attacker manipulates a request in a way that causes a malicious or incorrect response to be stored and served to other users.
Conceptually:
Attacker
│
▼
Manipulated Request
│
▼
Origin Generates Bad Response
│
▼
CDN Caches Response
│
▼
Other Users Receive It
The danger is amplification.
A normal application vulnerability may affect one request.
A poisoned shared cache may distribute the result to many users.
Cache Key Security
Suppose the origin changes its response based on:
X-Custom-Language
but the CDN cache key ignores that header.
Request A:
X-Custom-Language: en
generates English.
Request B:
X-Custom-Language: fr
should generate French.
If both share the same cache key:
/page
one language can be incorrectly served to another request.
The broader security rule is:
Every request property that changes a cacheable response must be understood by the caching layer.
This is why cache-key design is both a performance and correctness problem.
Cache Deception
Another class of cache problems occurs when dynamic or private content is mistakenly interpreted as cacheable static content.
For example, poorly configured infrastructure might treat:
/account/profile.css
as a static-looking URL even though the backend routes it to private account data.
If a CDN caches the resulting response, sensitive data may be exposed.
Caching rules should therefore be based on application semantics—not merely filename patterns.
Caching Authentication Responses
As a general principle:
Authenticated User-Specific Response
should not be placed into a shared cache unless the architecture explicitly guarantees correct separation.
Vercel’s current CDN criteria, for example, exclude responses from normal caching under conditions including authorization-bearing requests, Set-Cookie, and private/no-store directives.
These safeguards help, but application developers must still understand the data being returned.
Negative Caching
Caching is not limited to successful responses.
Some architectures cache responses such as:
404 Not Found
for a limited period.
This can protect origins from repeated requests for nonexistent resources.
Example:
Bot requests missing.jpg
│
▼
Origin returns 404
│
▼
CDN caches 404 briefly
Later bot requests:
Bot
│
▼
CDN
│
▼
Cached 404
No origin work is required.
But long-lived negative caching can delay newly created resources from becoming visible.
Caching Redirects
Redirects can also be cached.
For example:
301 /old-page → /new-page
may be stored by browsers and intermediaries.
Be especially careful with permanent redirects because an incorrect 301 can persist longer than expected.
When testing migrations, temporary redirects may be safer until routing behavior is confirmed.
Caching and SEO
Caching indirectly affects SEO through:
- Time to First Byte;
- page availability;
- Core Web Vitals;
- crawler efficiency;
- redirect behavior;
- content consistency.
But incorrect caching can create SEO problems.
For example:
Old Title Cached
New Title at Origin
can delay search-engine discovery of updates.
More serious mistakes can create:
Wrong Canonical Header
Wrong Redirect
Wrong Language Variant
Wrong HTTP Status
at CDN scale.
Caching therefore needs to be part of technical SEO reviews.
WordPress and CDN Caching
A WordPress site adds another caching layer.
Typical architecture:
Visitor
│
▼
CDN
│
▼
Web Server Cache
│
▼
PHP
│
▼
Object Cache
│
▼
Database
Possible layers include:
Cloudflare
LiteSpeed Cache
Nginx FastCGI Cache
Redis Object Cache
Browser Cache
Using all of them without understanding their roles can create invalidation problems.
A better approach is to define exactly which layer caches what.
Static Assets: Cache Aggressively
Versioned assets such as:
app.a71bc.js
style.12a9.css
logo.72e91.webp
are excellent long-term cache candidates.
A typical strategy:
Cache-Control: public, max-age=31536000, immutable
Because the filename changes when the content changes, old objects can remain cached safely.
HTML: Cache Carefully
HTML changes more frequently.
Possible strategy:
Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=600
Conceptually:
Browser
│
└── Revalidate frequently
CDN
│
└── Cache for 5 minutes
+
serve stale during refresh
This can work well for public content sites.
But actual values should reflect publishing frequency and correctness requirements.
API Responses: Cache Selectively
Consider:
/api/categories
Perhaps it changes once per day.
Aggressive caching may make sense.
But:
/api/stock-price
might require near-real-time data.
And:
/api/account
may contain private information.
Therefore:
API
│
├── Public + Stable → Cache
├── Public + Semi-Dynamic → Short Cache / SWR
├── Real-Time → Little/No Cache
└── Private → Never Shared Cache
Caching should be determined per route.
Practical Caching Strategy by Content Type
| Content | Example Strategy |
|---|---|
| Hashed JS/CSS | Very long TTL + immutable |
| Images with versioned URLs | Long TTL |
| Public blog posts | CDN cache + controlled revalidation |
| Homepage | Shorter CDN TTL |
| Public API data | Route-specific TTL |
| User dashboard | Private/no shared cache |
| Authentication response | Usually no shared caching |
| Real-time data | Minimal caching |
| 404 responses | Short negative cache where appropriate |
These are architectural starting points—not universal values.
How to Debug Caching with curl
One of the most useful commands is:
curl -I https://example.com
Look for headers such as:
Cache-Control
Age
ETag
Last-Modified
Vary
CF-Cache-Status
X-Cache
x-vercel-cache
Exact headers vary by provider.
Run the command multiple times:
curl -I https://example.com
curl -I https://example.com
curl -I https://example.com
If caching works, provider-specific status headers may change from something equivalent to:
MISS
to:
HIT
depending on platform behavior.
Debug Cache-Control
Use:
curl -sI https://example.com | grep -i cache
This can quickly expose relevant cache headers.
For more detailed HTTP debugging:
curl -v https://example.com
Our Linux Network Tools Guide provides a broader toolkit for investigating network and HTTP behavior.
Test Different Query Strings
Try:
curl -I "https://example.com/page?id=1"
then:
curl -I "https://example.com/page?id=2"
and:
curl -I "https://example.com/page?id=1&utm_source=test"
Observe whether cache behavior changes.
This can reveal unnecessary cache fragmentation.
Test Cookies
You can also test:
curl -I \
-H "Cookie: test=value" \
https://example.com/page
Compare the result without the cookie.
If an irrelevant cookie destroys caching, cache efficiency may be unnecessarily reduced.
Be careful when testing authenticated systems.
Monitor Origin Traffic
Do not evaluate caching only from the browser.
A successful caching strategy should also change origin metrics.
Look for reductions in:
Requests
CPU
Database Queries
Function Invocations
Origin Bandwidth
If CDN HITs appear high but backend load remains unchanged, another application path may still be bypassing the cache.
Monitor Cache Hit Ratio
A production dashboard should include:
Cache HIT Rate
Cache MISS Rate
Origin Latency
Origin Request Count
Error Rate
Bandwidth
Changes in hit ratio can reveal configuration regressions.
For example:
Yesterday:
HIT = 92%
Today:
HIT = 24%
This deserves investigation even if the website still appears functional.
Common Causes of Low Cache Hit Ratio
Excessive Query Parameters
?page=1&utm_source=a
?page=1&utm_source=b
?page=1&utm_source=c
Cookies in Cache Keys
Unique session values create fragmentation.
Excessive Headers
Highly variable headers generate unnecessary variants.
Very Short TTLs
Objects expire before they can be reused.
Frequent Global Purges
The cache constantly becomes cold.
Low Traffic Per Object
Rarely requested objects naturally have fewer opportunities for HITs.
Personalized Responses
Some content genuinely cannot be shared.
A low hit ratio is therefore not automatically a configuration failure.
Context matters.
Cold Cache vs Warm Cache
A cold cache has little reusable content.
Request
│
▼
MISS
A warm cache contains frequently requested objects.
Request
│
▼
HIT
Immediately after:
Deployment
Purge
New Region Activation
cache HIT rates may temporarily fall.
This is normal.
The key question is whether frequently accessed objects become warm efficiently.
Origin Shield Architecture
Large CDNs may use another cache layer between edge locations and the origin.
Without shield:
Edge A ─────► Origin
Edge B ─────► Origin
Edge C ─────► Origin
Edge D ─────► Origin
With shield:
Edge A ──┐
Edge B ──┤
Edge C ──┼──► Shield Cache ──► Origin
Edge D ──┘
AWS provides CloudFront Origin Shield as an additional caching layer designed to improve cache efficiency and reduce requests reaching origins.
This can be useful for high-traffic architectures with many geographically distributed edge locations.
Caching and Autoscaling
Caching can dramatically reduce scaling requirements.
Without cache:
Traffic × 10
│
▼
Origin Requests × 10
│
▼
Need More Compute
With a high cache hit ratio:
Traffic × 10
│
▼
CDN Absorbs Most Requests
│
▼
Smaller Origin Increase
This complements the concepts in our Cloud Auto Scaling Explained guide.
Autoscaling handles increased compute demand.
Caching tries to prevent unnecessary compute demand from existing in the first place.
Caching and Load Balancing
Consider:
Internet
│
▼
CDN
│
▼
Load Balancer
│
├── Server A
├── Server B
└── Server C
Every cache HIT prevents a request from reaching:
Load Balancer
+
Server
+
Application
Caching therefore sits in front of load balancing as a traffic-reduction mechanism.
For the next infrastructure layer, see Cloud Load Balancing Explained.
Caching and Edge Computing
Caching and edge computing work especially well together.
Caching asks:
Can we reuse an existing response?
Edge computing asks:
If computation is required, can some of it happen closer to the user?
The combined architecture becomes:
User
│
▼
Edge
│
├── Cache HIT
│ │
│ ▼
│ Response
│
└── Cache MISS
│
▼
Edge Function
│
├── Can Generate?
│
└── Need Origin?
│
▼
Cloud
This is why the Edge Computing Explained article and this caching guide should be tightly linked.
Production Caching Architecture
A mature architecture might look like:
Global Users
│
▼
DNS
│
▼
CDN
│
┌───────────┴───────────┐
│ │
CACHE HIT CACHE MISS
│ │
▼ ▼
Response Edge Logic
│
▼
Origin Shield
│
▼
Load Balancer
│
┌──────────┴──────────┐
▼ ▼
Application A Application B
│ │
└──────────┬──────────┘
▼
Application Cache
│
▼
Database
Each layer has a specific responsibility.
Observability should measure each layer separately.
A Better Caching Decision Framework
For every route, ask:
Is the response identical for multiple users?
If yes, shared caching may be possible.
How quickly does the content change?
This determines TTL.
Can slightly stale content be tolerated?
If yes, consider:
stale-while-revalidate
Can stale content be served during origin failure?
If yes, consider:
stale-if-error
Which request values change the response?
These may belong in the cache key.
Is the content sensitive?
If yes, avoid shared caching.
How is content invalidated?
Choose:
TTL
Purge
Tags
Versioned URL
Revalidation
before production.
Caching Best Practices
A strong caching strategy usually follows these principles.
Cache immutable assets aggressively.
Use versioned filenames and long TTLs.
Keep cache keys as simple as correctness allows.
Every unnecessary dimension reduces cache efficiency.
Separate personalized data from shared content.
Do not sacrifice an entire page’s cacheability because one small component is user-specific.
Use stale content strategically.
stale-while-revalidate can provide both freshness and low latency.
Plan invalidation before deployment.
Do not wait until stale content reaches production to decide how purging works.
Measure cache HIT ratio.
If caching is important, it deserves monitoring.
Protect the cache from untrusted variation.
Cache keys are a security boundary as well as a performance configuration.
Test the origin separately.
A fast cache can hide a very slow backend until a purge occurs.
Frequently Asked Questions
What is CDN caching?
CDN caching stores copies of HTTP responses on geographically distributed infrastructure so that future requests can be served closer to users without repeatedly contacting the origin.
What is a cache HIT?
A cache HIT occurs when the CDN finds a valid cached object matching the request’s cache key and serves it without retrieving a new copy from the origin.
What is a cache MISS?
A cache MISS occurs when the required valid object is not available in cache, so the CDN must retrieve or generate it through the origin path.
What is cache hit ratio?
Cache hit ratio is the proportion of requests served successfully from cache relative to the relevant request population.
What is TTL in caching?
TTL, or Time To Live, controls how long a cached object remains fresh before expiration or revalidation.
What is a cache key?
A cache key is the identifier a cache uses to distinguish one cached object from another. Depending on configuration, it can incorporate the URL, query strings, headers, cookies, and other request attributes.
What does max-age mean?
max-age specifies how long a response can remain fresh in compatible caches.
What does s-maxage mean?
s-maxage specifies a freshness lifetime for shared caches such as CDNs and can override max-age for those caches.
What is stale-while-revalidate?
It allows a cache to serve a stale response immediately while refreshing that response asynchronously in the background.
What is stale-if-error?
It allows eligible stale content to be served when refreshing from the origin encounters an error, depending on CDN support and configuration.
Should HTML be cached?
Public HTML can often be cached effectively with appropriate TTL and invalidation rules. Personalized or highly dynamic HTML requires more careful handling.
Should API responses be cached?
Some public GET API responses are excellent cache candidates. Private, transactional, personalized, or real-time APIs may not be suitable for shared caching.
What is cache poisoning?
Cache poisoning occurs when manipulated input causes an incorrect or malicious response to enter a shared cache and potentially be served to other users.
Does a CDN automatically cache everything?
No. Cache eligibility varies by provider, HTTP method, headers, content type, response status, cookies, and configuration. For example, Cloudflare normally caches common static resources by default but does not simply cache all dynamic HTML automatically.
Final Thoughts
Caching changes the economics and architecture of web applications.
Without caching:
Every User
│
▼
Every Request
│
▼
Origin
│
▼
Application
│
▼
Database
With effective caching:
Millions of Requests
│
▼
CDN
│
├── HIT
│ │
│ ▼
│ Response
│
└── MISS
│
▼
Origin
The goal is not to maximize caching blindly.
The goal is to cache the correct data, for the correct duration, under the correct key, at the correct layer.
A good cache key improves reuse without mixing responses that should remain distinct.
A good TTL reduces unnecessary origin work without making content unacceptably stale.
stale-while-revalidate can reduce latency during refreshes.
stale-if-error can increase resilience during origin problems.
Versioned assets can remain cached for extremely long periods.
Private responses must remain private.
And cache invalidation must be designed as part of the application—not treated as an emergency operation after deployment.
This leads to an important architecture principle:
Fast Infrastructure
+
Poor Cache Strategy
=
Unnecessary Work
while:
Good Architecture
+
Effective Caching
=
Lower Latency
+
Lower Origin Load
+
Better Scalability
+
Potentially Lower Cost
Caching is therefore not merely a browser optimization.
It is a fundamental component of distributed systems architecture.
Continue with Edge Computing Explained to understand where distributed execution fits around the caching layer.
For frontend delivery platforms, see AWS Amplify vs Vercel vs Netlify vs Cloudflare Pages.
For the infrastructure underneath these systems, continue with Cloud Network Architecture: AWS vs Azure vs Google Cloud, Cloud Load Balancing Explained, and Cloud Auto Scaling Explained.
The CorgiNetwork architecture path now becomes:
Frontend Deployment → Edge Computing → CDN Caching → Load Balancing → Cloud Networking → Scaling → Infrastructure Automation.