CloudFront Response Headers Policies: Security Headers, SEO Tags, and CORS

2 min readCloud Infrastructure

CloudFront response headers policies let you add, modify, or remove HTTP headers on every response — without changing your origin. Security headers (HSTS, CSP, X-Frame-Options), SEO directives, and CORS headers are all configurable centrally, with or without CloudFront Functions.

awscloudfrontsecuritycors

What response headers policies do

A response headers policy in CloudFront lets you add, override, or remove HTTP response headers for every response — before CloudFront returns it to the client. The origin doesn't need to change. You configure the policy once and attach it to a cache behavior.

Common uses:

  • Security headers: HSTS, Content-Security-Policy, X-Frame-Options, X-Content-Type-Options
  • SEO directives: X-Robots-Tag: noindex for staging environments
  • CORS headers: centralize CORS policy instead of configuring every origin individually
  • Custom headers: add environment indicators, cache diagnostics, or client hints

Policy vs CloudFront Functions: when to use which

ConceptAWS CloudFront

Response headers policies handle static, uniform header additions — the same headers for all responses matching a behavior. CloudFront Functions handle dynamic logic: add headers based on the request path, query parameters, or viewer country. Use policies when the headers don't depend on request context.

Prerequisites

  • HTTP response headers
  • CloudFront cache behaviors
  • Content-Security-Policy basics

Key Points

  • Response headers policies are evaluated after the origin responds — they cannot affect caching decisions.
  • The 'Override' flag controls whether the policy header wins over the origin's header of the same name.
  • AWS provides managed policies: SecurityHeadersPolicy, CORSWithDefaultHeaders, and others.
  • CloudFront Functions run at the edge before or after the cache — use for header logic that depends on the request.

Blocking search engine indexing for staging environments

The problem: Google indexes your staging environment, creating duplicate content that hurts your production site's SEO ranking.

The fix: add X-Robots-Tag: noindex, nofollow to all responses from the staging CloudFront distribution.

In Terraform:

resource "aws_cloudfront_response_headers_policy" "staging_noindex" {
  name = "staging-noindex"

  custom_headers_config {
    items {
      header   = "X-Robots-Tag"
      value    = "noindex, nofollow"
      override = true  # overwrite any X-Robots-Tag from the origin
    }
  }
}

resource "aws_cloudfront_distribution" "staging" {
  # ... origin and other config ...

  default_cache_behavior {
    target_origin_id           = "staging-origin"
    viewer_protocol_policy     = "redirect-to-https"
    response_headers_policy_id = aws_cloudfront_response_headers_policy.staging_noindex.id

    # ... other settings ...
  }
}

After updating the behavior, create a cache invalidation to clear any cached responses that don't have the header:

aws cloudfront create-invalidation \
  --distribution-id E1234567890ABC \
  --paths "/*"

Security headers policy

AWS provides a managed SecurityHeadersPolicy that adds standard security headers. You can also create custom policies:

resource "aws_cloudfront_response_headers_policy" "security" {
  name = "security-headers"

  security_headers_config {
    # Force HTTPS for 1 year; include subdomains
    strict_transport_security {
      access_control_max_age_sec = 31536000
      include_subdomains         = true
      preload                    = true
      override                   = true
    }

    # Prevent clickjacking
    frame_options {
      frame_option = "DENY"
      override     = true
    }

    # Prevent MIME type sniffing
    content_type_options {
      override = true
    }

    # Referrer policy
    referrer_policy {
      referrer_policy = "strict-origin-when-cross-origin"
      override        = true
    }

    # XSS protection (legacy, but still useful)
    xss_protection {
      mode_block  = true
      protection  = true
      override    = true
    }
  }

  # Custom CSP header (not in security_headers_config block)
  custom_headers_config {
    items {
      header   = "Content-Security-Policy"
      value    = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; img-src *"
      override = true
    }
  }
}

CORS policy

For APIs accessed from browsers, centralize CORS headers in the response headers policy rather than in your Lambda or application code:

