HTTP Security Headers: Cheap Defense Against XSS, Clickjacking and MIME Attacks

X-Frame-Options, X-Content-Type-Options, Referrer-Policy, CSP — four headers that block a long list of client-side attacks.

HTTP security headers are instructions WordPress — or more precisely, the web server — sends with every response. They do not patch vulnerabilities, but they dramatically reduce an attacker's ability to exploit a vulnerability that does exist. They cost nothing — just a server config change — and they neutralize entire categories of client-side attacks.

Why this matters

Without X-Frame-Options an attacker can embed your site inside an iframe on a malicious page, overlay invisible buttons on top, and trick a logged-in visitor into clicking "delete account" without realizing — this is clickjacking. Without X-Content-Type-Options: nosniff the browser may guess at file types, so an image that an attacker uploaded with embedded JavaScript could execute as a script. Without an appropriate Referrer-Policy, a visitor on /wp-admin/edit.php?post_id=42 who clicks an outbound link sends that internal path to a third party — leaking admin URL structure. Without Permissions-Policy, any script on the page can request access to camera, microphone, or geolocation. Without CSP, every XSS bug becomes an actual injection that runs in every visitor's browser.

How to detect

Run:

curl -I https://example.com/ | grep -iE "x-frame|x-content|referrer|permissions|content-security"

If the lines are missing, the headers are not configured. A convenient external tool is securityheaders.com — it grades the response from A to F and lists what is missing. A typical untouched WordPress site scores F until configured.

How to fix

On Apache, add to root .htaccess or to the VirtualHost:

<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 Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>

On Nginx, inside the server block:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

CSP is more involved. Start in Report-Only mode so you can see what would break without actually breaking anything:

Header always set 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:; report-uri /csp-report"

Common mistakes

Mistake one: enforcing a strict CSP from day one. WordPress and many themes still rely on inline JavaScript and inline CSS. A CSP without 'unsafe-inline' will break a typical theme. Roll out gradually: Report-Only for two weeks, then enforce. Mistake two: using X-Frame-Options: DENY on a site that uses Elementor, a page builder preview, or any internal iframe. SAMEORIGIN is almost always the right choice. Mistake three: setting headers both in the server config and in a PHP plugin — duplicate headers like X-Frame-Options: SAMEORIGIN, SAMEORIGIN are sometimes ignored by browsers.

Verifying the fix

Re-run curl -I and confirm every header appears. Visit securityheaders.com and check the grade is A or A+. Walk a few pages in Chrome DevTools > Network > Headers and confirm the headers ship on every response. If you added CSP, open the Console and verify there are no "Refused to load" errors — if there are, switch back to Report-Only until you fix the violations.

Tip: The "HTTP Headers" plugin can manage these from the WP dashboard, but it adds PHP overhead to every request. If you have access to .htaccess or Nginx, configure at the web server level — it is free, faster, and does not depend on PHP.