WordPress
security headers
reference.
Every HTTP header a serious WordPress site should send in 2026 — with the exact recommended value, why it matters, and the gotchas that break WordPress specifically. Copy, paste, test.
Strict-Transport-Security (HSTS)
Why: Forces every browser that has ever visited your site to only use HTTPS for the next N seconds. Kills SSL-stripping attacks and stops downgrade attempts on public wifi.
max-age=63072000; includeSubDomains; preload
Notes: Only enable includeSubDomains after auditing every subdomain has valid HTTPS. Preload requires submission at hstspreload.org and is essentially irreversible — do it last, once you're certain.
Content-Security-Policy (CSP)
Why: The single most important header. Controls what scripts, styles, images, iframes, and forms can load. Blocks nearly every stored-XSS and injected-crypto-miner attack even if a plugin gets compromised.
default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; frame-src 'self' https://www.youtube.com https://player.vimeo.com; connect-src 'self' https://www.google-analytics.com; object-src 'none'; base-uri 'self'; form-action 'self'
Notes: Ship in Report-Only mode first (`Content-Security-Policy-Report-Only`) for a week to a reporting endpoint before enforcing. WordPress with plugins usually needs 'unsafe-inline' for styles — accept that unless you can inline-hash-audit every plugin.
X-Frame-Options
Why: Stops other sites embedding yours in an iframe (clickjacking). Superseded by CSP's frame-ancestors but still respected by older browsers — ship both.
SAMEORIGIN
Notes: Use DENY if you never need to embed your own pages (rare). Also add `frame-ancestors 'self'` to your CSP as the modern equivalent.
X-Content-Type-Options
Why: Stops browsers 'sniffing' file types and executing an uploaded .txt as JavaScript. Critical for any WordPress site with user uploads.
nosniff
Notes: Zero downside, always enable.
Referrer-Policy
Why: Controls how much URL info gets sent when a visitor clicks a link off your site. Default browser behaviour leaks the full URL to third parties — including query strings that can contain session data.
strict-origin-when-cross-origin
Notes: Good balance. Use `no-referrer` if privacy-critical (health, legal); use `same-origin` for internal admin panels.
Permissions-Policy (formerly Feature-Policy)
Why: Disables browser APIs your site doesn't need — camera, microphone, geolocation, autoplay, USB, payments. A compromised plugin can't ask for the user's camera if you've turned the API off at the header level.
camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()
Notes: interest-cohort=() opts out of Google's FLoC / Topics API — worth including for privacy positioning.
Cross-Origin-Opener-Policy
Why: Isolates your browsing context from cross-origin windows opened via window.open. Protects against tab-nabbing and Spectre-class side-channel attacks.
same-origin
Notes: May break OAuth popups (Google login, Stripe Checkout iframe) — test the auth flow after enabling. Use `same-origin-allow-popups` if it does.
Cross-Origin-Resource-Policy
Why: Stops other sites hot-linking your images and scripts, and prevents your assets from being loaded into a compromised third-party context.
same-site
Notes: Use `cross-origin` for CDN-served assets you explicitly want embeddable (e.g. a logo hosted on a CDN sub-domain).
X-XSS-Protection
Why: Legacy header. Modern browsers ignore it; older ones can be actively harmful with it enabled. Ship it disabled explicitly.
0
Notes: Yes, 0 not 1. See github.com/helmetjs/helmet — the modern recommendation is to disable the old XSS auditor and rely on CSP instead.
Copy-paste configs
Nginx (site config, inside `server `)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()" always; add_header Cross-Origin-Opener-Policy "same-origin" always; add_header Cross-Origin-Resource-Policy "same-site" always; add_header X-XSS-Protection "0" always; # CSP: start with Report-Only for a week add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; frame-src 'self' https://www.youtube.com; report-uri /csp-report" always;
Apache (in .htaccess or vhost)
<IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" 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=(), payment=(), usb=(), interest-cohort=()" Header always set Cross-Origin-Opener-Policy "same-origin" Header always set Cross-Origin-Resource-Policy "same-site" Header always set X-XSS-Protection "0" </IfModule>
Cloudflare (Transform Rules → Response Header)
Add each header as a separate Response Header rule. Cloudflare's UI is per-header, which is actually easier than editing config — and headers set here survive a WordPress compromise entirely.
Free plan includes Response Header Transform Rules. Use them.
WordPress-specific gotchas
- The customizer breaks under strict CSP. WordPress's Customizer live-preview uses postMessage inside an iframe with inline scripts. Add a CSP exception for /wp-admin/customize.php, or accept 'unsafe-inline' in script-src (which most WordPress sites do anyway).
- Gutenberg needs 'unsafe-eval'. The block editor uses eval() for some block registration. If you strip it, the editor breaks. Scope 'unsafe-eval' to /wp-admin/ only via a separate CSP rule.
- jQuery migrate emits inline scripts. WordPress core still ships wp-includes/js/jquery/jquery-migrate.min.js with inline hooks. Either accept 'unsafe-inline' or deregister jQuery migrate.
- Elementor, Divi, and WPBakery inject inline everything. If you use these, strict CSP is essentially impossible without 'unsafe-inline'. This is one of many reasons block themes exist.
- HSTS preload is nearly irreversible. Once on the preload list, removing takes months. Only submit once you've run HSTS with preload directive for at least 30 days without incident.
- CSP report-uri needs an endpoint that accepts POST + application/csp-report. WordPress doesn't ship one. Use report-to instead, or a service like report-uri.com (free tier available).
- Cloudflare adds its own headers. If you're behind Cloudflare, check that your origin's headers aren't being stripped or duplicated. Cloudflare's `Cf-Cache-Status` and `Cf-Ray` are harmless; duplicate `X-Frame-Options` will confuse strict browsers.
“A security header is the last line of defence that doesn't rely on your plugins being uncompromised. Ship them at the server or CDN layer, not from a WordPress plugin — that plugin can be the thing that gets compromised.”
Every security audit I ship includes a full headers rewrite — server, CDN, and any WordPress-side fallbacks — tested against securityheaders.com and Mozilla Observatory. Median rating goes from D to A+ in one pass.