
Security headers are instructions you hand to the browser before your theme, your plugins, or a single line of PHP ever runs. They cost nothing in server resources and they close off entire classes of attacks (content sniffing, clickjacking, some script injection) at the browser level. The catch is that the more aggressive headers, especially Content-Security-Policy, can break your site if you flip them on all at once. This guide gives you the headers worth using, the order to add them in, and how to confirm each one is live without guessing.
What each header actually does
X-Content-Type-Options: nosniff stops the browser from guessing a file’s type based on its content instead of its declared Content-Type. Without it, a browser can be tricked into executing a file as script when it was served as something else. This header has essentially no downside for a WordPress site.
Referrer-Policy controls how much of your URL gets sent to other sites when a user clicks a link away from your pages. A value like strict-origin-when-cross-origin keeps full referrer data on your own domain but trims it down to just the origin when leaving, which is a sane default for analytics and privacy.
Permissions-Policy lets you switch off browser features your site does not use, like the camera, microphone, or geolocation APIs. It reduces the attack surface available to any script that manages to run on your pages, intentionally or not.
X-Frame-Options (and its modern CSP equivalent, frame-ancestors) prevents your site from being loaded inside an iframe on someone else’s domain. This is your defense against clickjacking, where an attacker overlays your login or checkout page under invisible controls on their own site.
Strict-Transport-Security (HSTS) tells the browser to never load your site over plain HTTP again, for a period of time you specify, even if a user types the http:// version or clicks an old link. It closes the window where an attacker on the network could downgrade a connection before HTTPS kicks in. It is also the one header on this list that is genuinely hard to undo once browsers have cached it, so treat it with respect.
Content-Security-Policy (CSP) is the big one: it tells the browser exactly which domains are allowed to serve scripts, styles, images, and fonts to your pages, and it can block inline scripts outright. Done well, CSP neutralizes most cross-site scripting attempts even if malicious code somehow gets injected into a page. Done carelessly, it silently breaks your page builder, your analytics snippet, and your Google Fonts.
A rollout order that will not break your theme
Add these roughly in this sequence, verifying each step before moving to the next:
- X-Content-Type-Options and Referrer-Policy first. Both are effectively risk-free and rarely interact with themes or plugins.
- Permissions-Policy next. Disable only the browser features you know you do not use. If you are unsure whether a plugin needs geolocation or camera access, leave that directive out for now rather than guessing.
- X-Frame-Options / frame-ancestors. Set this to
SAMEORIGIN(or'self'for the CSP version) unless you deliberately embed your site in an iframe on another domain you control, such as a preview environment. Some page builders open live previews in an iframe on the same domain, which is fine under SAMEORIGIN. - HSTS with a short max-age first. Start with something like a day (
max-age=86400), confirm HTTPS works everywhere on your site including any subdomains you reference, then raise the value over time (a year,max-age=31536000, is the common long-term setting). Hold off on thepreloaddirective until you are fully confident every subdomain and asset is HTTPS-only, since preload submission is meant to be effectively permanent. - CSP last, and always in report-only mode first. Use
Content-Security-Policy-Report-Onlyto see what would have been blocked without actually blocking anything, watch your browser console and any report endpoint for violations, adjust the policy to allow the domains you genuinely need (your CDN, your analytics provider, your fonts), and only then switch to the enforcingContent-Security-Policyheader.
Where to set the headers
You have three reasonable places to add these: the web server config, an edge layer like Cloudflare, or occasionally a security plugin. Setting them at the server or edge is more reliable because they apply consistently to every response, including anything served from a full-page cache.
On LiteSpeed or Apache-compatible setups, an .htaccess block works well:
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set X-Frame-Options "SAMEORIGIN"
</IfModule>Add HSTS once you have confirmed HTTPS is solid everywhere:
Header always set Strict-Transport-Security "max-age=86400; includeSubDomains"And CSP in report-only form while you tune it:
Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' https://your-analytics-domain.example; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; frame-ancestors 'self'"If your site sits behind Cloudflare, you can also set headers at the edge using Transform Rules, which has the advantage of applying even if your origin config gets overwritten by an update or migration, and it works whether or not your caching plugin is involved. Since Cloudflare already terminates SSL and can enforce HTTPS at the edge, adding HSTS there alongside your origin setting is a reasonable belt-and-suspenders approach. Some managed hosts, ServerBorn included, apply a sane baseline of these headers at the server level automatically, which is worth checking before you duplicate the work.
Verifying the results
Do not assume a header is live just because you added the config line. Check with your browser’s developer tools: open the Network tab, reload the page, click the main document request, and look at the Response Headers section. You should see each header you configured with the value you expect. Any general online HTTP header checker will give you the same view without opening dev tools, which is handy for checking from outside your own network.
For CSP specifically, watch the browser console while report-only mode is active. Violations show up as console warnings naming the exact resource and directive that would have been blocked, which tells you precisely what to add to the allow list before you switch to enforcing mode.
Common ways this breaks a WordPress site
CSP is the usual culprit. Inline <script> and <style> tags added by themes, page builders, and third-party plugins will get blocked unless your policy allows 'unsafe-inline' or you migrate to nonces or hashes, which is more setup than most site owners want to take on. Google Fonts, embedded video, chat widgets, and analytics scripts all load from external domains that need to be explicitly named in your policy or they will silently stop working.
HSTS breaks things differently: if any subdomain or embedded resource is still served over plain HTTP when you turn it on with includeSubDomains, browsers will refuse to load it, and there is no quick undo once the header has been cached by returning visitors.
X-Frame-Options and frame-ancestors can interfere with legitimate iframe embeds, such as a booking widget you intentionally load from another domain, or a preview pane in some page builders that loads from a staging subdomain.
The takeaway
Start with the headers that carry no real risk (nosniff, Referrer-Policy, a sensible Permissions-Policy), add clickjacking protection next, bring in HSTS gradually with a short max-age before committing long-term, and treat CSP as a tuning project you run in report-only mode until the console goes quiet. Verify each header in the actual response, not just in your config file. Done in that order, you end up with a meaningfully harder target for common browser-based attacks, and nothing on your site breaks along the way.