Back to Blog

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.

Viprasol Tech Team
June 7, 2027
12 min read

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

PurposeCache PolicyOrigin Request Policy
What they controlCache key + TTLWhat CloudFront sends to origin
HeadersIncluded in cache keyForwarded but not cached per
CookiesCached per unique valueForwarded to origin
Query stringsCached per unique valueForwarded to origin
Golden ruleEverything in cache key should matter for the responseForward 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 TypeExpected Hit RatioIf Lower
Static assets (/_next/static/)95–99%Check cache policy has no unnecessary headers
Images85–95%Check Vary headers; normalize query strings
HTML pages30–70%Expected — personalized content reduces hits
API responses0–30%Expected for auth'd APIs

See Also


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(): CreateInvalidationCommand with CallerReference timestamp, path normalization
  • Cache-Control patterns: s-maxage=3600 stale-while-revalidate from 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.

Share this article:

About the Author

V

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.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need DevOps & Cloud Expertise?

Scale your infrastructure with confidence. AWS, GCP, Azure certified team.

Free consultation • No commitment • Response within 24 hours

Viprasol · Big Data & Analytics

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.