Loading content...
You understand what origin shield is, how it reduces load, where to place it, and the trade-offs involved. Now it's time to translate that knowledge into production configuration.
Origin shield effectiveness depends heavily on configuration details—TTL alignment between cache layers, coalescing window settings, cache key design, and monitoring. Poor configuration can negate the benefits of shield entirely, while excellent configuration can maximize the protection and efficiency gains.
This page provides practical guidance drawn from production deployments across e-commerce, media, SaaS, and API platforms. You'll learn the configuration patterns that work, the mistakes to avoid, and how to monitor and tune your shield setup over time.
This page covers practical configuration strategies: TTL coordination between layers, coalescing settings, cache key design, header configuration, monitoring setup, and rollout best practices. You'll leave with a playbook for production-ready origin shield deployment.
TTL (Time To Live) configuration in a two-tier cache system requires careful coordination. Misaligned TTLs between edge and shield can cause unexpected behavior—either serving stale content too long or hitting origin more than necessary.
The TTL Hierarchy:
With origin shield, you have three TTL considerations:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960
/** * TTL Configuration Matrix * * Guidelines for coordinating TTLs across cache tiers. */ interface TTLConfiguration { contentType: string; description: string; originCacheControl: string; edgeTTL: string; shieldTTL: string; rationale: string;} const ttlConfigurations: TTLConfiguration[] = [ { contentType: "Static Assets (JS, CSS, fonts)", description: "Immutable versioned files", originCacheControl: "public, max-age=31536000, immutable", edgeTTL: "1 year", shieldTTL: "1 year", rationale: "Versioned via filename hash; cache forever until version changes" }, { contentType: "Images (product photos)", description: "Rarely changing visual assets", originCacheControl: "public, max-age=86400, stale-while-revalidate=3600", edgeTTL: "24 hours", shieldTTL: "48 hours", rationale: "Shield TTL longer to serve expired edges; SWR for freshness" }, { contentType: "API Responses (public data)", description: "Shared data endpoints", originCacheControl: "public, max-age=60, s-maxage=300", edgeTTL: "5 minutes (s-maxage)", shieldTTL: "10 minutes", rationale: "Edges refresh more often; shield provides coalescing buffer" }, { contentType: "HTML Pages (product pages)", description: "Dynamic but cacheable pages", originCacheControl: "public, max-age=300, stale-while-revalidate=600", edgeTTL: "5 minutes", shieldTTL: "15 minutes", rationale: "Shorter edge TTL for freshness; longer shield for protection" }, { contentType: "Real-Time Data", description: "Frequently updating content", originCacheControl: "public, max-age=10", edgeTTL: "10 seconds", shieldTTL: "30 seconds", rationale: "Shield absorbs rapid edge expiration while staying reasonably fresh" },]; // Golden Rule: shieldTTL >= edgeTTL// This ensures shield can serve expired edges without hitting originThe Golden TTL Rule:
Shield TTL should generally be ≥ Edge TTL
When edge TTL expires, the edge requests content from shield. If shield TTL is longer, shield can serve the edge without hitting origin. This is where coalescing and protection come from.
Configuration Pattern:
Origin sends: Cache-Control: public, max-age=300, s-maxage=900
Edge interprets: Cache for 900 seconds (s-maxage overrides max-age)
Shield config: Cache for 1800 seconds (2x edge, or via CDN override)
Using stale-while-revalidate:
The stale-while-revalidate directive is powerful with origin shield:
Cache-Control: public, max-age=300, stale-while-revalidate=600
The s-maxage directive controls shared cache (CDN) TTL separately from browser cache (max-age). This lets you cache longer at CDN while sending shorter max-age to browsers, giving you more control over content freshness at each layer.
Request coalescing (also called request collapsing) is the mechanism that bundles multiple simultaneous requests into a single origin fetch. Configuring it correctly is essential for origin protection.
Coalescing Parameters:
Most CDNs expose these coalescing controls:
| Setting | Conservative | Aggressive | Recommendation |
|---|---|---|---|
| Enable coalescing | Yes | Yes | Always enable for cacheable content |
| Coalescing window | 100ms | 1000ms | 200-500ms balances latency and efficiency |
| Max coalesced requests | 100 | Unlimited | 1000 is reasonable; unlimited for high-traffic |
| Coalesce on cache miss only | Yes | No | Yes - don't coalesce cache hits |
Understanding the Coalescing Window:
The coalescing window determines how long the shield waits for additional requests before fetching from origin:
Scenario: 100 edge servers request /image.png within 50ms window
With 100ms coalescing window:
1. First request arrives at t=0, initiates origin fetch
2. Requests at t=10, t=20, t=30... are queued (coalesced)
3. Origin response arrives at t=200
4. All 100 requests served from single origin fetch
With 10ms coalescing window:
1. First request at t=0 initiates fetch
2. Requests at t=10 may initiate SECOND fetch (window expired)
3. Multiple origin requests for same content
Longer window = better coalescing but higher latency for first request in batch
Shorter window = faster first response but less effective coalescing
12345678910111213141516171819202122232425262728293031323334353637383940414243
# Fastly VCL: Request Coalescing Configuration# This is configured in Fastly's VCL (Varnish Configuration Language) sub vcl_backend_fetch { # Enable request coalescing (enabled by default on Fastly) # Fastly calls this "request collapsing" # For specific paths, you might want to disable coalescing if (req.url ~ "^/api/user-specific/") { # Don't coalesce user-specific API calls set bereq.http.Fastly-No-Shield = "1"; } # For API endpoints, coalesce but with shorter timeouts if (req.url ~ "^/api/") { # API calls: don't wait too long set bereq.first_byte_timeout = 10s; } # For large assets, allow longer coalescing if (req.url ~ "\.(iso|zip|tar|dmg)$") { # Large downloads benefit from aggressive coalescing set bereq.first_byte_timeout = 60s; }} sub vcl_recv { # Ensure consistent cache keys for coalescing to work # Strip query parameters that don't affect content set req.url = querystring.filter_except(req.url, "v" + "version" + "id" + "page" + "size"); # Normalize Accept-Encoding for better coalescing if (req.http.Accept-Encoding) { if (req.http.Accept-Encoding ~ "br") { set req.http.Accept-Encoding = "br"; } elsif (req.http.Accept-Encoding ~ "gzip") { set req.http.Accept-Encoding = "gzip"; } else { unset req.http.Accept-Encoding; } }}Coalescing is only safe for idempotent, cacheable requests (typically GET). Never coalesce POST, PUT, or DELETE requests, and ensure coalescing is disabled for user-specific or authenticated content that bypasses cache.
The cache key determines which requests are considered "the same" for caching purposes. With origin shield, cache key design is crucial—it affects both edge and shield cache efficiency.
Cache Key Components:
Typical cache key includes:
The Consistency Requirement:
For origin shield to work effectively, cache keys must be consistent between edge and shield. If an edge computes a different cache key than the shield, coalescing fails and cache fragmentation occurs.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
/** * Cache Key Design Patterns * * Best practices for cache key construction with origin shield. */ interface CacheKeyConfig { includeHost: boolean; includeQueryParams: 'all' | 'none' | 'allowlist' | 'denylist'; queryParamList?: string[]; normalizeCase: boolean; stripTrailingSlash: boolean; normalizeEncoding: boolean; varyHeaders: string[];} // GOOD: Consistent, minimal cache keyconst productPageCacheKey: CacheKeyConfig = { includeHost: true, // Different hosts = different sites includeQueryParams: 'allowlist', // Only include params that affect content queryParamList: ['id', 'color', 'size'], // Product variants normalizeCase: true, // /Product/123 === /product/123 stripTrailingSlash: true, // /products/ === /products normalizeEncoding: true, // %20 === space varyHeaders: ['Accept-Encoding'], // Gzip vs Brotli}; // BAD: Over-broad cache key causing fragmentationconst badCacheKey: CacheKeyConfig = { includeHost: true, includeQueryParams: 'all', // BAD: Includes tracking params (utm_source, etc.) normalizeCase: false, // BAD: Case variations create duplicates stripTrailingSlash: false, // BAD: /path and /path/ are different entries normalizeEncoding: false, // BAD: Encoding variations create duplicates varyHeaders: ['Accept-Encoding', 'Accept-Language', 'User-Agent'], // BAD: Too many variations}; // Cache key function for edge/shield consistencyfunction computeCacheKey(request: Request, config: CacheKeyConfig): string { let key = ''; // Host if (config.includeHost) { key += request.headers.get('Host') || ''; } // Path normalization let path = new URL(request.url).pathname; if (config.normalizeCase) { path = path.toLowerCase(); } if (config.stripTrailingSlash && path.endsWith('/') && path !== '/') { path = path.slice(0, -1); } key += path; // Query parameters const url = new URL(request.url); if (config.includeQueryParams === 'allowlist' && config.queryParamList) { const filteredParams = new URLSearchParams(); for (const param of config.queryParamList) { if (url.searchParams.has(param)) { filteredParams.set(param, url.searchParams.get(param)!); } } // Sort for consistency filteredParams.sort(); if (filteredParams.toString()) { key += '?' + filteredParams.toString(); } } // Vary headers for (const header of config.varyHeaders) { key += '|' + header + ':' + (request.headers.get(header) || ''); } return key;} // The same function MUST be used at both edge and shield!Common Cache Key Mistakes:
Most CDNs provide cache analytics showing key distribution. Look for suspiciously high numbers of unique cache keys—this indicates key over-specification. A site with 10,000 products shouldn't have 10 million cache entries.
HTTP headers control caching behavior at every layer. Proper header configuration ensures edge, shield, and browsers all behave as intended.
Essential Caching Headers:
| Header | Purpose | Shield Consideration |
|---|---|---|
| Cache-Control | Primary caching directive | Use s-maxage for CDN-specific TTL |
| Vary | Cache key variation | Minimize Vary headers; each adds fragmentation |
| ETag | Conditional revalidation | Shield uses for efficient origin revalidation |
| Last-Modified | Time-based revalidation | Fallback for ETag-based revalidation |
| Surrogate-Control | CDN-specific directives | Allows CDN config without affecting browsers |
| Surrogate-Key | Tag-based invalidation | Essential for efficient purging at scale |
12345678910111213141516171819202122232425262728293031323334353637
# Example: Static Asset (immutable, versioned)HTTP/1.1 200 OKCache-Control: public, max-age=31536000, immutableSurrogate-Control: max-age=31536000Surrogate-Key: static-assets v2-assetsETag: "abc123xyz"Content-Type: application/javascript # Example: Product Page (cacheable but needs freshness)HTTP/1.1 200 OKCache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600Surrogate-Control: max-age=900Surrogate-Key: product-pages product-12345 category-electronicsVary: Accept-EncodingETag: "prod12345-v789"Content-Type: text/html # Example: API Response (shared data)HTTP/1.1 200 OKCache-Control: public, max-age=10, s-maxage=60Surrogate-Control: max-age=120Surrogate-Key: api-catalog catalog-v3Vary: Accept-EncodingContent-Type: application/json # Example: Personalized Content (not cacheable)HTTP/1.1 200 OKCache-Control: private, no-storeVary: CookieContent-Type: text/html # Example: Edge computation allowedHTTP/1.1 200 OKCache-Control: public, max-age=300, stale-if-error=86400Surrogate-Control: max-age=600, stale-if-error=86400CDN-Cache-Control: max-age=1800Content-Type: text/htmlThe Surrogate-Key Pattern:
Surrogate keys (also called cache tags) enable efficient invalidation at scale:
# Product page includes multiple tags
Surrogate-Key: product-123 category-shoes brand-nike homepage-featured
# Invalidate by category (purges all shoes)
Purge: category-shoes
# Invalidate specific product (purges just this page)
Purge: product-123
# Invalidate homepage features (purges featured products)
Purge: homepage-featured
With origin shield, surrogate key invalidation must propagate to:
Different CDNs use different headers for CDN-specific control. Fastly uses Surrogate-Control and Surrogate-Key. CloudFront uses x-cache headers. Cloudflare uses Cache-Tag. Consult your CDN's documentation for the correct header names.
You can't optimize what you can't measure. Comprehensive monitoring is essential for effective origin shield operation.
Essential Metrics Dashboard:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
-- Example queries for CDN log analytics-- Adapt to your log format and analytics platform -- 1. Cache Hit Rates by LayerSELECT DATE_TRUNC('hour', timestamp) as hour, ROUND(100.0 * SUM(CASE WHEN cache_status = 'HIT' THEN 1 ELSE 0 END) / COUNT(*), 2) as edge_hit_rate, ROUND(100.0 * SUM(CASE WHEN shield_status = 'HIT' THEN 1 ELSE 0 END) / SUM(CASE WHEN cache_status = 'MISS' THEN 1 ELSE 0 END), 2) as shield_hit_rateFROM cdn_logsWHERE timestamp > NOW() - INTERVAL '24 hours'GROUP BY 1ORDER BY 1; -- 2. Origin Load Over TimeSELECT DATE_TRUNC('minute', timestamp) as minute, COUNT(*) as origin_requests, SUM(response_bytes) / 1e9 as origin_gbFROM cdn_logsWHERE cache_status = 'MISS' AND shield_status = 'MISS' AND timestamp > NOW() - INTERVAL '1 hour'GROUP BY 1ORDER BY 1; -- 3. Top Cache Miss URLs (Optimization Targets)SELECT url_path, COUNT(*) as miss_count, SUM(response_bytes) / 1e6 as total_mb, AVG(origin_response_time_ms) as avg_origin_msFROM cdn_logsWHERE cache_status = 'MISS' AND shield_status = 'MISS' AND timestamp > NOW() - INTERVAL '1 hour'GROUP BY 1ORDER BY miss_count DESCLIMIT 50; -- 4. Coalescing EffectivenessSELECT DATE_TRUNC('minute', timestamp) as minute, SUM(coalesced_requests) as total_coalesced, COUNT(*) as origin_fetches, ROUND(SUM(coalesced_requests)::numeric / NULLIF(COUNT(*), 0), 1) as coalesce_ratioFROM shield_origin_logsWHERE timestamp > NOW() - INTERVAL '1 hour'GROUP BY 1ORDER BY 1; -- 5. Latency PercentilesSELECT PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY edge_to_shield_ms) as p50_edge_shield, PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY edge_to_shield_ms) as p95_edge_shield, PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY edge_to_shield_ms) as p99_edge_shield, PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY shield_to_origin_ms) as p50_shield_origin, PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY shield_to_origin_ms) as p95_shield_originFROM cdn_logsWHERE cache_status = 'MISS' AND timestamp > NOW() - INTERVAL '1 hour';Alerting Strategy:
Set up alerts for abnormal shield behavior:
| Condition | Threshold | Urgency | Action |
|---|---|---|---|
| Shield hit rate drops | <70% for 10 min | High | Investigate cache configuration |
| Origin requests spike | 5x baseline | High | Check for invalidation storm or shield bypass |
| Shield→origin latency rises | >500ms P95 | Medium | Check origin health, network issues |
| Error rate increases | >1% 5xx | High | Check shield and origin health |
| Coalescing ratio drops | <10 during peak | Medium | Check coalescing configuration |
Configure your CDN to add diagnostic headers like X-Cache (HIT/MISS), X-Cache-Layer (edge/shield), and X-Coalesced (count). These make debugging much easier and can be stripped before reaching end users.
Rolling out origin shield to production requires careful planning. A poor rollout can cause outages; a good rollout demonstrates value immediately.
Pre-Rollout Checklist:
Phased Rollout Strategy:
Phase 1: Single Non-Critical Path (Day 1-3)
- Enable shield for static assets (/static/*, /images/*)
- Monitor hit rates and latency
- Validate coalescing works
Phase 2: Expand Coverage (Day 4-7)
- Add product pages, category pages
- Monitor origin load reduction
- Fine-tune TTLs based on observations
Phase 3: API Endpoints (Day 8-14)
- Enable shield for cacheable API endpoints
- Monitor API latency closely
- Adjust coalescing windows if needed
Phase 4: Full Traffic (Day 15+)
- Route all cacheable traffic through shield
- Optimize based on top cache miss analysis
- Document final configuration
| Risk | Mitigation | Rollback Trigger |
|---|---|---|
| Increased latency | Monitor P95 closely; start with non-critical paths | 100ms P95 increase |
| Origin overload | Monitor origin requests; shield should REDUCE load | Origin requests increase |
| Cache consistency issues | Test invalidation works at all layers | Stale content served post-purge |
| Configuration errors | Test in staging; peer review configs | Any 5xx errors from shield |
| Cost overruns | Monitor CDN billing; set budget alerts | Costs exceed budget by 20% |
Enable origin shield early in the week when your team is fully staffed and alert. Issues may take time to surface, and you want engineers available to respond quickly.
Here are complete configuration examples for common CDN providers. These represent production-ready configurations adaptable to your needs.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
# AWS CloudFront with Origin Shield - Terraform Configuration resource "aws_cloudfront_distribution" "main" { enabled = true is_ipv6_enabled = true comment = "Production CDN with Origin Shield" default_root_object = "index.html" price_class = "PriceClass_All" origin { domain_name = aws_s3_bucket.content.bucket_regional_domain_name origin_id = "S3Origin" s3_origin_config { origin_access_identity = aws_cloudfront_origin_access_identity.main.cloudfront_access_identity_path } # ORIGIN SHIELD CONFIGURATION origin_shield { enabled = true origin_shield_region = "us-east-1" # Same region as origin } } # Default cache behavior for static assets default_cache_behavior { allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] target_origin_id = "S3Origin" forwarded_values { query_string = false cookies { forward = "none" } } viewer_protocol_policy = "redirect-to-https" min_ttl = 0 default_ttl = 86400 # 1 day max_ttl = 31536000 # 1 year compress = true } # Cache behavior for API endpoints ordered_cache_behavior { path_pattern = "/api/*" allowed_methods = ["GET", "HEAD", "OPTIONS"] cached_methods = ["GET", "HEAD"] target_origin_id = "S3Origin" cache_policy_id = aws_cloudfront_cache_policy.api.id viewer_protocol_policy = "https-only" compress = true }} resource "aws_cloudfront_cache_policy" "api" { name = "api-cache-policy" comment = "Cache policy for API endpoints" default_ttl = 60 max_ttl = 300 min_ttl = 0 parameters_in_cache_key_and_forwarded_to_origin { cookies_config { cookie_behavior = "none" } headers_config { header_behavior = "whitelist" headers { items = ["Accept-Encoding"] } } query_strings_config { query_string_behavior = "whitelist" query_strings { items = ["id", "page", "limit"] } } }}We've covered practical configuration strategies for production origin shield deployments. Let's consolidate the key takeaways:
Module Complete:
You've now completed the comprehensive exploration of origin shield:
You're now equipped to design, implement, and optimize origin shield configurations for production systems.
Congratulations! You've mastered origin shield concepts and configuration. You understand how this intermediate caching layer protects origin infrastructure, the placement trade-offs, and practical configuration strategies. Apply these concepts to protect your origin servers, reduce bandwidth costs, and improve reliability during traffic spikes.