GH GambleHub

CDN caching and TTL optimization

Brief Summary

A CDN cache is an "accelerator + shield" between the user and origin. It works well when:

1. The cache key is stable and contains no "noise."

2. TTL policy under load: 's-maxage '/' max-age' + 'stale-while-escalate/if-error'.

3. Disability is managed: by tags/prefixes + "soft" purge.

4. Tiered-cache/origin-shield and negative-cache are included.

5. There is an observability: hit-ratio by layers, p95 TTFB, return share 304.

Base headers and what they mean

`Cache-Control`:
  • 'max-age = '- TTL for browser.
  • 's-maxage = '- TTL for CDN/proxy (overlaps'max-age').
  • 'stale-while-revalidate = '- give out outdated, update in parallel.
  • 'stale-if-error = '- we return the outdated one when the origin error occurs.
  • 'immutable '- the resource does not change (suitable for versioned assets).
  • 'ETag '/' Last-Modified '- conditions for 304, save bytes/CPU origin.
  • 'Vary '- a list of headers that affect the cache key (use with restraint!).
  • 'Surrogate-Control '- "extended" Cache-Control for CDN (if supported).
  • 'Expires' - obsolete, but still accounted for by customers.
Example (statics, year):

Cache-Control: public, max-age=31536000, immutable
Example (semi-speaker with secure obsolescence):

Cache-Control: public, s-maxage=300, max-age=60, stale-while-revalidate=600, stale-if-error=86400
ETag: "a1c3..."

Cache Key Design and Normalization

The goal is for essentially the same requests to fall into the same object.

URL normalization: case, double slashes, trailing slash, order of query parameters.
Ignore "noise": 'utm _', 'fbclid', 'gclid', arbitrary ref tags.
Limited Vary: only really significant titles ('Accept-Encoding', sometimes' Accept ',' Accept-Language'for locale).
Device-class: if necessary, use 2-3 classes (mobile/desktop/tablet), not endless user-agent branches.
Auth context: do not cache private by default; use signed-URLs/cookies-bypass or separate public/private paths.

Fastly style (pseudo):

Surrogate-Key: product:123 catalog
Cache-Control: public, s-maxage=300, stale-while-revalidate=600
Vary: Accept-Encoding

TTL strategies by content type

