← Back to blog

WordPress Security Headers: Copy Ready .htaccess and nginx Snippets

September 30, 2026
WordPress Security Headers: Copy Ready .htaccess and nginx Snippets

Set the headers at the server level when you have access to the config files. If you don't, use a plugin or code snippet, but confirm your caching layer still passes those headers through. Start Content-Security-Policy in report-only mode, keep HSTS conservative at first, and test every page type before you trust any of it.


TL;DR:

  • Server-level configuration is the most reliable way to set security headers, but plugins or code snippets can suffice when access is limited, provided caching does not strip them.
  • Starting with a conservative Content-Security-Policy in report-only mode and low HSTS max-age helps prevent site breakage during rollout, especially for WordPress sites.
  • HSTS should include subdomains and avoid the preload list until all subdomains are HTTPS-enabled and thoroughly tested to prevent long rollback delays.
  • Caching layers often serve static snapshots that bypass PHP code adding headers, making server configuration essential for consistent security header deployment.
  • Use external tools and browser dev tools to verify headers after deployment, especially on error pages and pages with caching, to ensure proper implementation.

inSave Hosting
Strengthen Your WordPress Hosting
InSave Hosting supports secure WordPress sites with free SSL certificates, managed security features, and performance-focused hosting technologies.
Explore WordPress hosting

Table of Contents

Why HTTPS alone is not enough for browser-level protection

HTTPS encrypts the connection between the browser and your server, but it does nothing once the page loads. A site can run on HTTPS and still let another domain load it inside a hidden frame, run an injected script, or misread a file's content type. Security headers close those gaps by telling the browser how to behave once the page arrives.

Each header handles a different threat:

  • HSTS forces HTTPS on every future visit, closing the window for downgrade attacks.
  • Content-Security-Policy restricts which scripts and resources can run, limiting cross-site scripting.
  • X-Content-Type-Options stops the browser from guessing a file's type, which blocks MIME-sniffing tricks.
  • Frame-ancestors (or X-Frame-Options) stops other sites from embedding yours in a frame, which prevents clickjacking.
  • Referrer-Policy controls how much of your URL gets leaked to other sites.
  • Permissions-Policy limits which browser features, like camera or geolocation, a page can use.

Get any of these too strict and you'll see it fast: blocked analytics scripts, broken payment iframes, or a checkout page that silently stops working. That's exactly why the rollout has to be gradual.

A WordPress site doesn't need an exotic header list, just a consistent one. Here's a practical starting point, tuned for testing rather than lockdown:

HeaderPurposeConservative starting value
Strict-Transport-SecurityForces HTTPS on repeat visitsconservative max-age
Content-Security-Policy-Report-OnlyLogs violations without blocking anythingdefault-src 'self'; report-uri /csp-report
X-Content-Type-OptionsBlocks MIME-type guessingnosniff
Referrer-PolicyLimits referrer data sent to other sitesstrict-origin-when-cross-origin
Permissions-PolicyRestricts browser features by defaultgeolocation=(), camera=()
Content-Security-Policy (frame-ancestors)Prevents clickjacking via framingframe-ancestors 'self'

HSTS deserves particular caution. Adding includeSubDomains applies the rule to every subdomain, and preload submits your domain to a list browsers check before even requesting the page. According to MDN's documentation on the header, preload eligibility requires includeSubDomains and a max-age of at least 31,536,000 seconds, and it only works when HSTS is sent over HTTPS since browsers ignore it over plain HTTP. Don't add preload until every subdomain is confirmed working on HTTPS, because reversing it can take a long time to propagate.

For CSP, MDN recommends starting in report-only mode so you can see what the policy would have blocked without actually blocking anything, which matters on a WordPress site full of plugins, embeds, and third-party scripts.

How to add headers at the server level: Apache .htaccess examples

If your WordPress install runs on Apache, and most shared hosting does, the .htaccess file is usually the most reliable place to set headers. Using Header always set matters because it applies the header to every response, including 4xx and 5xx error pages, not just successful ones. One breakdown of API security headers points out that this is the main reason server-level configuration beats application-level code: it covers static files, redirects, and error templates that PHP never touches.

A basic block looks like this:

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=86400"
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "geolocation=(), camera=()"
  Header always set Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report"
</IfModule>

Before you paste anything in:

  • Confirm mod_headers is enabled. Most hosts have it on by default, but shared environments sometimes restrict it.
  • Back up the existing .htaccess file so you can revert in one step if something breaks.
  • Deploy through staging first, or through your host's file manager if you don't have shell access.

Pro Tip: Run curl -I yourdomain.com right after deploying, then check a 404 page, a redirect, and a cached article page separately, since each can behave differently.

How to add headers in nginx: add_header examples and placement

On nginx, headers usually go inside the server block or an include file your host manages. The always flag matters here too: without it, nginx only sends the header on certain response codes, which quietly excludes error pages.

A typical set of directives:

  • add_header Strict-Transport-Security "max-age=86400" always;
  • add_header X-Content-Type-Options "nosniff" always;
  • add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  • add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;

Two common pitfalls trip people up. First, add_header doesn't inherit the way you'd expect: if you set headers in the server block and then add more in a location block, the location block's headers can replace the parent set entirely rather than adding to it. Second, if a CDN or reverse proxy sits in front of nginx, it can strip or overwrite headers before they reach the browser, so verify at the edge, not just at the origin. After reloading nginx, run the same check across a normal page and an error page to confirm nothing got dropped.

