Free tool
CSP report header builder
Choose what your pages may load, and get back a complete report-only header: both reporting mechanisms, the Reporting-Endpoints header that report-to does nothing without, and the lines to paste into nginx, Apache, Vercel, Netlify or Node.js.
Report-only blocks nothing. The browser reports what the policy would have blocked and loads it anyway, and every policy should start that way. The header is built by JavaScript on this page, with no account, and nothing is uploaded.
Start from your current header
Paste the policy you serve now and the rows below take its sources. Directives the rows do not offer are carried through unchanged and listed, never dropped. Apply fixes then makes the changes that are safe without knowing your site, and lists each beside the finding it answers.
Your header
This header is report-only, so it blocks nothing, and Apply fixes is safe. It can take 'unsafe-inline', 'unsafe-eval', wildcards and schemes out of the script sources without breaking a page, and the reports that follow show what the stricter policy would have blocked. Send them to HeaderHawk and you work through that list before you enforce. The fixes never add a nonce or a hash, and never remove a host.
Fixes applied
Carried through from your header
Adjusted from what you typed
Any server or CDN
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'report-sample'; style-src 'self' 'report-sample'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/r; report-to headerhawk
Reporting-Endpoints: headerhawk="https://your-ingest-code.ingest.us.headerhawk.com/r"A complete config for each platform, with the gotchas that stop a header arriving: CSP by platform.
The server block
# A location block that sets any add_header of its own inherits none
# of these, so repeat both lines in every location that adds headers.
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'report-sample'; style-src 'self' 'report-sample'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/r; report-to headerhawk" always;
add_header Reporting-Endpoints 'headerhawk="https://your-ingest-code.ingest.us.headerhawk.com/r"' always;A complete nginx config, with the gotchas that stop the header arriving: CSP on nginx.
VirtualHost or .htaccess, with mod_headers
# "always" sends them on error responses too, which "onsuccess" does not.
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'report-sample'; style-src 'self' 'report-sample'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/r; report-to headerhawk"
Header always set Reporting-Endpoints 'headerhawk="https://your-ingest-code.ingest.us.headerhawk.com/r"'A complete Apache config, with the gotchas that stop the header arriving: CSP on Apache.
vercel.json
{
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy-Report-Only",
"value": "default-src 'self'; script-src 'self' 'report-sample'; style-src 'self' 'report-sample'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/r; report-to headerhawk"
},
{
"key": "Reporting-Endpoints",
"value": "headerhawk=\"https://your-ingest-code.ingest.us.headerhawk.com/r\""
}
]
}
]
}A complete Vercel config, with the gotchas that stop the header arriving: CSP on Vercel.
_headers in the publish directory
/*
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'report-sample'; style-src 'self' 'report-sample'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/r; report-to headerhawk
Reporting-Endpoints: headerhawk="https://your-ingest-code.ingest.us.headerhawk.com/r"A complete Netlify config, with the gotchas that stop the header arriving: CSP on Netlify.
Express middleware, before your routes
app.use((req, res, next) => {
res.setHeader(
"Content-Security-Policy-Report-Only",
"default-src 'self'; script-src 'self' 'report-sample'; style-src 'self' 'report-sample'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; report-uri https://your-ingest-code.ingest.us.headerhawk.com/r; report-to headerhawk",
);
res.setHeader("Reporting-Endpoints", "headerhawk=\"https://your-ingest-code.ingest.us.headerhawk.com/r\"");
next();
});A complete Node.js config, with the gotchas that stop the header arriving: CSP in Node.js.
Checked by the policy analyzer
Every change is graded by the same rules as the CSP policy analyzer. Its note that a report-only policy blocks nothing is left out, because this header is meant to be report-only.
Why the header reports two ways
report-uri is the original mechanism, and it is deprecated. report-to replaced it, sending reports through the browser's Reporting API. A browser that understands report-to uses it and ignores report-uri, so listing both never sends a report twice.
Both are still needed because report-to arrived late in Safari and Firefox. Chrome and Edge have sent it for years, Safari since 16.4, and Firefox only since 149, released in March 2026. A visitor on an older Safari or Firefox reports through report-uri or not at all.
And report-to names a group, not a URL. The group, headerhawk here, is defined by the separate Reporting-Endpoints response header. A policy that says report-to headerhawk without that header reports nothing, and nothing tells you why. Every format above sets both headers.
Two practical differences: reports sent through report-to are batched, so they can take a minute or so to arrive, and they are only sent from pages served over HTTPS.
What this cannot do for you
It writes a starting policy, not your policy. It does not fetch your site, so it cannot know which hosts your pages load from. The same-origin starting point reports every third-party script, font and API call you use, and in report-only mode that is how you find them. It cannot set a nonce either, because a nonce has to change on every response and a header fixed in server configuration does not. upgrade-insecure-requests is not offered, because browsers ignore it in a report-only policy.
See what this header would block
Put your ingest code in it and the reports arrive in HeaderHawk grouped by directive, blocked source and page, with the file, line and script sample behind each group. Free for 1 site.