search

How to Add and Configure Security Headers on WordPress

Security Headers in WordPress add an extra layer of protection against common attacks without requiring changes to your application’s code.

When it comes to securing websites or web applications, there are several aspects to consider. A good place to start strengthening the security of WordPress sites is by adding Security Headers. In my experience, missing security headers are one of the most common findings in WordPress pentest reports – and one of the easiest to fix.

What Are Security Headers in WordPress?

Security Headers are HTTP response headers that instruct the browser how to behave when handling your site’s content. They help prevent various attacks – including clickjacking, cross-site scripting (XSS), and protocol downgrade attacks – by restricting what the browser is allowed to do.

Below are the most important Security Headers with configuration examples for both Apache and NGINX servers.

It is important to note that the settings presented in this post may not be suitable for every site. You should test each header individually and check for console errors after every change.

Incorrect Security Header configuration can cause a 500 error or break parts of your site. Always back up your .htaccess or nginx.conf file before making changes, and make sure you have FTP or file manager access so you can revert if the site goes down.

For a deeper reference on each header and its parameters, see the MDN Web Docs on HTTP Security Headers.

Recommended Security Headers to Add

Here are the recommended Security Headers for WordPress sites. Add them one at a time and test after each change.

A. HTTP Strict Transport Security (HSTS)

HTTP Strict Transport Security (HSTS) tells browsers to only connect to your site over HTTPS, never over plain HTTP. This prevents protocol downgrade attacks and cookie hijacking.

Your site must already have a valid SSL certificate and be fully working on HTTPS before enabling HSTS.

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

The max-age=31536000 value means the browser will remember this policy for one year. The includeSubDomains directive applies the policy to all subdomains as well – only use this if all your subdomains support HTTPS.

If you are setting up HSTS for the first time, start with a short max-age (e.g., 300 for 5 minutes) and gradually increase it once you confirm everything works.

B. X-Frame-Options

X-Frame-Options protects users from clickjacking attacks. Without it, an attacker could load your site inside an iframe on their own page and trick users into clicking on hidden elements.

This is particularly dangerous when the user is logged into an area on your site that requires authentication.

<IfModule mod_headers.c>
Header always append X-Frame-Options SAMEORIGIN
</IfModule>
add_header X-Frame-Options "SAMEORIGIN" always;

The SAMEORIGIN value allows your own site to embed itself in iframes (useful for previews in the WordPress admin) but blocks other domains from doing so.

Note that X-Frame-Options is being superseded by the frame-ancestors directive in Content-Security-Policy (covered in section D below). Modern browsers prioritize frame-ancestors over X-Frame-Options. For maximum compatibility, use both – the CSP example below already includes frame-ancestors 'self'.

C. X-Content-Type-Options

Setting the X-Content-Type-Options header prevents the browser from guessing (or “sniffing”) the MIME type of a file. Without it, a browser might interpret a malicious file as JavaScript or HTML.

Header set X-Content-Type-Options nosniff
add_header X-Content-Type-Options "nosniff" always;

This is one of the simplest headers to add and has virtually no risk of breaking your site.

D. Content-Security-Policy

The Content Security Policy (CSP) header is the most powerful – and the most complex – security header. It tells the browser exactly which sources are allowed to load scripts, styles, images, fonts, and other resources.

A strict CSP can effectively prevent XSS attacks. However, WordPress themes and plugins rely heavily on inline scripts and styles, which makes a strict policy difficult to implement without breaking functionality.

A realistic starting point for most WordPress sites:

Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' https: data:; font-src 'self' https: data:; connect-src 'self' https:; frame-ancestors 'self';"
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' https: data:; font-src 'self' https: data:; connect-src 'self' https:; frame-ancestors 'self';" always;

The unsafe-inline and unsafe-eval directives weaken CSP protection, but they are currently required for most WordPress sites to function. WordPress core, Gutenberg, and many plugins depend on inline scripts. Removing these directives will likely break your site. Use them as a practical compromise and tighten the policy over time as WordPress improves its CSP support.

WordPress core has been making progress toward strict CSP. As of WordPress 6.4+, the manual construction of script tags was eliminated from WP_Scripts on frontend and login screens (Trac #58664), making nonce-based CSP more feasible. If you want to experiment with a stricter policy on the frontend, the Strict CSP plugin can enforce nonce-based CSP without unsafe-inline. The admin area still requires unsafe-inline for now.

If you want to test your CSP before enforcing it, use Content-Security-Policy-Report-Only instead. This logs violations in the browser console without blocking anything, so you can see what would break.

E. Referrer-Policy

The Referrer-Policy header controls how much referrer information the browser sends when navigating from your site to another.

Header set Referrer-Policy "strict-origin-when-cross-origin"
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

The strict-origin-when-cross-origin value sends the full URL for same-origin requests but only the origin (domain) for cross-origin requests. This is a good balance between privacy and functionality – it preserves analytics data while not leaking full page paths to external sites.

F. Permissions-Policy

The Permissions-Policy header (formerly Feature-Policy) controls which browser features and APIs your site can use, such as the camera, microphone, geolocation, and payment APIs.

Most WordPress sites do not need access to these features, so disabling them reduces your attack surface:

Header set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), browsing-topics=()"
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), browsing-topics=()" always;

The empty parentheses () disable each feature entirely. The browsing-topics=() directive opts your site out of Google’s Topics API (the replacement for the deprecated FLoC interest-cohort feature). If your site uses any of these features (e.g., a store locator that needs geolocation), adjust accordingly: geolocation=(self).