TypeTTL CDN (`s-maxage`)Browser ('max-age')In addition
Versioned assets ('/app. a1b2. js`)1 year1 year`immutable`; disability not needed
Catalogs/landing pages1-10 min30-120 s`stale-while-revalidate=10–30 мин`
Images (Resays)10-60 min5-15 minVary по `Accept` (webp/avif)
GET APIs (cached)10-120 s0-30 sIdempotent only; 'stale-if-error' 5-60 min
500/timeout errors00Negative-cache 30-120 sec (at the CDN level), do not cache 401/403/POST

Disability policies

By URL/Prefix: "sweep everything under '/static/2025-11-05/'."

By Tag/Key: "remove all 'catalog' and 'product: 123'."

Soft purge: mark as obsolete, do not erase the object - faster refilling.

Event-driven: CI/CD or admin event invokes webhook "invalidate tags."

Recommendation: combine both tactics: versioning paths for assets + tag-purge for content/pages.

Tiered-cache, origin-shield и prewarm

Tiered-cache: CDN regional layers → fewer origin requests.
Origin-shield: one "shield" POP to origin - improves locality and hit-ratio.
Prewarm (pre-fetch): Warm up hot URLs/caches before event/release.
Negative-cache: cache 5xx/Timeout for a short time (30-120 s) so as not to overwhelm origin with a storm of retras.

API Cache: When You Can

Only GET/HEAD and idempotent.
Key: path + essential queries (for example, '? category =... & page =...').
Validation: 'ETag '/' Last-Modified' and short 's-maxage'.
Filters by user: bring personalization to the client/edge function or use signed-requests + "public" response.

Example (API, 30 s + SWR):

Cache-Control: public, s-maxage=30, max-age=5, stale-while-revalidate=120, stale-if-error=600
ETag: "feed-v42"

Cache poisoning protection

Hard URL/header normalization; whitelist of parameters in the key.
Clipping suspicious headers/duplicates ('X-Forwarded-', extended 'Accept').
Limit'Vary 'and control the size/number of headers.
Domain separation: private/admin - on a separate name without cache.
Validation of responses: do not cache 4xx (except 404 for static), do not cache "user" pages without an explicit policy.

Compression and Formats

Brotli for text (js/css/json), gzip - fallback; pre-compressed assets are acceptable.
Images: webp/avif where support; use'Vary: Accept '+ derivatives.
Range-requests for video/audio: CDN caches chunks.
Content-Negotiation: Keep the key cardinality low (device-class instead of raw UAs).

Observability and SLO

Key Metrics

Hit-ratio (by bytes/requests) на edge/tier/shield.
p50/95/99 TTFB by region and type (static/API).
Fill-rate/Origin egress - how much goes to origin.
304 rate and average response size.
Error budget: share of 'stale-if-error '/' SWR' issues; purge frequency.

SLO examples

'p95 TTFB'statics regionally ≤ 120-150 ms, API GET cached ≤ 200-250 ms.
Edge hit-ratio statics ≥ 90%, semi-speakers ≥ 60%.
The percentage of responses from the stale branch with errors ≤ 0. 5% in 30 days.

Config cheat sheets

Nginx (reverse-proxy before CDN or in self-PoP)

nginx proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CDN:512m max_size=100g inactive=7d;

map $args $clean_args {
"~(^    &)(utm_    gclid    fbclid) """; # default $ args simplified example;
}

server {
listen 443 ssl http2;
set $cache_key "$scheme$request_method$host$uri?$clean_args    $http_accept    $http_accept_encoding";
location /static/ {
proxy_cache CDN;
proxy_cache_key $cache_key;
proxy_ignore_headers Set-Cookie;
add_header Cache-Control "public, s-maxage=86400, max-age=3600, stale-while-revalidate=600" always;
proxy_pass https://origin_static;
}

location /api/public/ {
proxy_cache CDN;
proxy_cache_key $cache_key;
proxy_cache_valid 200 30s;
add_header Cache-Control "public, s-maxage=30, max-age=5, stale-while-revalidate=120, stale-if-error=600" always;
proxy_set_header If-None-Match $upstream_http_etag;
proxy_pass https://origin_api;
}
}

Envoy (SWR + negative-cache, concept)

yaml http_filters:
- name: envoy. filters. http. cache typed_config:
"@type": type. googleapis. com/envoy. extensions. filters. http. cache. v3. CacheConfig typed_config:
"@type": type. googleapis. com/envoy. extensions. cache. simple_http_cache. v3. SimpleHttpCacheConfig
Cache-Control/Surrogate-Control Header Cache Policies
We cache 5xx errors briefly via route/retry policy + local_rate_limit

Headers for "fast" assets


Cache-Control: public, max-age=31536000, immutable
ETag: "hash"
Content-Encoding: br

Headers for semi-speakers (catalogs)


Cache-Control: public, s-maxage=600, max-age=120, stale-while-revalidate=1800, stale-if-error=86400
Vary: Accept-Encoding, Accept

FinOps: How cash saves money

Egress origin ↓, less CPU/DB load → lower infrastructure costs.
Fewer requests to paid backends (search/index/images).
Target metric: $/decrease in p95 and $/decrease in egress by 1 GB - track the post-launch effect.

iGaming/fintech specific

Provider catalogs/assets: versioned paths + annual TTL.
Event/tournament landings: 1-5 min 's-maxage' + 'SWR' for 10-30 min; tag-purge on upgrade.
Liv pages (coefficients/tables): partial cache of JSON blocks, short TTL (5-30 s), for personal blocks - client render.
PSP/payment endpoints: do not cache, strict 'no-store'; Cache only reference books (BIN tables, statuses).
Antibot: static/GET caching, gray routes for suspicious ASNs; keep 'Vary' out of noisy headlines.

Implementation checklist

  • Cache key described: URL normalization, list of allowed queries, 'Vary' only for the desired one.
  • Public/private paths separated; private - 'no-store' and bypass CDN.
  • TTL ladders by content type introduced; configured'SWR/if-error '.
  • tiered-cache + origin-shield configured; negative-cache 5xx (short) enabled.
  • There is tag/URL purge, soft purge; integration with CI/CD.
  • Includes compression (br/gzip), web image formats, and range responses.
  • Metrics: hit-ratio by layer, p95 TTFB, 304 rate, origin egress; alerts to failures.
  • Playbooks: cache warm-up before peaks, emergency purge, origin degradation.

Common errors

Non-versio assets with a large TTL → "sticky" bundles from users.
Excessive 'Vary' (by 'User-Agent', all headers) → an explosion of cardinality and a low hit-ratio.
Caching 4xx/401/403/private content.
Lack of negative-cache → an avalanche of requests for degraded origin.
No tag-purge → massive point purge and storm re-fill.
The cache key includes "noisy" UTM/ref parameters.
Too short TTL for statics → extra load on CDN and origin.

Mini playbooks

1) Warm up the cache before the event

1. Collecting top-N URLs by logs → 2) Parallel prefetch (rate-limited) by region → 3) Check hit-ratio ↑ and p95 ↓.

2) Emergency soft-purge catologists

1. Send 'PURGE '/tag-clear → 2) CDN gives stale and pulls up fresh with the background → 3) Check for no spikes on origin.

3) Origin failure

1. 'stale-if-error' helps X hours → 2) Enable the banner "technical work" on the edge → 3) Upon recovery - the target warm-up.

Result

Strong CDN strategy = correct cache key + meaningful TTL with SWR/if-error + managed disability + tiered/shield + observability. Fix the policy in the headers and IaC, measure the hit-ratio and p95, plan to warm up to peaks - and users will always receive a quick answer, and origin will remain alive even in the hottest hour.

Contact

Get in Touch

Reach out with any questions or support needs.We are always ready to help!

Telegram
@Gamble_GC
Start Integration

Email is required. Telegram or WhatsApp — optional.

Your Name optional
Email optional
Subject optional
Message optional
Telegram optional
@
If you include Telegram — we will reply there as well, in addition to Email.
WhatsApp optional
Format: +country code and number (e.g., +380XXXXXXXXX).

By clicking this button, you agree to data processing.