Security Headers Generator
Generate HTTP security headers for Apache, nginx or WordPress PHP, with a content security policy you can start in report-only mode.
# Security headers, Apache. Put this in .htaccess, or better, in the vhost:
# .htaccess is read on every request, a vhost is read once.
<IfModule mod_headers.c>
# Only send this over HTTPS. A browser that sees it once will refuse plain
# HTTP for the whole max-age, so start short and raise it.
Header always set Strict-Transport-Security "max-age=15768000" "expr=%{HTTPS} == 'on'"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"
# Report only: nothing is blocked. Watch the reports, then switch to enforce.
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
</IfModule>
# wp-admin is the hardest place to write a policy for, so it is left alone.
<If "%{REQUEST_URI} =~ m#^/wp-admin/#">
Header unset Content-Security-Policy
Header unset Content-Security-Policy-Report-Only
</If>
Output is valid and updates as you type.
Fix the highlighted fields to update the output.
Pick the headers you want and the server you run, and the generator writes them for Apache, nginx or WordPress itself, with a content security policy that starts in report-only mode.
How to use
- Choose the server. The PHP version works on any host, including managed hosting with no config access; a server rule is faster and covers static files too.
- Start HSTS at five minutes. A browser that sees the header once refuses plain HTTP for the whole duration, and there is no way to tell it to forget early.
- Leave the policy in report-only mode on an existing site. It blocks nothing and tells you what would have broken.
- Add your analytics, font and CDN hosts. Almost every real site needs a few, and a policy without them blocks its own assets.
- Keep wp-admin excluded until the rest of the site is clean. The block editor is the hardest part of a WordPress site to write a policy for.
Example
The starting point for an existing site, in Apache:
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy-Report-Only "default-src 'self'; …"
</IfModule>
always matters: without it the header is skipped on error responses, which is exactly where you want framing protection.
Pitfalls
- HSTS cannot be taken back quickly. A browser honours the max-age it already saw, so a two year header on a site that later loses its certificate is a two year outage for returning visitors.
includeSubDomainsapplies to every subdomain, including internal ones you forgot. Each needs a valid certificate from that moment.X-Frame-Options: DENYbreaks the WordPress customizer preview, which frames the site itself.- A content security policy without
'unsafe-inline'for styles breaks the block editor and most themes, which is why the WordPress friendly profile allows it. 'unsafe-inline'with'unsafe-eval'allows most of what CSP exists to prevent. It is a starting point, not a destination.- In nginx,
add_headerin a nested block replaces the whole inherited set rather than adding to it. Every block that sets one header must set them all. - Report-only mode with no report endpoint logs to the browser console and nowhere else, so nobody sees the violations.
- Headers set in PHP do not apply to static files served directly by the web server. Only a server rule covers those.
Compatibility
X-Content-Type-Options, X-Frame-Options and Referrer-Policy are supported by every current browser. Strict-Transport-Security needs HTTPS to mean anything and is ignored over plain HTTP. Content Security Policy Level 3 is supported by Chrome, Edge, Firefox and Safari; frame-ancestors supersedes X-Frame-Options where both are present. Permissions-Policy replaced Feature-Policy in 2020 and is honoured by Chromium browsers. The generated PHP targets PHP 7.0 and up, and the tool runs entirely in your browser.