techcoff.com

How to Add Security Headers to WordPress Without a Plugin (2026)

How to add security headers to WordPress without a plugin — HSTS, X-Frame-Options guide

A fresh WordPress install scores a D or F on a security headers scan — and in Scott Helme’s June 2026 crawl of the top 1 million sites, 440,832 of the 819,002 that responded scored an F. Strong passwords and a firewall will not stop clickjacking or downgrade attacks. A few lines of code will.

Learning how to add security headers to WordPress without a plugin takes about ten minutes, costs nothing, and adds zero plugins to slow your site down. Below is the copy-paste code for all three layers — .htaccess, Nginx, and a small snippet — plus the two mistakes that break more sites than any hacker ever will.

Key takeaway: security headers are free, invisible to visitors, and take minutes to add — there is no good reason to run a WordPress site without them.

The 6 HTTP Security Headers WordPress Needs

HTTP security headers are short instructions your server sends to every visitor’s browser before any page content loads. They tell the browser what it is and is not allowed to do with your site — and the browser enforces them automatically. Here are the six that do the real work:

HeaderWhat attack it stopsRecommended value
Strict-Transport-Security (HSTS)Downgrade attacks that force visitors back to unencrypted HTTPmax-age=31536000; includeSubDomains
X-Frame-OptionsClickjacking — your site loaded inside a hidden frame on a scam siteSAMEORIGIN
X-Content-Type-OptionsMIME sniffing — the browser guessing a file’s type and running it as codenosniff
Referrer-PolicyLeaking the full URL of the page a visitor came from to other sitesstrict-origin-when-cross-origin
Permissions-PolicyStrangers’ scripts using your visitors’ camera, microphone, or locationcamera=(), microphone=(), geolocation=()
Content-Security-PolicyCross-site scripting (XSS) — injected scripts running on your pagesA light default-src 'self' policy (below)

Picture a hidden frame on a scam page loading your login form underneath a fake “Claim your prize” button. One click, and your visitor submits a form they never saw. X-Frame-Options with SAMEORIGIN makes that attack impossible — the browser refuses to frame your site anywhere except on your own domain.

WordPress core sends almost none of these headers on the front end, which is why a brand-new install fails a header scan. Headers are one layer of a full hardening setup — see our complete guide to securing WordPress without a plugin, and pair them with locking down wp-config.php.

Key takeaway: you only need six headers — everything else on the scanner’s list is outdated or optional.

Before You Start: Pick ONE Layer and Back Up First

You have three ways to add headers — server config, Nginx config, or code — but pick exactly one. Set the same header in two places and your server sends it twice, confusing browsers and making a Content-Security-Policy stricter than either version alone.

Back up your site (or at least the file you are about to edit) first. One typo in .htaccess can take down every page with a 500 error, and you will want a one-click restore.

Key takeaway: one layer only, and a backup first — duplicates and typos cause more outages than attackers do.

Method 1: WordPress Security Headers .htaccess (Apache / Shared Hosting)

Most shared WordPress hosting — Bluehost, Hostinger, SiteGround, and most cPanel hosts — runs Apache, so .htaccess is the method most readers need. Open .htaccess in your WordPress root folder (via your host’s file manager or FTP) and paste this block above the # BEGIN WordPress section:

<IfModule mod_headers.c>
    # HSTS: force HTTPS for one year (only sent over HTTPS)
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" env=HTTPS
    # Stop clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"
    # Stop MIME sniffing
    Header always set X-Content-Type-Options "nosniff"
    # Limit referrer data sent to other sites
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    # Block camera, microphone, and location access
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
    # Light CSP: only allow resources from your own site
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'"
</IfModule>

Three details matter. Place it OUTSIDE the # BEGIN WordPress block — WordPress rewrites everything between its own markers whenever you save permalink settings, and anything inside gets wiped out. Use Header always set, not just Header set — without always, Apache skips the header on error pages and redirects, leaving your 404 pages and HTTP-to-HTTPS redirect unprotected. The <IfModule mod_headers.c> wrapper is your safety net — if your host has not enabled the headers module, Apache skips the block instead of crashing your site with a 500 error.

The env=HTTPS condition on the HSTS line is deliberate: browsers ignore HSTS on plain HTTP, so it is never sent on an insecure connection. LiteSpeed hosts can use this same block — LiteSpeed reads Apache-style rules.

Key takeaway: paste above # BEGIN WordPress, use Header always set, and let the IfModule wrapper protect you from a 500 error.

Method 2: Add HSTS to WordPress on Nginx

If your site runs on Nginx — a VPS, a Cloudways or Kinsta-style setup, or any modern managed host — there is no .htaccess file. Add the headers to your HTTPS server block instead:

server {
    listen 443 ssl;
    server_name example.com;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'" always;

    # ...the rest of your server block...
}

Put the headers in the HTTPS server block only — never on port 80, since HSTS over plain HTTP is ignored by browsers. Keep the always parameter: without it, Nginx drops the headers on error responses. And watch one quirk — if any location block has its own add_header lines, Nginx silently drops the server-level ones for that location. Check there first if a page is missing headers.

After editing, test with nginx -t and reload with nginx -s reload. A failed test leaves the old config running, so always test first.

Method 3: Add Security Headers with a Small Code Snippet (mu-plugin)

No server access, or your host locks down .htaccess? A tiny must-use plugin does the job with the send_headers hook. Create security-headers.php inside wp-content/mu-plugins/ (create the folder if it does not exist) and paste this:

<?php
/**
 * Plugin Name: Security Headers (No Plugin Needed)
 * Description: Adds HTTP security headers via the send_headers hook.
 */

