CloudFront Content Security Policy and Security Headers Policy: Add CSP With a Response Headers Policy (or a CloudFront Function), the 1,783-Character Limit, and Why the Managed SecurityHeadersPolicy Has No CSP
To add a Content-Security-Policy (CSP) to a site served by CloudFront, create a response headers policy with your CSP under Security headers, turn on Origin override, and attach the policy to the distribution's cache behaviors. No code, no Lambda, and the header is added to every response, including cached ones. Two details catch almost everyone. First, AWS's managed SecurityHeadersPolicy, the one most tutorials tell you to pick, does not set a Content-Security-Policy at all: it adds Referrer-Policy, Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options and X-XSS-Protection, and for every header except X-Content-Type-Options it keeps whatever your origin sends. Second, CloudFront limits the CSP value to 1,783 characters by default; in our test, a policy that allowed 60 third-party script hosts came to 2,916 characters, 1,133 over the limit. This guide shows the policy step by step, the CLI and Python versions, a CloudFront Function alternative we tested, how to roll out CSP safely in report-only mode, and how to fix what it breaks.
Jake's booking site passed every functional test and then failed a security scan from the shop's new card-payment provider: "Missing Content-Security-Policy." Ethan had already attached the managed security headers policy to the CloudFront distribution. "So we have security headers," Jake said. "We have five of them," Ethan said. "The one they asked for isn't one of the five."
New to these terms? Content-Security-Policy is an HTTP response header that tells the browser which sources a page may load scripts, styles, images and frames from; anything else is blocked, which stops most cross-site scripting (XSS) and many injected-script attacks. CloudFront is AWS's content delivery network, and a response headers policy is a CloudFront setting that adds or removes HTTP headers on the responses it sends to visitors. Our plain-English CloudFront guide covers distributions and cache behaviors if you are new to them.
What the managed SecurityHeadersPolicy really sets
CloudFront offers ready-made response headers policies, and SecurityHeadersPolicy sounds like the answer. Here is everything it sets:
| Header | Value | Replaces the origin's header? |
|---|---|---|
| Referrer-Policy | strict-origin-when-cross-origin | No |
| Strict-Transport-Security | max-age=31536000 | No |
| X-Content-Type-Options | nosniff | Yes |
| X-Frame-Options | SAMEORIGIN | No |
| X-XSS-Protection | 1; mode=block | No |
| Content-Security-Policy | Not set | |
So a site with the managed policy still has no CSP, and a scanner that checks for one still fails. Notice the third column as well: for four of the five headers, if your origin already sends the header, CloudFront passes the origin's value through unchanged, even a weak one. The policy's ID, for CloudFormation, the CLI and the API, is 67f7725c-6f97-4210-82d7-5512b31e9d03. A CSP needs a policy of your own; you can include the same five headers in it, with Origin override on, so CloudFront's values always win.
One modern note: browsers no longer act on X-XSS-Protection, so it does no harm but adds nothing; a CSP is what does that job today.
How to add a Content-Security-Policy in CloudFront, step by step
- Open the CloudFront console, choose Policies, then the Response headers tab, then Create response headers policy.
- Give it a name, such as
shop-security-headers. - Under Security headers, enable Content-Security-Policy and paste your policy (see writing the policy). Turn on Origin override.
- Enable the other security headers you want, usually Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options (or rely on CSP's
frame-ancestors) and Referrer-Policy, each with Origin override on. - Optionally, under Remove headers, add
ServerorX-Powered-Byto stop advertising your server software. - Choose Create.
- Open your distribution, the Behaviors tab, edit each behavior (the default one and any path patterns), and under Response headers policy choose your new policy. Save.
- Wait for the distribution to finish deploying, then verify the header.
The policy applies at the cache behavior level, so a distribution with separate behaviors for /api/* and /static/* needs the policy on each of them. You do not need to invalidate the cache: CloudFront adds the headers when it sends each response, cached or not.
Origin override: who wins when your origin also sends the header
Each security header in a policy has an Origin override setting:
- Override on: CloudFront always sends the policy's value, replacing any value from the origin.
- Override off: if the origin's response already has the header, CloudFront keeps the origin's value and ignores the policy's; it adds its own only when the origin sent none.
Turn override on when CloudFront should be the single source of truth, which is the usual choice: one place to change the policy, the same value for every origin behind the distribution. Turn it off only when your application builds the CSP itself, for example to include a fresh nonce on every page (see nonces), and CloudFront should only fill in a default for responses that lack one.
Writing the policy: a CSP that works for a typical site
Start strict and add only what your pages really load. This is the policy we used for Jake's booking site, which loads Google Tag Manager, images from its own CloudFront domain, and an API on a subdomain:
default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline';
img-src 'self' data: https://*.cloudfront.net; connect-src 'self' https://api.example.com; font-src 'self';
frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'; upgrade-insecure-requests
What each part does, briefly:
default-src 'self': the fallback for anything not listed: only your own origin.script-src: where JavaScript may come from. Avoid'unsafe-inline'here; it lets injected inline scripts run, which defeats most of CSP's protection.style-src 'self' 'unsafe-inline': inline styles are common and much less dangerous than inline scripts; remove'unsafe-inline'when you can.img-srcandconnect-src: images, and wherefetch()and XHR may connect (APIs, analytics endpoints).frame-ancestors 'none': nobody may put your pages in an iframe (the modern replacement for X-Frame-Options).base-uri,form-action,object-src 'none': close three less obvious injection paths.upgrade-insecure-requests: load any leftoverhttp://resources over HTTPS.
In the console you paste it as one line. Every directive ends with a semicolon except the last.
The 1,783-character limit, and how to stay under it
CloudFront limits the Content-Security-Policy value in a response headers policy to 1,783 characters. That sounds like plenty until marketing adds tags. We built policies with a small script: the policy above is 319 characters, but allowing 60 separate third-party script hosts brought it to 2,916 characters, 1,133 over the limit. Ways to fit:
- Use wildcards for a vendor's subdomains (
https://*.vendor.com) instead of listing each one. - Load third-party tags through a tag manager and allow its domains once, or self-host the scripts you can.
- Remove directives that repeat
default-src: iffont-srcwould be'self'anyway, leave it out. - Request a higher quota in Service Quotas ("Maximum length of the Content-Security-Policy header value"), if the long list is genuinely needed.
- Send the CSP from your application instead (with Origin override off), where no CloudFront limit applies.
Other limits on response headers policies: 20 custom policies per account by default, 100 distributions per policy, 10 custom headers per policy, and 10,240 characters for all custom header names and values combined.
Creating the policy with the AWS CLI or Python
From the CLI, save the configuration as JSON and create the policy:
aws cloudfront create-response-headers-policy --response-headers-policy-config file://policy.json
The same thing in Python, which also checks the length before calling AWS:
#!/usr/bin/env python3
"""csp_policy.py: build a CSP, check CloudFront's 1,783-character limit, and create a response headers policy."""
import sys, boto3
LIMIT = 1783
DIRECTIVES = {
'default-src': ["'self'"],
'script-src': ["'self'", 'https://www.googletagmanager.com'],
'style-src': ["'self'", "'unsafe-inline'"],
'img-src': ["'self'", 'data:', 'https://*.cloudfront.net'],
'connect-src': ["'self'", 'https://api.example.com'],
'font-src': ["'self'"],
'frame-ancestors': ["'none'"],
'base-uri': ["'self'"],
'form-action': ["'self'"],
'object-src': ["'none'"],
'upgrade-insecure-requests': [],
}
def build(d):
return '; '.join(k + (' ' + ' '.join(v) if v else '') for k, v in d.items())
def policy_config(csp, name='shop-security-headers'):
return {'Name': name, 'Comment': 'Security headers with CSP',
'SecurityHeadersConfig': {
'ContentSecurityPolicy': {'Override': True, 'ContentSecurityPolicy': csp},
'StrictTransportSecurity': {'Override': True, 'AccessControlMaxAgeSec': 63072000, 'IncludeSubdomains': True, 'Preload': False},
'ContentTypeOptions': {'Override': True},
'FrameOptions': {'Override': True, 'FrameOption': 'DENY'},
'ReferrerPolicy': {'Override': True, 'ReferrerPolicy': 'strict-origin-when-cross-origin'}},
'RemoveHeadersConfig': {'Quantity': 1, 'Items': [{'Header': 'Server'}]}}
if __name__ == '__main__':
csp = build(DIRECTIVES)
print(csp); print(f'length {len(csp)} of {LIMIT}' + (' TOO LONG for CloudFront' if len(csp) > LIMIT else ''))
if '--create' in sys.argv:
if len(csp) > LIMIT: sys.exit('shorten the policy first')
r = boto3.client('cloudfront').create_response_headers_policy(ResponseHeadersPolicyConfig=policy_config(csp))
print('created', r['ResponseHeadersPolicy']['Id'])
We validated the request against the CloudFront API model with botocore's Stubber, without calling AWS: the configuration above is accepted as is. One detail from that test answers a common question: Override is required for every security header you include. Leave it out and boto3 stops before sending, with Missing required parameter in ResponseHeadersPolicyConfig.SecurityHeadersConfig.ContentSecurityPolicy: "Override". Strict-Transport-Security also needs its max age, and Frame-Options its value.
Roll it out safely: Content-Security-Policy-Report-Only
A strict CSP on a site that has not been audited breaks things: an analytics tag, a chat widget, a payment form. The safe way is to ship it in report-only mode first. The browser then reports what it would have blocked, in its developer console (and to a reporting endpoint, if you set one), without blocking anything.
- In your response headers policy, under Custom headers, add
Content-Security-Policy-Report-Onlywith your policy as the value, and Origin override on. Leave the real Content-Security-Policy off for now. - Browse every important page and open the browser's developer console: each violation shows the directive and the blocked source.
- Add the legitimate sources to the policy, and remove anything you did not expect to see (that is how CSP finds injected scripts).
- When a few days pass with no new legitimate violations, move the same policy into the Content-Security-Policy security header and delete the report-only custom header.
Alternative: add the CSP with a CloudFront Function
A response headers policy applies one set of headers to everything in a cache behavior. If you want the CSP only on HTML pages, or different headers by path, a CloudFront Function on the viewer response event can decide per response:
// CloudFront Function (cloudfront-js-2.0), viewer response: add a CSP and security headers to HTML only
function handler(event) {
var response = event.response;
var headers = response.headers;
var type = headers['content-type'] ? headers['content-type'].value : '';
if (type.indexOf('text/html') === 0) {
headers['content-security-policy'] = { value: "default-src 'self'; script-src 'self' https://www.googletagmanager.com; img-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; object-src 'none'" };
}
headers['strict-transport-security'] = { value: 'max-age=63072000; includeSubDomains' };
headers['x-content-type-options'] = { value: 'nosniff' };
headers['referrer-policy'] = { value: 'strict-origin-when-cross-origin' };
delete headers['server'];
return response;
}
We ran it against two simulated viewer-response events in Node.js 22: the HTML response came back with content-security-policy, content-type, referrer-policy, strict-transport-security, x-content-type-options and no server header, and the PNG response got the same minus the CSP. Header names in the event object are lower case, and each value is an object with a value property. Node is more permissive than CloudFront's own JavaScript runtime, so run the function through the console's Test tab as well before you publish it, then associate it with the behavior's Viewer response event.
For most sites the response headers policy is simpler and has no per-request cost; choose the function only when you need per-response logic. Neither can rewrite the HTML body, which matters for nonces.
Nonces and hashes: why CloudFront alone cannot do them
The strongest CSPs allow inline scripts only with a matching nonce (a random value that changes on every page load and appears both in the header and in each <script nonce="..."> tag) or a hash of each allowed script. A nonce must be the same in the header and the HTML, so whoever builds the HTML must also set the header. CloudFront response headers policies send the same value every time, and CloudFront Functions cannot modify the response body. So:
- Hashes work with CloudFront: compute the SHA-256 of each inline script at build time and list them in
script-src(watch the 1,783 characters). - Nonces belong in your application or server-side rendering, which sends the CSP itself, with Origin override off in CloudFront.
- For a static site on S3, the practical choice is to move inline scripts into files and allow
'self'.
What a new CSP breaks, and how to fix it
| Symptom | Console message mentions | Fix |
|---|---|---|
| Analytics or tag manager stopped | script-src, connect-src | Allow the vendor's script and collection domains |
| Fonts fall back to defaults | font-src, style-src | Allow the font CDN for both, or self-host fonts |
Inline buttons (onclick) do nothing | script-src, inline | Move handlers into a script file |
| Embedded video or map is blank | frame-src | Allow the provider in frame-src |
| Payment or login popup fails | frame-src, form-action, connect-src | Allow the provider's documented domains |
| Your site cannot be embedded by a partner | frame-ancestors | List the partner's origin instead of 'none' |
Payment providers and identity providers publish the exact domains their widgets need; use their list rather than guessing. If an API call breaks with a CORS-looking error after you add a CSP, check connect-src before you touch CORS; our CORS guide covers the real CORS errors.
How to check the header is really there
curl -sI https://www.example.com/ | grep -i -E "content-security-policy|strict-transport|x-content-type|x-frame|referrer"
Also check a cached object, an error page and a file under every path pattern, because each cache behavior needs the policy. If the header is missing:
- The policy is attached to the default behavior but not to the path pattern that served the response.
- The distribution is still deploying; wait until its status is Deployed.
- Override is off and the origin sends its own (perhaps empty or different) value.
- You are looking at a response from another layer, such as a browser extension or a corporate proxy that rewrites headers; test from a second network.
Browser developer tools (the Network tab, response headers) show the same thing, and an online header scanner is a useful second opinion. Ethan's re-scan after attaching the new policy came back clean, and the payment provider's checklist turned green the same afternoon.
CloudFront Content-Security-Policy: frequently asked questions
How do I add a Content-Security-Policy header in CloudFront?
Create a response headers policy, enable Content-Security-Policy under Security headers with your policy and Origin override on, and attach the policy to every cache behavior of the distribution.
Does the CloudFront SecurityHeadersPolicy include a CSP?
No. The managed SecurityHeadersPolicy sets Referrer-Policy, Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options and X-XSS-Protection, but no Content-Security-Policy. Create your own policy for CSP.
What is the maximum length of a CSP in CloudFront?
1,783 characters for the Content-Security-Policy value in a response headers policy, by default. You can request a higher quota in Service Quotas.
What does Origin override mean in a CloudFront response headers policy?
With override on, CloudFront always sends the policy's header value. With it off, CloudFront keeps the origin's header when the origin sends one and adds its own only when the origin sent none.
For what security purpose is the CloudFront response headers policy needed?
It lets CloudFront add headers that tell browsers to enforce HTTPS, block content-type sniffing, refuse framing and restrict where scripts load from, without changing your origin's code.
Can I add a CSP to an S3 static website behind CloudFront?
Yes. S3 cannot set CSP on its own, so attach a response headers policy with the CSP to the distribution's behaviors; it applies to every object CloudFront serves.
Is it possible to set Content-Security-Policy headers in Amazon S3?
Not as a response header S3 sends for every object. Put CloudFront in front of the bucket and add the CSP with a response headers policy, which covers every object the distribution serves.
What is a recommended Content-Security-Policy?
Start with default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; form-action 'self', then add only the script, style, image and connect sources your pages really use, tested in report-only mode.
How do I set Strict-Transport-Security in CloudFront?
Enable Strict-Transport-Security in the response headers policy with a max age (for example 63072000 seconds), optionally includeSubDomains and preload, and Origin override on. Also set the viewer protocol policy to redirect HTTP to HTTPS.
Do I need to invalidate the CloudFront cache after adding a response headers policy?
No. CloudFront adds the headers when it sends each response, including responses served from cache. Wait for the distribution to finish deploying.
How do I test a CSP without breaking my site?
Send the policy in a Content-Security-Policy-Report-Only custom header first, read the violations in the browser console, adjust, then switch to the enforcing header.
Can a CloudFront Function add security headers?
Yes, on the viewer response event, by setting entries in event.response.headers. Use it when headers must differ by content type or path; otherwise a response headers policy is simpler.
Can CloudFront add a CSP nonce?
Not on its own. A nonce must match the HTML, and neither response headers policies nor CloudFront Functions can change the body. Generate nonces in your application and turn Origin override off.
What are the default settings required in a boto3 ResponseHeadersPolicyConfig?
Every security header you include needs Override, and some need their value, such as AccessControlMaxAgeSec for Strict-Transport-Security and FrameOption for X-Frame-Options. Missing Override fails validation before the request is sent.
How many custom headers can a CloudFront response headers policy have?
Ten by default, with 10,240 characters for all custom header names and values combined. You can request higher quotas.
How do I remove the Server header in CloudFront?
Add Server under Remove headers in the response headers policy, or delete it in a viewer-response CloudFront Function.
Should I still use X-Frame-Options with CSP?
CSP's frame-ancestors directive replaces it in modern browsers. Sending both is harmless and helps older clients.
Why is my CloudFront security header not showing?
The policy may be missing from the cache behavior that served the response, the distribution may still be deploying, or Override may be off while the origin sends its own value.
Is X-XSS-Protection still needed?
Modern browsers ignore it. It does no harm, but a Content-Security-Policy is what protects against cross-site scripting now.
For readers preparing for an AWS certification
We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.
Exams that test this topic: Security – Specialty (SCS-C03) • Solutions Architect – Associate (SAA-C03) • Developer – Associate (DVA-C03 from December 1, 2026).
Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:
1. A static website on Amazon S3 is served through CloudFront. A security review requires a Content-Security-Policy header on every response without changing the site's code. What is the simplest solution?
Create a CloudFront response headers policy with the Content-Security-Policy security header (Origin override on) and attach it to the distribution's cache behaviors.
2. A team attached the managed SecurityHeadersPolicy to a distribution, but a scanner still reports a missing Content-Security-Policy. Why?
The managed SecurityHeadersPolicy does not include a Content-Security-Policy; a custom response headers policy is needed for CSP.
3. An origin already sends weak security headers. CloudFront must always send the company's values instead. Which setting ensures this?
Origin override set to true for each security header in the response headers policy.
4. A company wants to test a new Content-Security-Policy without breaking the site. What should it do?
Send the policy in a Content-Security-Policy-Report-Only header first (a custom header in the response headers policy), review violations, then enforce it.
5. Security headers must differ by content type: CSP only on HTML responses. Which CloudFront feature fits with the least overhead?
A CloudFront Function on the viewer response event that sets headers based on the response's content type.
6. A developer wants every inline script to require a per-request nonce in the CSP. Where must the CSP be generated?
In the application that renders the HTML, because CloudFront cannot change the response body; set Origin override off so CloudFront keeps the application's header.
Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.
The payment provider's scan passed on Monday, and the only thing the new policy broke was a forgotten chat widget nobody had used since spring. "So the CSP found a ghost," Jake said. "It found a script we were still loading for no reason," Ethan said. "I'd call that a bonus." If your results differ from what this page describes, please tell us. Guides like this stay right because readers write in.
📌 If you keep one line from this page
The managed SecurityHeadersPolicy has no Content-Security-Policy, so create your own response headers policy with a CSP of at most 1,783 characters and Origin override on, and try it in report-only mode first.
Managed policy 67f7725c-6f97-4210-82d7-5512b31e9d03 = no CSP · 1,783-character CSP limit · Override required · 10 custom headers per policy · October 24, 2026.
Revision note. Written October 24, 2026, from CloudFront's managed policy settings, an API-model check of the policy request, and a local test of the CloudFront Function. "An ounce of prevention is worth a pound of cure," the old saying goes, and a CSP is mostly prevention.