resource "aws_cloudfront_response_headers_policy" "cors" {
  name = "api-cors"

  cors_config {
    access_control_allow_credentials = true

    access_control_allow_headers {
      items = ["Authorization", "Content-Type", "X-Api-Key"]
    }

    access_control_allow_methods {
      items = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
    }

    access_control_allow_origins {
      items = ["https://app.example.com", "https://www.example.com"]
    }

    access_control_max_age_sec = 86400  # cache preflight for 1 day
    origin_override            = true
  }
}

origin_override = true means the CloudFront policy's CORS headers overwrite any CORS headers your origin sends. Use false if you want your origin to control CORS and CloudFront to only add headers when the origin sends none.

💡Adding headers dynamically with CloudFront Functions

Response headers policies can only add static, uniform headers. If you need to add headers based on request context (different CSP for different paths, environment-specific headers based on origin response), use a CloudFront Function.

// CloudFront Function: add environment indicator based on request path
function handler(event) {
    var response = event.response;
    var request = event.request;

    // Add cache diagnostics header
    response.headers['x-cache-status'] = {
        value: response.headers['x-cache'] ? response.headers['x-cache'].value : 'UNKNOWN'
    };

    // Add different CSP based on path
    if (request.uri.startsWith('/admin')) {
        response.headers['content-security-policy'] = {
            value: "default-src 'self'; script-src 'self'"
        };
    } else {
        response.headers['content-security-policy'] = {
            value: "default-src 'self' https://cdn.example.com"
        };
    }

    return response;
}

Associate the function with the viewer-response event to run after the cache (either cache hit or origin response). This runs at the edge on every request — keep functions lightweight (under 10ms execution, under 10KB code).

You cannot use both a response headers policy and a CloudFront Function to set the same header — they conflict. Use CloudFront Functions when you need conditional logic; use response headers policies when the headers are static.

Managed response headers policies

AWS provides several pre-built policies available by name:

  • SecurityHeadersPolicy: HSTS, X-Frame-Options SAMEORIGIN, X-Content-Type-Options nosniff, Referrer-Policy strict-origin-when-cross-origin, X-XSS-Protection
  • CORSWithDefaultHeaders: standard CORS headers allowing all origins (not suitable for credentialed requests)
  • CORSWithSecurityHeadersPolicy: CORS + security headers combined
  • SimpleCORS: minimal CORS with Access-Control-Allow-Origin: *
# List available managed policies
aws cloudfront list-response-headers-policies \
  --type managed \
  --query 'ResponseHeadersPolicyList.Items[].{Name:ResponseHeadersPolicy.ResponseHeadersPolicyConfig.Name,Id:ResponseHeadersPolicy.Id}'

Use managed policies as a starting point for standard configurations and customize from there.

You add a Content-Security-Policy header via a CloudFront response headers policy with override=true. After deployment, the browser console shows the CSP is not blocking the content you expected. You check the response headers in the browser and see two CSP headers in the response. What is happening?

medium

Your Lambda origin also sets a Content-Security-Policy header in its responses. The CloudFront policy has override=true.

  • Aoverride=true doesn't work for Content-Security-Policy — it only applies to security_headers_config block headers
    Incorrect.override=true works for custom_headers_config items too. The header name doesn't affect how override behaves.
  • BCloudFront is appending the policy header rather than replacing it — both origin and CloudFront headers are present. Browsers apply the more permissive of the two CSPs
    Correct!When two CSP headers are present, browsers apply the most restrictive intersection of both — they don't pick the more permissive one. But more importantly, override=true should replace the origin header. If you're seeing two headers, the likely cause is that the response headers policy is not correctly attached to the cache behavior, or the cache is serving a cached response that predates the policy. Invalidate the cache and verify the behavior attachment.
  • CThe Lambda function must be redeployed to stop sending its CSP header
    Incorrect.override=true is specifically designed to handle this case — you don't need to change the Lambda function. The CloudFront policy should overwrite the origin's header.
  • DCSP headers from CloudFront are ignored by Chrome but respected by Firefox
    Incorrect.Browser CSP behavior is standardized. Both Chrome and Firefox process CSP headers the same way.

Hint:If override=true should replace the origin header, but two headers are appearing, what might explain the discrepancy?