| name | setup-cdn-edge-waf |
| description | Configures CDN/edge delivery and a WAF — cache keys and Cache-Control/Surrogate-Control, stale-while-revalidate, tag/path purge wired into deploy, origin shielding with request collapsing, edge TLS + HTTP/3, and a managed OWASP ruleset with edge rate-limiting and bot/DDoS mitigation rolled out detect-then-enforce — to raise cache hit-ratio and absorb attacks before the origin. |
| when_to_use | Serving static assets or APIs through a CDN, raising cache hit-ratio, or adding edge security (WAF, DDoS, bot mitigation). Distinct from caching-strategy (app/Redis caching), configure-reverse-proxy-lb (the origin proxy itself), rate-limiting (origin app limits), and configure-dns-tls (the cert/DNS records the CDN sits on top of). |
When to Use
Reach for this skill when the work is at the edge in front of the origin — caching, TLS termination, and attack absorption at the POP, not inside your app:
- "Put the static site / assets / images behind a CDN and stop hitting origin"
- "Our cache hit-ratio is low / everything is
MISS / origin is melting under read traffic"
- "Cache this API response at the edge but purge it the instant we deploy / the record changes"
- "Add a WAF / block SQLi+XSS / OWASP ruleset / managed rules"
- "We're getting scraped / credential-stuffed / L7 DDoS'd — mitigate at the edge before it reaches us"
- "Enable HTTP/3, edge TLS, origin shielding, request collapsing"
NOT this skill:
- Cache-aside / write-through / Redis TTLs inside the application → caching-strategy
- Configuring the origin reverse proxy / load balancer (nginx/Envoy/HAProxy, upstream pools, health checks) → configure-reverse-proxy-lb
- Per-user/per-key request quotas enforced in the origin app (429 + Retry-After business logic) → rate-limiting
- Issuing the cert, DNS records, HSTS, CAA that the CDN hostname rides on → configure-dns-tls
- Fixing the actual SQLi/XSS/SSRF in code (the WAF is a shield, not a patch) → remediate-web-vulnerabilities
- Actively pen-testing the app to find injection bugs → fuzz-dynamic-security-test
Steps
-
Classify every route into a cache class first — caching policy follows the class, not the URL. Decide this before touching any config.
| Class | Example | Cache-Control | Edge TTL | Cache key includes |
|---|
| Immutable static | /_next/static/*.js, hashed assets | public, max-age=31536000, immutable | 1y | path only |
| Versioned media | /img/logo.png | public, max-age=86400, stale-while-revalidate=604800 | 1d | path only |
| HTML (anon) | /, /blog/x | public, max-age=0, s-maxage=300, stale-while-revalidate=86400 | 5m (edge), 0 (browser) | path + Vary allow-list |
| Cacheable API (GET) | /api/products | public, s-maxage=60, stale-while-revalidate=300 | 60s | path + canonical query + auth-tier |
| Private / auth | /account, POST | private, no-store | bypass | never cache |
Split max-age (browser) from s-maxage/Surrogate-Control (edge) so you can hold a long edge TTL while browsers revalidate. Origin sets Surrogate-Control: max-age=... (CDN strips it before responding); browser-facing Cache-Control carries the public value.
-
Engineer the cache key — this is where hit-ratio is won or lost. Default to: scheme + host + path + sorted-allowlisted-query.
- Strip tracking/garbage query params (
utm_*, fbclid, gclid, ref, session ids) from the key — otherwise every share link is a MISS. Allow-list the params that actually change the response; drop everything else.
- Sort query params so
?a=1&b=2 and collapse to one entry.
Common Errors
- Blanket purge on every deploy. Drops global hit-ratio to zero and stampedes origin each release. Tag with surrogate keys and purge only what changed; let hashed assets expire on their own.
- Tracking params in the cache key.
?utm_source=... makes every shared link a unique MISS. Strip the allow-list's complement before keying.
Vary: Cookie (or User-Agent). Fragments the cache to near-uniqueness — effectively no caching. Normalize to a coarse derived dimension (auth-tier, device-class) and Vary on that.
- One TTL for both browser and edge. Using
max-age alone means you can't hold a long edge TTL without baking it into browsers. Separate s-maxage/Surrogate-Control (edge) from max-age (browser).
- No
stale-while-revalidate. Every expiry becomes a synchronous origin round-trip and a latency spike; add SWR + stale-if-error.
- No origin shield / no request collapsing. A cold key fetches origin once per POP and concurrent misses each hit origin — a viral object DDoSes you. Enable both.
- Cleartext edge-to-origin. TLS terminates at the edge but the origin pull is HTTP — a MITM goldmine. Use full/strict mode with a validated origin cert.
- WAF flipped straight to block. Guaranteed false positives on launch. Run count/log mode 24–72h, tune, then enforce.
- Hard-blocking suspected bots. A false positive locks out a real user with no recovery. Serve a managed/JS challenge instead so legit clients pass automatically.
- Caching
Set-Cookie / personalized HTML. Leaks one user's session or data to the next requester. Force private, no-store on any response carrying Set-Cookie or auth-specific content, and strip Set-Cookie from cacheable responses.
- Treating the WAF as the fix. A blocked payload is still an unpatched bug; an attacker who bypasses one rule still wins. Fix the vuln in code (remediate-web-vulnerabilities) — the WAF only buys time.
Verify
- Hit-ratio rises, origin load drops. Pull the CDN analytics cache hit-ratio before/after; it should climb (static well above 95%, cacheable HTML/API materially up). Confirm origin request rate / bandwidth fell by a corresponding amount — the whole point.
- Edge actually served it.
curl -sI https://host/path shows the CDN cache header (x-cache: HIT, cf-cache-status: HIT, or x-served-by with cache-*-HIT) and the expected Cache-Control/age. A second request after a MISS must return HIT.
- Key normalization works. Request
?utm_source=x and the bare URL → both HIT the same object (same age/etag), proving tracking params are stripped and params are sorted.
- Purge actually invalidates. Cache an object (
HIT), change the entity, fire the surrogate-key/path purge, re-request → MISS then fresh content. A purge that doesn't flip HIT→MISS is broken.
- SWR serves stale instantly. After TTL expiry, the first request returns immediately (stale, background revalidate) rather than blocking on origin; latency stays flat across the expiry boundary.
- TLS + HTTP/3 negotiated.
curl --http3 -sI https://host succeeds and Alt-Svc: h3 is advertised; TLS 1.3 on edge and validated TLS on the origin pull (no cleartext hop).
- WAF blocks a test attack. Send a benign canary payload (
?q=' OR 1=1--, a reflected-XSS probe) → blocked (403/challenge) with a rule id in the WAF log. Confirm in the same window that real traffic shows ~0 false positives (block-mode log clean of legit requests).
- Edge rate-limit / bot rule fires. Burst
/login past the threshold from one IP → challenge/429 at the edge (request never reaches origin logs). A known-good client sails through.
Done = cache hit-ratio is up and measured origin load is down, HIT/purge/SWR behave exactly as configured, edge+origin TLS with HTTP/3 are verified, and the WAF blocks a canary attack with zero false positives on real traffic (managed rules in enforce mode, not count).