Most WordPress sites send none of the standard security headers, and many of the plugins that add them only cover PHP pages. Here's where to set them, values that don't break the dashboard or WooCommerce, and how to check the result.
From best to worst:
.htaccess, or nginx). This covers static files and cached pages too.Use only one. If the same header comes from two places, browsers get it twice, and two Content-Security-Policy headers are both enforced, which quietly breaks scripts.
Most cPanel hosts run one of these. Add this to the .htaccess file in your WordPress root, above the # BEGIN WordPress line, because WordPress rewrites that block:
<IfModule mod_headers.c> Header always set Strict-Transport-Security "max-age=300" 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=()" Header unset X-Powered-By </IfModule>
Keep the word always, or Apache skips the headers on redirects and error pages. If nothing changes, your host may have mod_headers off; ask them.
In the site's server block:
add_header Strict-Transport-Security "max-age=300" 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=()" always;
Watch out: if a location block has its own add_header, nginx drops the server-level ones for that location. Repeat them there or use an include file. Reload with sudo nginx -t && sudo systemctl reload nginx.
Go to Rules, then Transform Rules, then Modify Response Header. Create a rule for all incoming requests and add one "Set static" line per header above. Also turn on Always Use HTTPS and set SSL/TLS to Full (strict).
Save as wp-content/mu-plugins/security-headers.php. Must-use plugins load automatically and can't be switched off by accident:
<?php
add_action('send_headers', function () {
header('Strict-Transport-Security: max-age=300');
header('X-Frame-Options: SAMEORIGIN');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: camera=(), microphone=(), geolocation=()');
});This only covers pages WordPress builds, not images or files served straight from disk, and a page cache may serve old copies until you purge it.
max-age=31536000; includeSubDomains. Add preload only if every subdomain is on HTTPS, because it's hard to undo.Core, themes, and plugins print inline scripts, so a strict CSP takes real work. Two directives are safe right away:
Content-Security-Policy: frame-ancestors 'self'; upgrade-insecure-requests
For a full policy, start with Content-Security-Policy-Report-Only, click through the site, wp-admin, checkout, and forms with the browser console open, add each legitimate source (Stripe, PayPal, Google Analytics, reCAPTCHA, fonts), and only then switch to enforcing.
curl -sI https://yoursite.com | grep -iE "strict-transport|x-frame|x-content|referrer|permissions|content-security"
Check from outside wp-admin, after purging your cache plugin and any CDN cache. Hosting elsewhere? See our security headers checker guide for Netlify, Vercel, and Cloudflare configs.
Want a second opinion in under a minute? Scan your site free with SiteShield. It checks which security headers your WordPress site actually sends (HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy), plus SSL, SPF, DMARC, and privacy basics and shows your score and every issue found. If you'd rather skip the trial and error, the $14 fix bundle gives you copy-paste fixes written for your exact site.
SiteShield by Telekeen · Telekeen Communications