CloudFront Response Headers Policies: Security Headers, SEO Tags, and CORS
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.
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: noindexfor 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 CloudFrontResponse 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-ProtectionCORSWithDefaultHeaders: standard CORS headers allowing all origins (not suitable for credentialed requests)CORSWithSecurityHeadersPolicy: CORS + security headers combinedSimpleCORS: 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?
mediumYour 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?