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.

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.

In this post, we will go through the most important Security Headers and see how to add them to your site, whether you are running an Apache server or an NGINX server.

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.

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.

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=()"
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;

The empty parentheses () disable each feature entirely. 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=()"
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>

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.

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