Application-level methods for WordPress: plugins, wp_headers, and caching caveats

When you don't have server access, a plugin or a small code snippet using the wp_headers filter or a header() call in your theme's functions file can add most of these headers. Look for a plugin that shows the actual raw header values it's sending, lets you export or import your configuration, and makes clear which headers it owns versus which ones your host or CDN might also be setting. A roundup of WooCommerce security plugin options covers some of the tradeoffs between lightweight header tools and fuller security suites.

The catch is caching. Here's the sequence that trips most people up:

  1. A plugin or PHP snippet runs on each request and adds headers dynamically.
  2. A caching plugin or CDN then serves a static HTML snapshot for repeat visits.
  3. That cached snapshot never runs PHP again, so the headers never get attached.

This is a documented limitation: caching systems commonly serve stored HTML that bypasses the PHP execution stage entirely, which is exactly why server-level configuration is preferred when it's available. If you're relying on a caching setup, our guide to caching in hosting explains how that layer interacts with dynamic content, which is worth understanding before you assume your headers are actually reaching visitors.

Pro Tip: Check a cached page's headers separately from a freshly purged one; if they differ, your cache is the problem, not your plugin.

Rollout plan and testing checklist

A cautious rollout beats a fast one, especially with CSP and HSTS since both can lock users out of features or force HTTPS somewhere it isn't ready.

  1. Deploy on staging first, with Content-Security-Policy set to report-only, and let it run long enough to surface real traffic patterns.
  2. Review the violation reports and add any legitimate script, style, or embed sources to the policy before moving to production.
  3. Keep HSTS max-age low during testing and hold off on preload, since MDN notes preload eligibility needs includeSubDomains and a minimum max-age of 31,536,000, a setting you don't want live until every subdomain is confirmed on HTTPS.
  4. Verify headers with curl -I, browser DevTools' network tab, and an external header scanner, checking the homepage, an article page, the login page, REST API responses, a redirect, and a 404 page.
  5. Document what you'd roll back and how, before you enforce anything.

Recommended header set: OWASP's HTTP Headers Cheat Sheet lays out this same staged approach and stresses that whoever owns each header (server, CDN, or application) needs to be clear, since overlapping layers can quietly override one another.

InSave Hosting perspective: operational notes from a WordPress host

Header ownership should sit wherever the traffic actually terminates first. If a CDN or nginx include sits in front of your site, that's where HSTS and frame-ancestors belong. If you're on shared hosting with no server access, the plugin route is your only option, and that's fine as long as you verify cached pages separately.

If headers aren't showing up after you've configured them, ask your host directly whether a CDN, edge cache, or reverse proxy sits between your server and visitors, since that's the layer most likely to be silently stripping them. For related groundwork, see our notes on fixing mixed content issues and our broader WordPress security hardening steps.

— Ihor

Ready for headers that stick, even behind a cache?

Application-level headers are only as good as the cache layer sitting in front of them, and on shared environments that layer isn't always something you control. InSave Hosting's WordPress hosting plans, WP Start, WP Growth, WP Pro, and WP Agency, give you staging environments to test CSP in report-only mode before anything touches production, plus support access when you need to confirm whether a header should be set at the server or the application layer. Sites are managed by SRE and DevOps engineers based in the EU and US, backed by free migrations, daily backups, and a 30-day money-back guarantee. If you're planning a header rollout and want a hosting setup that won't fight your testing process, check the WordPress hosting plans and pick the tier that matches your site's traffic.

Ready for headers that stick, even behind a cache? — overview diagram

Sources

For deeper reading, MDN's CSP guide, its Permissions-Policy documentation, and the OWASP HTTP Headers Cheat Sheet cover the syntax and rollout guidance in detail. For verification, use curl, browser DevTools, and an external scanner such as hstspreload.org before you commit to preload.

  • HTTP Headers Cheat Sheet - OWASP

FAQ

How do you set a header in WordPress?

You can add headers at the server level with .htaccess on Apache or add_header directives on nginx, which is the most reliable method since it covers error pages and redirects. If you don't have server access, a plugin or the wp_headers filter works, but confirm caching doesn't strip the header from stored pages.

Is a Content-Security-Policy header necessary?

A CSP header significantly reduces the risk of cross-site scripting by restricting which scripts and resources a page can load. MDN recommends starting in report-only mode so you can see what would break before you enforce the policy on a live site.

What are HTTP security headers?

They're response headers that tell the browser how to handle a page: whether to force HTTPS, which scripts to trust, whether another site can frame it, and how much referrer data to share. The OWASP HTTP Headers Cheat Sheet lists the commonly recommended set and how to roll each one out safely.

Which security plugin works best for adding headers in WordPress?

The right plugin depends on your setup, but look for one that shows the raw header values it sends and lets you export your configuration for backup. A comparison of WooCommerce security plugins covers the tradeoffs between lightweight header tools and fuller security suites worth checking before you pick one.

Do I need server access to add security headers?

No, but server-level configuration through .htaccess or nginx is generally more reliable since it applies to every response, including error pages and redirects that PHP-based methods can miss. Without server access, a plugin or code snippet is a workable substitute as long as you verify the headers survive your caching layer.