Caching is the cheapest way to make a site faster, but each cache layer suits different content. This guide maps what to cache where, from the visitor's browser to your database, with examples for Cloudflare, nginx and Redis, and notes on which layers you control on Domain India hosting.
Your app has caching opportunities at 5 distinct layers: browser, CDN edge, reverse proxy, app memory and database. Putting the right cache at the right layer can cut response times from hundreds of milliseconds to tens. This guide maps every cache decision, with Domain India hosting and Cloudflare examples.
The 5 caching layers
| Layer | Latency to user | TTL typical | Who sets it |
|---|---|---|---|
| Browser (HTTP cache) | 0ms | minutes-days | HTTP headers |
| CDN edge (Cloudflare) | 10-50ms | seconds-days | HTTP headers + rules |
| Reverse proxy (nginx, Varnish) | 1-5ms | seconds-minutes | proxy config |
| Application cache (Redis, memcached) | 1-2ms | minutes-hours | app code |
| Database (query cache, materialised views) | 0-10ms | varies | DB config / app |
Each layer catches different things. Use them together.
On shared hosting you control the browser layer (headers in .htaccess) and the CDN layer (Cloudflare). Our cPanel servers already run an nginx caching proxy in front of Apache; DirectAdmin servers have no nginx proxy, so Apache answers every request. Redis and Memcached are not available on shared hosting or the App Platform; to run them, or your own nginx or Varnish cache, use a VPS.
Decision matrix — what goes where
| Content type | Best cache layer | TTL |
|---|---|---|
| Static assets (CSS, JS, images, fonts) | Browser + CDN | 1 year |
| Public HTML pages (marketing) | CDN | 1-24 hours |
| Personalised HTML (dashboard) | App cache (per-user) | Minutes |
| DB query results (user profile) | App (Redis) | Minutes |
| DB query results (product list) | App (Redis) + CDN API cache | 1-5 min |
| Third-party API responses | App (Redis) | Hours |
| Computed views (aggregations) | Materialised view + App | Hours-days |
| Session data | App (Redis) | Until expiry |
| Rate-limit counters | App (Redis) | Window |
Layer 1 — Browser cache
The cheapest cache: the browser serves the file without any network request.
Set HTTP headers. On your own nginx server:
location ~* \.(jpg|jpeg|png|gif|webp|svg|css|js|woff2|ttf)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary "Accept-Encoding";
}
location /api/ {
expires -1; # no cache for API
add_header Cache-Control "no-store";
}
location / {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}On Domain India shared hosting (Apache), set the same headers in .htaccess; mod_expires and mod_headers are loaded on our cPanel and DirectAdmin servers:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType image/webp "access plus 1 year"
</IfModule>Compression is already handled on our shared servers (gzip through nginx on cPanel, mod_deflate on DirectAdmin), so you don't need to add it yourself.
Fingerprinting your static files breaks the cache automatically:
bundle.abc123.js — when the content changes, the hash changes and the browser fetches the new file. Set TTL = forever.
Layer 2 — CDN edge (Cloudflare)
Cloudflare caches what you tell it to. By default: static files yes, HTML no.
Cache HTML with a Cache Rule
Cloudflare has replaced Page Rules with Cache Rules for caching settings. In the Cloudflare dashboard, open Caching → Cache Rules and create a rule:
When: Hostname equals yourcompany.com
Then:
Cache eligibility: Eligible for cache
Edge TTL: 2 hours
Browser TTL: 30 minutesNow public HTML pages are cached at the edge too.
Bypass cache for dynamic paths and logged-in users
Add a second rule, placed after the first so it wins:
When: URI Path starts with /admin/
OR Cookie contains "wp_logged_in"
OR Cookie contains "sessionid"
Then:
Cache eligibility: Bypass cacheWhen a login cookie is set, Cloudflare skips the cache and goes to your server. Logged-out users get the cached page; logged-in users get fresh pages.
Cloudflare's own analytics
See the hit ratio in Cloudflare → Analytics → Cache.
Target: >70% hit ratio for content sites. Dashboard apps will be lower but still benefit from static-asset caching.
Purge cache after deploy
curl -X POST "https://api.cloudflare.com/client/v4/zones/YOUR_ZONE_ID/purge_cache" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'Or purge specific URLs:
--data '{"files":["https://yourcompany.com/api/users","https://yourcompany.com/"]}'Add this to your deploy pipeline.
Layer 3 — Reverse proxy (nginx / Varnish)
A reverse proxy caches responses after your app generates them, before they go out to Cloudflare or the visitor.
On Domain India cPanel hosting this layer already exists: nginx runs in front of Apache and can keep 200, 301 and 302 responses for up to 120 minutes (measured 22 September 2026). You can't configure it from your account. If a change doesn't show, add ?t= with a new value to the URL to check. Domain India DirectAdmin hosting has no proxy cache, so use browser headers and Cloudflare there.
On your own VPS, configure nginx yourself:
proxy_cache_path /var/cache/nginx keys_zone=STATIC:10m max_size=1g inactive=60m;
location /api/ {
proxy_cache STATIC;
proxy_cache_valid 200 5m;
proxy_cache_valid 404 1m;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://app;
}The X-Cache-Status: HIT / MISS header shows the cache state.
Varnish (alternative, more powerful, VPS only):
sub vcl_backend_response {
set beresp.ttl = 5m;
set beresp.grace = 1h; # serve stale during outages
}See our Server-Side Caching article.
Layer 4 — Application cache (Redis)
The most flexible layer: your code decides what to cache. Redis is not available on Domain India shared hosting or the App Platform, so this layer runs on your own VPS; on shared hosting, use your framework's database or file cache instead.
See full patterns in our Redis Beyond Caching article.
Classic cache-aside:
async function getUser(id: string) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached);
const user = await db.user.findUnique({ where: { id } });
await redis.setEx(`user:${id}`, 300, JSON.stringify(user));
return user;
}Cache tags (Laravel, Rails) — invalidate related keys together:
// Laravel
Cache::tags(['users', "user:{$id}"])->put("user.{$id}.profile", $data, 300);
// Later invalidate just this user:
Cache::tags(["user:{$id}"])->flush();Layer 5 — Database-level
- Postgres materialized views for expensive aggregations:
sql CREATE MATERIALIZED VIEW daily_stats AS SELECT date(created_at), count(*), sum(amount) FROM orders GROUP BY 1; REFRESH MATERIALIZED VIEW daily_stats; -- schedule hourly - Postgres
pg_prewarm— load hot tables into shared_buffers on startup - MySQL query cache (removed in 8.0; cache in your app instead)
- Read replicas — not caching exactly, but they spread the load
Invalidation strategies
Three approaches:
TTL-only (easy, eventual consistency)
Cache: 300s TTL
Updates: ignored
Result: users see stale up to 5 minWorks for: product catalogues, articles, rankings.
Explicit invalidation
async function updateUser(id, data) {
await db.user.update({ where: { id }, data });
await redis.del(`user:${id}`);
}Works for: user profiles, settings, anything with a clear "owner".
Write-through
async function updateUser(id, data) {
const user = await db.user.update({ where: { id }, data });
await redis.setEx(`user:${id}`, 300, JSON.stringify(user));
}The cache always matches the database. Best consistency, most code.
Cache stampede prevention
When a popular key expires, 1,000 concurrent requests can all hit the database. Prevent it with:
Lock (SET NX):
async function withLock(key, callback, lockTtl = 10) {
const lockKey = `lock:${key}`;
const acquired = await redis.set(lockKey, '1', { NX: true, EX: lockTtl });
if (acquired) {
try { return await callback(); } finally { await redis.del(lockKey); }
}
// Someone else is computing — wait briefly and try the cache again
await new Promise(r => setTimeout(r, 100));
const cached = await redis.get(key);
return cached ? JSON.parse(cached) : callback();
}Stale-while-revalidate / early refresh: see our Laravel + Redis cache article.
Hit ratio targets
Measure. Without data, you're guessing:
| Layer | Target hit rate |
|---|---|
| Browser cache | 60-80% |
| Cloudflare | >70% (content), >50% (apps) |
| nginx proxy cache | >80% for what you cache |
| Redis app cache | >80% |
| PostgreSQL buffer cache | About 99% for a warm database |
Monitor via:
redis-cli INFO stats→keyspace_hits / (keyspace_hits + keyspace_misses)- Cloudflare Analytics → Cache
- PostgreSQL:
blks_hit / (blks_hit + blks_read)frompg_stat_database X-Cache-Statusnginx header aggregated in logs
Common pitfalls
FAQ
Cloudflare + nginx cache — overkill?
It depends on traffic. Cloudflare answers cached requests at the edge; an nginx cache on the origin catches what reaches your server. For most sites, Cloudflare plus Domain India's cPanel caching proxy is enough.
How do I cache pages for logged-in users?
Either use per-user cache keys (which cost more memory) or separate the static and dynamic parts of the page. See our Next.js App Router article on Partial Prerendering.
Does Cloudflare cost extra for high cache usage?
Cloudflare's plans don't charge for cached bandwidth. The saving is on your origin, which serves fewer requests.
CDN for APIs?
Yes for public, read-only APIs. Set a short TTL (30-60s) and Cache-Control: public. Bypass the cache for user-specific APIs.
Can I use Redis for the application cache on Domain India shared hosting?
No. Redis and Memcached are not available on Domain India shared hosting or the App Platform. Use your framework's database or file cache there, or run Redis on a Domain India VPS.
When does caching hurt?
When it hides bugs (stale data looks like a broken app), when the cost of inconsistency is higher than the cost of a slower response, and when debugging slows down because you can't tell whether the cache or the code is at fault.
Ready to build a fast cache stack? Put Cloudflare in front of cPanel hosting, or choose a VPS to run Redis and your own proxy cache.
cPanel hosting runs an nginx caching proxy in front of Apache. Add Cloudflare for edge caching.
View cPanel hosting