Security headers are HTTP response headers that tell browsers to activate built-in security features. Most WordPress sites are missing several of these headers by default because neither WordPress core nor most hosting providers set them automatically. The good news is that adding them takes only a few minutes, and the security improvement is significant. This guide covers every important security header, explains what each one does, and shows you how to add them all at once.
Which Security Headers Should Every WordPress Site Have?
Here is the complete list of recommended security headers with their values and what they protect against:
- X-Frame-Options: SAMEORIGIN: prevents your site from being loaded inside an iframe on another domain. This stops clickjacking attacks where a malicious site overlays invisible buttons on top of your page.
- X-Content-Type-Options: nosniff: stops browsers from guessing the MIME type of a file. Without this, an attacker could upload a file that looks like an image but contains JavaScript, and the browser might execute it.
- Referrer-Policy: strict-origin-when-cross-origin: controls how much URL information is shared when users click links to other sites. This setting sends the full URL for same-origin requests but only the domain for cross-origin ones, protecting sensitive URL parameters.
- Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(): disables browser features your site does not use. The empty parentheses mean "nobody is allowed to use this feature," which prevents malicious scripts from accessing the camera, microphone, or location.
- Strict-Transport-Security: max-age=31536000; includeSubDomains: forces browsers to always use HTTPS. See our dedicated HSTS guide for details on safe rollout.
- X-XSS-Protection: 1; mode=block: a legacy header for older browsers that enables the built-in XSS filter. Modern browsers have deprecated this in favor of CSP, but it does not hurt to include it for backward compatibility.
- Content-Security-Policy: the most powerful (and complex) security header. See our dedicated CSP guide for a complete walkthrough.
How to Prioritize: Which Headers to Add First
If you are adding security headers for the first time, here is the order that gives you the biggest security improvement with the least risk:
- X-Content-Type-Options: zero risk of breaking anything. Just add it.
- X-Frame-Options: safe unless you intentionally embed your site in iframes on other domains (unlikely for most WordPress sites).
- Referrer-Policy: very low risk. The recommended value
strict-origin-when-cross-originis already the default behavior in modern browsers. - Permissions-Policy: low risk unless your site actually uses the camera, microphone, or geolocation APIs.
- X-XSS-Protection: legacy header, no risk at all.
- Strict-Transport-Security: requires working HTTPS first. Follow a gradual rollout (see our HSTS guide).
- Content-Security-Policy: highest risk of breaking things on WordPress. Always start with Report-Only mode (see our CSP guide).
Adding All Headers via Apache .htaccess
This is the most common method for WordPress sites on shared hosting. Open the .htaccess file in your WordPress root directory and add the following block. Place it before the # BEGIN WordPress section to keep things organized:
<IfModule mod_headers.c>
# Prevent clickjacking
Header always set X-Frame-Options "SAMEORIGIN"
# Prevent MIME-type sniffing
Header always set X-Content-Type-Options "nosniff"
# Control referrer information
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Restrict browser features
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
# Force HTTPS (only add if SSL is fully working)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# XSS Protection for legacy browsers
Header always set X-XSS-Protection "1; mode=block"
</IfModule>A few things to keep in mind:
- The
IfModulewrapper ensures Apache does not throw a 500 error ifmod_headersis not enabled on your server. - Using
Header always set(instead of justHeader set) ensures the headers are sent on all responses, including error pages. - If you already have a
<IfModule mod_headers.c>block in your.htaccess, add the individualHeaderlines inside that existing block instead of creating a duplicate.
Adding All Headers via Nginx
If your WordPress site runs on Nginx (common with managed WordPress hosts like Kinsta, Cloudways, GridPane, or SpinupWP), add these lines inside your server block:
# Security Headers
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=(), payment=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-XSS-Protection "1; mode=block" always;Important Nginx-specific notes:
- The
alwayskeyword sends the header on all response codes (including 4xx and 5xx). Without it, Nginx only sends headers on 2xx responses. - If you have a separate
locationblock that handles PHP (e.g.,location ~ .php$), you may need to add the headers there as well, depending on your Nginx configuration. Nginx does not inheritadd_headerdirectives from parent blocks if the child block has its ownadd_header. - After making changes, always test and reload:
sudo nginx -t && sudo systemctl reload nginx.
Adding Headers via PHP in WordPress
If you cannot access server configuration files, you can send security headers from PHP. Add this to your theme's functions.php or to a custom site-specific plugin:
function iwp_add_security_headers() {
if ( is_admin() ) {
return;
}
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=(), payment=()' );
header( 'X-XSS-Protection: 1; mode=block' );
}
add_action( 'send_headers', 'iwp_add_security_headers' );The is_admin() check skips the WordPress admin area to avoid potential conflicts with admin functionality. Note that this PHP approach has a limitation: it only applies to pages that WordPress processes. Static files (images, CSS, JS, fonts) served directly by your web server will not carry these headers. For complete coverage, server-level configuration is always the better choice.
WordPress Security Header Plugins
If you prefer a plugin-based approach, these options provide a user-friendly interface for managing security headers:
- HTTP Headers: a free plugin with a comprehensive UI for setting all security headers. It supports X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, and more. You configure everything from the WordPress admin without touching any files.
- Really Simple SSL Pro: the premium version includes a security headers module that lets you toggle individual headers on or off. It also provides recommendations and explains each header.
- Headers Security Advanced & HSTS WP: a focused plugin specifically for security headers. It covers all major headers and includes presets for common configurations.
- Jeremykendall's Security Headers: a lightweight option that adds all recommended headers with sensible defaults.
While plugins are convenient, they come with trade-offs. If the plugin is deactivated, updated with a bug, or removed, your security headers disappear instantly. For production sites, server-level configuration is more reliable because it does not depend on WordPress or any plugin being active.
Testing Your Headers with Online Tools
After adding your headers, verify them with these free tools:
- securityheaders.com: enter your URL and get an A+ to F grade along with a detailed breakdown of which headers are present and which are missing. Aim for at least an A grade.
- observatory.mozilla.org: Mozilla's tool performs a more comprehensive analysis, including CSP evaluation and cookie security.
- Browser DevTools: press F12, go to the Network tab, click the main document request, and scroll to Response Headers to see exactly what your server sends.
You can also check from the command line:
curl -sI https://example.comThis shows all response headers, including your newly added security headers.
CDN and Reverse Proxy Considerations
If your WordPress site sits behind a CDN like Cloudflare, Sucuri, or Fastly, be aware that the CDN can strip, override, or add its own headers. Here is what you need to know:
- Cloudflare: does not strip security headers by default, so headers set on your origin server will pass through. Cloudflare also offers its own security headers settings in the dashboard under SSL/TLS > Edge Certificates (for HSTS) and in managed rules.
- Sucuri: their firewall may add some security headers automatically. Check your Sucuri dashboard settings to see which ones are active and avoid duplicates.
- Fastly / KeyCDN / BunnyCDN: most CDNs allow you to add custom response headers in their configuration panel. This can be a good alternative if you cannot modify server config on your origin.
If you see duplicate headers in your response (e.g., two X-Frame-Options lines), this can cause unpredictable browser behavior. Make sure each header is set in only one place: either on your origin server or at the CDN level, not both.
Common Mistakes When Adding Security Headers
- Duplicate IfModule blocks: if your
.htaccessalready has a<IfModule mod_headers.c>block (perhaps from a plugin), adding a second one can cause conflicts. Merge your headers into the existing block. - Setting headers only via PHP: this misses all static files. Use server-level configuration for complete coverage.
- Overriding headers in location blocks (Nginx): if a child
locationblock has anyadd_header, it replaces all headers from the parent block. You need to repeat all headers in each location block that defines its own. - Not testing after plugin updates: a plugin update or theme switch can remove your PHP-based headers. Always re-test after major changes.
- Forgetting about subdomains: if you have subdomains (staging, mail, etc.), make sure they also have security headers configured. Attackers often target the weakest subdomain.
Verify Your Security Headers with InspectWP
After adding all headers, run a new InspectWP scan on your WordPress site. The security section of your report lists every security header and shows its status. Each header that is present and correctly configured will appear as green. Missing headers show up as red or yellow, depending on their importance. Use InspectWP's automatic report feature to monitor your headers over time and catch any regressions caused by server changes or plugin updates.