add_action( 'send_headers', function () {
    if ( is_ssl() ) {
        // HSTS must only be sent over HTTPS
        header( 'Strict-Transport-Security: max-age=31536000; includeSubDomains' );
    }
    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=()' );
    header( "Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'" );
} );

Must-use plugins load automatically — no activation screen, no settings page. But know its one real limit: PHP only covers pages WordPress itself renders, not static files. Images, CSS, and JavaScript are served directly by the web server, so they will not carry these headers. For full coverage, the server methods above are better. The mu-plugin is the right choice when server config is not an option — not the first choice when it is.

Key takeaway: the code method works everywhere, but it cannot protect static files — use the server method when you can.

Option 4: Cloudflare Transform Rules (the CDN Shortcut)

If your site already sits behind Cloudflare’s free plan, add all six headers without touching your server. Go to Rules → Transform Rules → Modify Response Header, create one rule per header, and set each to “Set static” with the same values from the table above.

It is the easiest method to undo — delete the rule and the header is gone — and it covers every response Cloudflare serves, including cached pages and static files. Remember the golden rule: if Cloudflare adds the headers, your server must not add them too. Pick one layer, and keep it there.

For the official reference on how each header works, see Mozilla’s MDN documentation on Strict-Transport-Security, and Cloudflare documents Transform Rules in its developer docs.

The Two Mistakes That Break Sites

Most security header guides skip this part. These two mistakes cause more broken sites than any attack:

1. Never set the same header in two layers. If your host already adds HSTS and you add it again in .htaccess, visitors receive it twice. Browsers handle duplicates unpredictably, and two Content-Security-Policy headers are both enforced — so the effective policy becomes stricter than either one alone, and your embedded videos or payment buttons can stop working. Scan your site first (see below) and check what your host or CDN already sends.

2. Never enable HSTS before HTTPS works everywhere. HSTS tells browsers to refuse plain HTTP for your domain for a full year. If any page, subdomain, or embedded resource still needs HTTP — a staging subdomain without a certificate, an old image hotlinked over HTTP — those visitors will see an error you cannot remotely undo until the max-age expires. Roll it out in stages: get a valid certificate, force HTTPS everywhere, test for a week, start HSTS at max-age=86400 (one day), then raise it to 31536000 (one year). Do not add includeSubDomains until every subdomain serves HTTPS, and do not touch preload — removal from the browser preload list takes months.

Key takeaway: duplicates break things silently, and HSTS is a one-year commitment — roll it out in stages, never all at once.

How to Test WordPress Security Headers (Verify Your Score)

Adding headers without testing them is guessing. Run these two free checks after every change:

  1. securityheaders.com — enter your domain for a letter grade from A+ to F, plus a line-by-line breakdown of every header found and missed. Aim for an A; A+ needs a strict Content-Security-Policy, which most WordPress sites should not chase on day one.
  2. Mozilla Observatory — the deeper test. It grades your headers and TLS setup together, and its report explains exactly what to fix next.

If a header you added does not show up, check three causes: a cached page (clear your cache plugin and CDN cache first), the header set in the wrong layer, or a duplicate the scanner is merging. You can also verify in your browser: open DevTools, go to the Network tab, reload, and click the main document to inspect its response headers.

Headers are the browser-facing layer — pair them with limiting login attempts without a plugin so brute-force bots face a locked door and a hardened browser.

Frequently Asked Questions

What are security headers in WordPress?

Security headers are HTTP response headers your server sends with every page, telling visitors’ browsers how to handle your site safely. WordPress does not set most of them by default, so you add them through .htaccess, Nginx, a small code snippet, or your CDN. They protect against clickjacking, MIME sniffing, cross-site scripting, and HTTPS downgrade attacks — without changing anything your visitors see.

How do I add security headers to WordPress without a plugin?

Pick one layer and add the six headers there. On Apache or shared hosting, paste the Header always set block into .htaccess above the # BEGIN WordPress section. On Nginx, add add_header ... always; lines to your HTTPS server block. With no server access, create a must-use plugin in wp-content/mu-plugins/ using the send_headers hook. If you use Cloudflare, use Transform Rules instead. Never set the same header in two places.

Which security headers should I use?

Use six: Strict-Transport-Security (HSTS), X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and a light Content-Security-Policy. These cover the attacks that actually hit small WordPress sites. Skip outdated headers like X-XSS-Protection — modern browsers have retired it, and it can create vulnerabilities of its own.

How do I test security headers?

Scan your site at securityheaders.com for a quick letter grade, then run the deeper Mozilla Observatory check for a full report. You can also verify manually in your browser’s DevTools under the Network tab. Always clear your cache plugin and CDN cache before testing, or you will scan a stale copy.

Will security headers break my site?

The five non-CSP headers almost never break anything — they are safe to add today. Content-Security-Policy is the one that can break embedded videos, fonts, or third-party scripts if it is too strict, which is why this guide uses a light policy with 'unsafe-inline' for scripts and styles. If something stops loading, switch it to Content-Security-Policy-Report-Only, watch the browser console for blocked resources, and adjust the policy before enforcing it.

Conclusion

You now know how to add security headers to WordPress without a plugin — the six headers, the copy-paste code for .htaccess, Nginx, and a mu-plugin, and the Cloudflare shortcut for good measure. The whole job takes ten minutes, and the payoff is a site that actively defends its own visitors instead of hoping for the best.

Add them today, scan your site tonight, and fix the two mistakes before they fix you: one layer only, and HSTS only after HTTPS works everywhere. A site with an A grade on securityheaders.com is not just safer — it tells Google, and every visitor, that its owner takes security seriously. That is a ten-minute job worth doing right.

Need help hardening the rest of your site? See what we do or get in touch — we handle WordPress security setup for small businesses, headers included.

Leave a Comment