AWS CloudFront Cache Policies: TTLs, Vary Headers, Origin Request Policies, and Invalidation
Configure AWS CloudFront cache policies for maximum cache efficiency. Covers cache policy vs origin request policy separation, TTL configuration by content type, Vary header handling, cache key normalization, programmatic invalidation, and Terraform setup.
CloudFront cache policies define what makes two requests different (the cache key) and how long to keep responses (TTL). Origin request policies define what gets forwarded to your origin — and crucially, these two are separate. Mixing them up is the most common CloudFront misconfiguration: forwarding headers to your origin that you aren't including in your cache key means CloudFront might serve response A for a request that should get response B.
Cache Policy vs Origin Request Policy
| Purpose | Cache Policy | Origin Request Policy |
|---|---|---|
| What they control | Cache key + TTL | What CloudFront sends to origin |
| Headers | Included in cache key | Forwarded but not cached per |
| Cookies | Cached per unique value | Forwarded to origin |
| Query strings | Cached per unique value | Forwarded to origin |
| Golden rule | Everything in cache key should matter for the response | Forward only what origin needs |
Terraform: Cache and Origin Request Policies
# terraform/cloudfront-policies.tf
# Policy 1: Static assets (JS, CSS, images) — long TTL, immutable
resource "aws_cloudfront_cache_policy" "static_assets" {
name = "${var.app_name}-static-assets"
comment = "Long-lived cache for hashed static assets"
default_ttl = 86400 # 1 day default
max_ttl = 31536000 # 1 year maximum
min_ttl = 0
parameters_in_cache_key_and_forwarded_to_origin {
# Compress: gzip/brotli based on Accept-Encoding
enable_accept_encoding_gzip = true
enable_accept_encoding_brotli = true
cookies_config { cookie_behavior = "none" }
headers_config { header_behavior = "none" } # No headers in cache key
query_strings_config { query_string_behavior = "none" }
}
}
# Policy 2: HTML pages — short TTL, vary by Accept-Encoding only
resource "aws_cloudfront_cache_policy" "html_pages" {
name = "${var.app_name}-html-pages"
comment = "Short cache for HTML, allow stale-while-revalidate"
default_ttl = 60 # 1 minute default (honor Cache-Control from origin)
max_ttl = 86400 # 1 day max
min_ttl = 0
parameters_in_cache_key_and_forwarded_to_origin {
enable_accept_encoding_gzip = true
enable_accept_encoding_brotli = true
cookies_config { cookie_behavior = "none" }
headers_config { header_behavior = "none" }
query_strings_config { query_string_behavior = "none" }
}
}
# Policy 3: API responses — no cache (or very short)
resource "aws_cloudfront_cache_policy" "api" {
name = "${var.app_name}-api"
comment = "No caching for API endpoints"
default_ttl = 0
max_ttl = 0
min_ttl = 0
parameters_in_cache_key_and_forwarded_to_origin {
enable_accept_encoding_gzip = true
enable_accept_encoding_brotli = true
cookies_config { cookie_behavior = "none" }
headers_config { header_behavior = "none" }
# Cache per query string for API (e.g., /api/products?category=X)
query_strings_config {
query_string_behavior = "all"
}
}
}
# Origin Request Policy: what to forward to origin
resource "aws_cloudfront_origin_request_policy" "app" {
name = "${var.app_name}-origin-request"
comment = "Forward necessary headers to origin, not cookies"
cookies_config {
cookie_behavior = "whitelist"
cookies {
items = ["session"] # Forward session cookie for SSR auth
}
}
headers_config {
header_behavior = "whitelist"
headers {
items = [
"CloudFront-Viewer-Country", # Geo data
"X-Forwarded-For",
"Accept-Language", # For i18n
"Host",
]
}
}
query_strings_config {
query_string_behavior = "all" # Forward all query strings to origin
}
}
# Apply policies to CloudFront behaviors
resource "aws_cloudfront_distribution" "main" {
# ... existing distribution config ...
# Default behavior: HTML pages
default_cache_behavior {
target_origin_id = "alb-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = aws_cloudfront_cache_policy.html_pages.id
origin_request_policy_id = aws_cloudfront_origin_request_policy.app.id
compress = true
}
# Static assets: long TTL (Next.js hashes filenames)
ordered_cache_behavior {
path_pattern = "/_next/static/*"
target_origin_id = "alb-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = aws_cloudfront_cache_policy.static_assets.id
compress = true
}
# API: no cache
ordered_cache_behavior {
path_pattern = "/api/*"
target_origin_id = "alb-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = aws_cloudfront_cache_policy.api.id
origin_request_policy_id = aws_cloudfront_origin_request_policy.app.id
compress = true
}
# Images: long TTL
ordered_cache_behavior {
path_pattern = "/images/*"
target_origin_id = "alb-origin"
viewer_protocol_policy = "redirect-to-https"
allowed_methods = ["GET", "HEAD"]
cached_methods = ["GET", "HEAD"]
cache_policy_id = aws_cloudfront_cache_policy.static_assets.id
compress = true
}
enabled = true
is_ipv6_enabled = true
# ...
}
☁️ Is Your Cloud Costing Too Much?
Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.
- AWS, GCP, Azure certified engineers
- Infrastructure as Code (Terraform, CDK)
- Docker, Kubernetes, GitHub Actions CI/CD
- Typical audit recovers $500–$3,000/month in savings
Programmatic Cache Invalidation
// lib/cdn/invalidate.ts
import {
CloudFrontClient,
CreateInvalidationCommand,
} from "@aws-sdk/client-cloudfront";
const cf = new CloudFrontClient({ region: "us-east-1" }); // CloudFront is global but API is us-east-1
export async function invalidatePaths(paths: string[]): Promise<string> {
// Normalize paths: must start with /
const normalized = paths.map((p) => (p.startsWith("/") ? p : `/${p}`));
const result = await cf.send(
new CreateInvalidationCommand({
DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID!,
InvalidationBatch: {
CallerReference: `inv-${Date.now()}`, // Unique per invocation
Paths: {
Quantity: normalized.length,
Items: normalized,
},
},
})
);
return result.Invalidation?.Id ?? "";
}
// Usage patterns:
// Single page: invalidatePaths(["/blog/my-post"])
// Wildcard: invalidatePaths(["/blog/*"]) ← counts as 1 invalidation
// Everything: invalidatePaths(["/*"]) ← counts as 1 invalidation
// Max 3,000 paths/month free; $0.005 per path after
// Trigger from Server Action after content publish:
export async function publishPost(postId: string) {
await db.post.update({ where: { id: postId }, data: { publishedAt: new Date() } });
// Invalidate the specific post page and the blog index
await invalidatePaths([`/blog/${postId}`, "/blog", "/"]);
}
Cache Headers from Next.js Origin
// Next.js tells CloudFront how long to cache via Cache-Control headers
// app/blog/[slug]/page.tsx
// Static pages: CloudFront caches based on s-maxage
export const revalidate = 3600; // Sets Cache-Control: s-maxage=3600, stale-while-revalidate
// For route handlers: set headers explicitly
// app/api/products/route.ts
export async function GET() {
const products = await getProducts();
return Response.json(products, {
headers: {
// s-maxage: CloudFront cache duration
// stale-while-revalidate: serve stale while fetching fresh
"Cache-Control": "public, s-maxage=300, stale-while-revalidate=600",
// Vary tells CloudFront to cache separate responses per Accept-Encoding
"Vary": "Accept-Encoding",
},
});
}
// For pages that must NOT be cached (authenticated):
export const dynamic = "force-dynamic";
// This sets Cache-Control: private, no-cache — CloudFront won't cache it
⚙️ DevOps Done Right — Zero Downtime, Full Automation
Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.
- Staging + production environments with feature flags
- Automated security scanning in the pipeline
- Uptime monitoring + alerting + runbook automation
- On-call support handover docs included
Monitoring Cache Hit Ratio
# CloudWatch dashboard for cache performance
resource "aws_cloudwatch_dashboard" "cloudfront" {
dashboard_name = "${var.app_name}-cloudfront"
dashboard_body = jsonencode({
widgets = [
{
type = "metric"
properties = {
metrics = [
["AWS/CloudFront", "CacheHitRate", "DistributionId", var.cloudfront_id, "Region", "Global"],
["AWS/CloudFront", "OriginLatency", "DistributionId", var.cloudfront_id, "Region", "Global"],
["AWS/CloudFront", "4xxErrorRate", "DistributionId", var.cloudfront_id, "Region", "Global"],
["AWS/CloudFront", "5xxErrorRate", "DistributionId", var.cloudfront_id, "Region", "Global"],
]
period = 300
title = "CloudFront Performance"
}
}
]
})
}
Cache Hit Ratio Benchmarks
| Content Type | Expected Hit Ratio | If Lower |
|---|---|---|
Static assets (/_next/static/) | 95–99% | Check cache policy has no unnecessary headers |
| Images | 85–95% | Check Vary headers; normalize query strings |
| HTML pages | 30–70% | Expected — personalized content reduces hits |
| API responses | 0–30% | Expected for auth'd APIs |
See Also
- AWS Lambda@Edge vs CloudFront Functions
- AWS CloudFront Lambda@Edge Patterns
- Next.js Cache Revalidation
- Next.js Performance Optimization
- Terraform State Management
Working With Viprasol
CloudFront misconfiguration silently destroys cache efficiency — a single unnecessary Cookie in the cache key can drop your hit ratio from 95% to 5% because every unique cookie value creates a separate cache entry. Our team audits your CloudFront setup, separates cache key concerns from origin request forwarding, sets aggressive TTLs for hashed static assets, and implements programmatic invalidation for content updates.
What we deliver:
- Three cache policies: static_assets (1yr max), html_pages (1min default), api (0 TTL)
- Origin request policy: session cookie whitelist, CloudFront-Viewer-Country, Accept-Language forwarding
- Ordered cache behaviors:
/_next/static/*→ static policy,/api/*→ api policy,/images/*→ static policy invalidatePaths():CreateInvalidationCommandwith CallerReference timestamp, path normalization- Cache-Control patterns:
s-maxage=3600 stale-while-revalidatefrom Next.js pages/route handlers - CloudWatch dashboard: CacheHitRate, OriginLatency, 4xx/5xx error rates
Talk to our team about your CDN optimization →
Or explore our cloud infrastructure services.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need DevOps & Cloud Expertise?
Scale your infrastructure with confidence. AWS, GCP, Azure certified team.
Free consultation • No commitment • Response within 24 hours
Making sense of your data at scale?
Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.