Protect your Wordpress users with HTTP Security Headers

If you find any issues with the instructions then do not hesitate to create an issue here
This document is under European Union Public License 1.2 (EUPL-1.2).

Changelog

DateDescription
2024-12-06Remove navigate-to as it is not anymore part of the CSPv3 spec
2024-06-07Updated CSP to include upgrade-insecure-requests, because block-all-mixed-content is deprecated.
2024-04-18Fix recommended value for Cross Origin Resource Policy (CORP)
2024-04-09Initial tutorial with Content-Security Policy (CSP), Cross-Origin Policies, Referrer Policy, HTTP Strict Transport Security (HSTS), Permission Policy and deprecated HTTP security headers

Introduction

Websites are the prime target of many attacks as they can be reached via the Internet by everyone and by definition a website should be accessible to as many people as possible. The Open Worldwide Application Security Project (OWASP) identifies regularly the top 10 web application security risks.
We will describe here how we can with simple means (additional HTTP headers) improve the security of your wordpress site for your content creators and content consumers significantly.
hese
Note: Not all of the security related risks for the given categories are covered. However, the additional headers provide a simple and good additional layer of defense, ie you should have for your website multiple layers of defense.
You are responsible for the security of your website! Use this tutorial as guidance, but always try to make it better than described here and keep in mind that software evolves and you need to regularly review if your security is still up-to-date.

A bit of history

While it was always possible to add HTTP security headers to Wordpress, it was a bit limited for Content-Security-Policies (CSPs), because you needed to allow "unsafe-eval" to be able to use the Wordpress block editor (Gutenberg) which is a significant security risk (Nevertheless: Setting any CSP header is much better than setting none!).
Luckily since Wordpress 6.5 you can use even more secure CSPs as well - although they still contain "unsafe-inline" they can be still a lot more secure now.
Interestingly the way to get there took a bit longer. A couple of years ago some people that wanted to use a CSP without "unsafe-eval" discovered that a CSP without "unsafe-eval" renders the Wordpress block editor useless. Surprisingly it took quiet some time to figure out why, because eval was not used in the Wordpress block editor (Gutenberg). However a Function object was used, which is apparantly less known in the Javascript world, but as problematic from a security point as eval. The reason why it was used is because React and a third party statement was very slow at this time and the insecure Function promised significant performance benefits. Luckily this is not needed anymore and a simple change not using Function objects or eval introduced not only a more secure block editor, but also did not show any performance penalty.
As said, the use of unsafe-inline is still needed, but this will also be addressed in the next releases.
In any case - even if you are on older Wordpress versions: Always define a content security policy - it is much better than having none.
If you are on newer releases - always check if you can harden your HTTP security headers even more - every new release allows you to make your Wordpress site more secure for yourself and other users.

Prerequisites

As said, you should use secure HTTP headers with any Wordpress version. We show here one that work with at least Wordpress 6.5, for older versions you will need to adapt them a bit.
Additionally, you should test if they work with third party Wordpress plugins. If they do not work then please create issues for the plugin developers to update their plugins to make them safer to user for everyone.

How to add http response headers to your Wordpress site

Before we describe what HTTP security headers you can add, we explain how they can be added. In fact there are many different ways and you should ask your Wordpress hoster what is the best way.
We describe here one way: Using a .htaccess file that you need to put in the document root of your web server document root.
Find here an example how to set the response header "Referrer-Policy" to "no-referrer":

Of course you can define multiple headers in the same block.
Once you have updated the file you can use the developer tools of your browser to check if it has been really set. For example, in Firefox you can use the web developer tools.
Once you have opened them, navigate to the address of your wordpress site and select in the developer tools the tab "Network":

Choose a http response with the code 200 on the left and then check on the right for the response header that you defined. It is crucial that you always test and check that the configured HTTP security headers are returned - otherwise you have no protection.
Now we check what other HTTP security headers you can add in subsequent sections.

Content-Security Policies (CSP)

Content-Security Policy (CSP) (see also here) have become in recent years very important to secure the user in the browser for your website.
They have also evolved over the years so it is always worth to check for new security features.
Essentially content-security-policies define from which origins scripts, stylesheets and other objects can be loaded. If the browser detects a non-compliant origin it will simply not be loaded and thus reducing the risk that malicious code will be loaded from other origins significantly.
One example CSP that works with Wordpress 6.5 can be found here:

You can find more information what the element means under the previous links. You will notice that there is "unsafe-inline" mentioned. While this is indeed not as secure as it could be, the policy still introduces still a significant amount of additonal security. Additionally, the Wordpress team works on also removing the need for unsafe-inline (see e.g. here).
You can check the policy if it works as described above.
You will also notice one other thing: The browser will not load anymore avatars from gravatar.com:

Furthermore, you will see in the browser console also that this image is not allowed by CSP:

In my case this is desired (the CSP specifies that images only from the own website can be loaded). In case you do want to allow images from gravatar.com you need to update the img-src part of the above policy to "img-src 'self' data: https://secure.gravatar.com"
Generally it is a good security practice to not allow resources from other sites to be included in the website. This is also recommended for data privacy protection of your user.
The content security policy is much more powerful and you can try additional settings to make your website even more secure. Find more information under:

Cross-Origin Policies

Cross-origin Policies are a more recent type of HTTP security header. There are current the following ones including a recommended restricted one:

NameDescriptionRecommended Value
Cross Origin Embedder Policy (COEP)Defines which resources the document can include from other origins
Cross Origin Opener Policy (COOP)Avoid that you share the browsing context with a potentially hostile origin
Cross Origin Resource Policy (CORP)This will protect against side-channel attacks like Spectre which can easily even in protected environments leak confidential data

Referrer Policy

Describes how much information is provided to the website of the link from where the link was clicked. No-referrer means no information.
Note: Especially if your URL contains authentication information (which it must never) then it is key that this is not leaked to another website when a user clicks on a link in your website.
Recommended Value:

More information in the Mozilla developer documentation.

HTTP Strict Transport Security (HSTS)

The HTTP Strict Transport Security (HSTS) response header instructs the browser to access the website now and in the future only through HTTPS. This prevents man-in-the-middle attacks and malicious redirects. Requires that the browser was able to at least once connect successfully through HTTPS.
You can set how long the policy is valid (recommendation: 2 years). Additionally, you should configure that it is ensured for all subdomains.

Permission Policy

The Permission Policy allows to enable/disable certain browser features and APIs. The more you disable the better your website and your users are protected as the attack surface is reduced significantly. Furthermore, it can increase data privacy as features/APIs cannot be used for device fingerprint.
You should regularly review the permission policy as browsers add frequently new features and API.
The following policy disables a lot of features/APIs:

You can also restrict usage of features/APIs for specific origins (e.g. self, other domains).
You can also use a Permission Policy Generator, but please make sure that they are correct using the method described in a previous chapter.

Deprecated HTTP security headers

We will present now some deprecated HTTP security headers. Although deprecated, you should configure them IN ADDITION to the previous security headers to have at least some security if people use outdated software/devices.

NameDescriptionRecommended Value
X-Content-Type-OptionsInstructs the browser not to guess mime-types, but uses the one that are specified in the headers.
X-Frame-OptionsInstructs the browser that the website should not be embedded into a frame of another website
X-XSS-ProtectionProtects against some cross-site-scripting attacks.