Work Services Journal About Contact
Warsaw Contact

Add Security Headers to WordPress Without a Plugin

Run almost any site through securityheaders.com or Mozilla Observatory and the first thing you notice is how much of the score has nothing to do with your code and everything to do with a handful of missing HTTP response headers. WordPress does not send most of them by default. A security plugin will add them, but so will six lines on the send_headers hook, and I would rather keep one more plugin out of the stack.

What it sends

The snippet hooks into send_headers and sets a small set of headers on every response:

  • Strict-Transport-Security, forcing HTTPS for a year, including subdomains, with preload
  • X-Frame-Options: DENY, blocking the site from being embedded in an iframe (clickjacking protection)
  • X-Content-Type-Options: nosniff, stopping browsers from guessing a file's MIME type
  • Referrer-Policy: strict-origin, limiting what referrer information leaks to other sites
  • Permissions-Policy, disabling geolocation, microphone, camera, USB, Bluetooth and payment APIs the site does not use
  • Removes the X-Powered-By header, so you are not advertising your PHP version to anyone probing the site

The code

Add this as a new PHP snippet via the Code Snippets plugin, never functions.php:

function set_security_headers() {
    if (!headers_sent()) {
        header("Strict-Transport-Security: max-age=31536000; includeSubDomains; preload");
        header("X-Frame-Options: DENY");
        header("X-Content-Type-Options: nosniff");
        header("Referrer-Policy: strict-origin");
        header("Permissions-Policy: geolocation=(), microphone=(), camera=(), usb=(), bluetooth=(), payment=()");
        if (function_exists('header_remove')) {
            header_remove("X-Powered-By");
        }
    }
}
add_action('send_headers', 'set_security_headers');

Still works in 2026

These headers have not changed in years and every major browser still honours them. The one thing worth knowing: X-Frame-Options: DENY also blocks your own site from framing itself, which can matter if a page builder previews pages in an iframe. Switch it to SAMEORIGIN if that bites you. And if a CDN or cache layer serves pages without hitting PHP at all, as mine usually do, set the same headers at that layer instead so cached responses are covered too.

Full source on GitHub: djaysan/security-headers. Want your site's headers checked and fixed? Get in touch.

Let’s build

Have a project in mind?

Get in touch