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:
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.
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.
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.
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.
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.
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.
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.
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.
Consider two otherwise identical systems.
1,000,000 requests
90% cache hit ratio Origin requests:
≈ 100,000 1,000,000 requests
20% cache hit ratio Origin requests:
≈ 800,000 System B may require far more:
Improving caching can therefore sometimes deliver more value than adding servers.
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:
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.
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.
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.
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.
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.
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 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 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.
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:
The exact values should follow your publishing requirements.
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.
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.
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.
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:
It is less suitable when every response must reflect immediate state.
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.
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.
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.
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.
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.
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 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 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 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.
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.
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.
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.
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.
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.
Simple:
Publish
│
▼
Wait
│
▼
TTL Expires
│
▼
New Version Fetched Easy, but updates are delayed.
Tell the CDN:
Delete cached /article The next request becomes a MISS:
Request
│
▼
MISS
│
▼
Origin
│
▼
New Content This provides faster updates.
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.
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.
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.
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.
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:
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 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.
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’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.
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 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 indirectly affects SEO through:
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.
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.
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 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.
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.
| 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.
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.
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.
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.
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.
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.
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.
?page=1&utm_source=a
?page=1&utm_source=b
?page=1&utm_source=c Unique session values create fragmentation.
Highly variable headers generate unnecessary variants.
Objects expire before they can be reused.
The cache constantly becomes cold.
Rarely requested objects naturally have fewer opportunities for HITs.
Some content genuinely cannot be shared.
A low hit ratio is therefore not automatically a configuration failure.
Context matters.
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.
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 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.
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 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.
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.
For every route, ask:
If yes, shared caching may be possible.
This determines TTL.
If yes, consider:
stale-while-revalidate If yes, consider:
stale-if-error These may belong in the cache key.
If yes, avoid shared caching.
Choose:
TTL
Purge
Tags
Versioned URL
Revalidation before production.
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.
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.
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.
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.
Cache hit ratio is the proportion of requests served successfully from cache relative to the relevant request population.
TTL, or Time To Live, controls how long a cached object remains fresh before expiration or revalidation.
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.
max-age specifies how long a response can remain fresh in compatible caches.
s-maxage specifies a freshness lifetime for shared caches such as CDNs and can override max-age for those caches.
It allows a cache to serve a stale response immediately while refreshing that response asynchronously in the background.
It allows eligible stale content to be served when refreshing from the origin encounters an error, depending on CDN support and configuration.
Public HTML can often be cached effectively with appropriate TTL and invalidation rules. Personalized or highly dynamic HTML requires more careful handling.
Some public GET API responses are excellent cache candidates. Private, transactional, personalized, or real-time APIs may not be suitable for shared caching.
Cache poisoning occurs when manipulated input causes an incorrect or malicious response to enter a shared cache and potentially be served to other users.
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.
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.
AI Agents as Code: Managing AI Agent Configurations, Prompts and Tools with Git AI agents…
Data Center Power Distribution Architecture: UPS, PDU, Redundancy and Rack Power Explained Data center power…
DNSSEC Troubleshooting: Fix SERVFAIL & DNSKEY Errors DNSSEC Troubleshooting: How to Fix SERVFAIL, DS, DNSKEY…
Terraform State Management Explained: Remote State, Locking, Backends and Best Practices Terraform makes infrastructure reproducible…
RTO vs RPO Explained: Disaster Recovery Objectives, Examples and Planning A disaster recovery plan is…
Network Intrusion Detection Explained: NIDS, Sensors, Signatures and Traffic Analysis A firewall can decide whether…