G. X-XSS-Protection (Deprecated)

The X-XSS-Protection header was designed to activate the browser’s built-in XSS filter. However, this header is now deprecated and has been removed from all modern browsers (Chrome removed it in version 78, Firefox never supported it, and Edge removed it in version 17).

The XSS filter was found to have security flaws of its own – it could be exploited to create cross-site information leaks. The recommended approach is to either omit this header entirely or explicitly disable it:

Header set X-XSS-Protection "0"
add_header X-XSS-Protection "0" always;

Use Content-Security-Policy instead for XSS protection. If you previously had X-XSS-Protection: 1; mode=block in your configuration, it is safe to remove it.

Complete Configuration Example

Here is a complete .htaccess configuration with all recommended headers:

<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" env=HTTPS
Header set X-Content-Type-Options nosniff
Header always append X-Frame-Options SAMEORIGIN
Header set Referrer-Policy "strict-origin-when-cross-origin"
Header set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), browsing-topics=()"
Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' https: data:; font-src 'self' https: data:; connect-src 'self' https:; frame-ancestors 'self';"
</IfModule>

If you encounter a 500 error after adding these headers, try a minimal configuration first and add headers one by one to identify which one causes the issue:

<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000" env=HTTPS
Header set X-Content-Type-Options nosniff
Header always append X-Frame-Options SAMEORIGIN
Header set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>

H. Cross-Origin Headers (Advanced)

Three newer headers help isolate your site from cross-origin attacks like Spectre:

Cross-Origin-Opener-Policy (COOP) prevents other sites from gaining a reference to your window object through popups. Cross-Origin-Embedder-Policy (COEP) ensures all resources loaded by your page explicitly opt into being loaded. Cross-Origin-Resource-Policy (CORP) controls which origins can load your resources.

Header set Cross-Origin-Opener-Policy "same-origin"
Header set Cross-Origin-Resource-Policy "same-origin"
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;

These headers can break cross-origin embeds, third-party fonts, and CDN-loaded resources. Most WordPress sites should skip COEP and only add COOP and CORP if they do not rely on cross-origin resources. Test thoroughly before deploying.

Checking Your Security Headers

After adding the headers, verify they are working correctly. You can test your site on securityheaders.com to get a grade and see which headers are present.

Checking Security Headers

You can also check headers in your browser’s Developer Tools. Open the Network tab, click on the main document request, and look at the Response Headers section to confirm your headers are being sent.

FAQs

Common questions about WordPress security headers:

Can security headers break my WordPress site?
Yes. An incorrect Content-Security-Policy can block scripts, styles, or images that your site needs. A malformed .htaccess entry can cause a 500 error. Always add headers one at a time, test after each change, and keep a backup of your original configuration file so you can revert quickly.
Why does my CSP need unsafe-inline and unsafe-eval?
WordPress core, the Gutenberg editor, and most plugins rely on inline scripts and dynamically generated code. Removing unsafe-inline and unsafe-eval from your CSP will break the WordPress admin and likely your front-end as well. These directives are a practical compromise until WordPress core improves its CSP compatibility.
Is X-XSS-Protection still useful?
No. The X-XSS-Protection header has been removed from all modern browsers. Chrome removed it in version 78 (2019), Firefox never supported it, and Edge dropped it in version 17. The browser's built-in XSS filter was found to have security flaws of its own. Use Content-Security-Policy for XSS protection instead.
Do I need HSTS if I already have an SSL certificate?
Yes. An SSL certificate encrypts the connection, but without HSTS a browser might still attempt an initial HTTP connection before redirecting to HTTPS. That first unencrypted request is vulnerable to man-in-the-middle attacks. HSTS tells the browser to skip HTTP entirely and always connect over HTTPS.
Should I use includeSubDomains in my HSTS header?
Only if all your subdomains support HTTPS. The includeSubDomains directive forces HTTPS on every subdomain, including internal tools or staging environments. If any subdomain does not have a valid SSL certificate, it will become inaccessible. Verify all subdomains work on HTTPS before enabling this.
Can I add security headers using a WordPress plugin instead?
Yes. Plugins like "Headers Security Advanced and HSTS WP" or "Really Simple SSL" can configure security headers from the WordPress admin. However, adding headers at the server level (via .htaccess or nginx.conf) is more reliable and performs better since the headers are set before WordPress even loads.

Summary

Security headers are one of the easiest ways to improve your WordPress site’s security posture. Start with the low-risk headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy), then add HSTS once you are confident your HTTPS setup is solid, and finally work on Content-Security-Policy and Permissions-Policy.

The key rule: add one header at a time, test, and keep a backup. If something breaks, you can always revert.

Security headers are just one layer of a solid WordPress security setup. Consider enabling two-factor authentication, securing the REST API, and disabling XML-RPC as additional hardening steps. Note that misconfigured headers like X-Robots-Tag can block AI crawlers too – the AI Visibility Audit checks for this.

If you have questions or want to suggest improvements, feel free to leave a comment below.

Join the Discussion
1 Comments  ]
  • Brian 3 June 2025, 18:51

    Thanks! Very helpful

Leave a Comment

To add code, use the buttons below. For instance, click the PHP button to insert PHP code within the shortcode. If you notice any typos, please let us know!

Savvy WordPress Development official logo