
🔒 HTTP security headers for WordPress: complete setup guide
Your WordPress site is running, the security plugin is installed, but a check on securityheaders.com gives you an F. Sound familiar?
HTTP security headers are an invisible layer of protection that gets overlooked even after setting up a firewall and two-factor authentication. They don't need license renewals, don't load the server, and block entire classes of attacks: from clickjacking to session hijacking. And they take 10 minutes to add.
Below, step by step: which headers you actually need, how to configure them via.htaccess, functions.php, or a plugin, and how to verify that the protection is working, not just listed.
💡 Quick overview:
- Check your site's current headers via securityheaders.com or Mozilla Observatory to see the real picture
- Enable a basic set of 4-5 headers: X-Content-Type-Options, X-Frame-Options, HSTS, Referrer-Policy, Permissions-Policy
- Choose your method: a couple of lines in.htaccess (Apache/LiteSpeed), a snippet in functions.php, or the Headers Security Advanced & HSTS WP plugin
- Leave CSP for later: it's tricky to configure and breaks Gutenberg if done without testing
- After implementation, recheck: an A+ score is achievable in 10 minutes
What HTTP security headers are
When a browser requests a page, the web server sends back more than just HTML code. Every HTTP response contains service fields, headers. Some handle caching and content type, and some handle security.
The browser reads security headers before it even starts rendering the page. If the server says "I forbid embedding this site in frames," the browser won't do it, even if the page code contains an <iframe>. If the server says "use HTTPS only for a year," the browser remembers and won't go to the HTTP version, even if the user types it manually.
No JavaScript, no plugin, no firewall affects this. This is the protocol level. That's precisely why headers are a separate, independent line of defense.
Why a WordPress site needs this
Security plugins like Wordfence or Solid Security close vulnerabilities at the application level: brute force, injections, malicious uploads. HTTP headers work one level higher: they protect the communication channel itself between the server and the visitor.
Here's what properly configured headers block:
Clickjacking. An attacker embeds your site in a transparent frame on top of their own and intercepts user clicks. Blocked by the
X-Frame-Optionsheader or theframe-ancestorsdirective in CSP.MIME sniffing. The browser tries to "guess" the file type and may execute an uploaded image as JavaScript. Blocked by
X-Content-Type-Options: nosniff.Session hijacking. An attacker on an open Wi-Fi network alters the server response and steals login cookies. HSTS forbids the browser from accessing the HTTP version of the site.
Referrer leakage. When following an external link, the destination site sees which page the user came from, including the URL with parameters. Referrer-Policy limits this.
Unauthorized device access. A web page requests the microphone, camera, or geolocation without the user's knowledge. Permissions-Policy blocks such requests at the browser level.
CSP adds another layer: you explicitly define which domains can load scripts, styles, and fonts. Even if malicious code gets into the database, the browser won't execute it because the source isn't in the allowlist. But CSP requires care: one small mistake, and the Gutenberg editor stops working.
Key HTTP security headers: what they include and why
Below are the headers worth configuring on any WordPress site. For each, we specify: which attack it protects against, the recommended value, and where things might break.
X-Content-Type-Options
The simplest and safest to configure. The only valid value is nosniff. The browser stops "guessing" the MIME type of a file based on its content and trusts only the type the server sent in the Content-Type header.
Without this header, the browser may treat a user-uploaded image as a script and execute it, a classic file-upload attack scenario.
Recommended value: nosniff
Breakage risks: minimal. Problems are possible only if the server serves files with an incorrect Content-Type, but that's a separate issue worth fixing rather than masking.
X-Frame-Options
Controls whether your site can be embedded in a <frame>, <iframe>, or <object> on another site.
Two main values:
DENY, a complete ban on embedding. Maximum protection.SAMEORIGIN, embedding allowed only from the same domain. Needed if you yourself use frames for your own pages (for example, preview in the admin panel).
For most WordPress sites, SAMEORIGIN works. If you know for certain that no frames are used, set DENY.
Recommended value: SAMEORIGIN
Breakage risks: some plugins use iframes for previews. If something stops displaying in the admin panel after enabling DENY, switch to SAMEORIGIN.
Strict-Transport-Security (HSTS)
Tells the browser: "for the next N seconds, access this domain only via HTTPS." Protects against downgrade attacks and cookie hijacking on open networks.
The key parameter is max-age. It's recommended to start with a small value (a day or a week) and, after verification, increase it to a year.
The optional includeSubDomains parameter extends the rule to all subdomains, important if you have several and use a CDN.
The preload parameter adds the site to the global HSTS list of browsers: Chrome, Firefox, and Safari will access your domain only via HTTPS even before the first request. Submit an application via hstspreload.org.
Recommended value: max-age=31536000; includeSubDomains
Breakage risks: if you enable HSTS and the SSL certificate expires, users won't be able to access the site at all. Keep an eye on certificate auto-renewal.
Referrer-Policy
Manages what information about the source page the browser sends when following a link to another site.
Main values, from strictest to most permissive:
no-referrer, nothing is sent at all.strict-origin-when-cross-origin, to a different domain, only the domain is sent (no path or parameters); to your own, the full URL. The optimal balance of privacy and analytics.strict-origin, always only the domain, no path.
If you don't want destination sites to know where the user came from at all, set no-referrer. But keep in mind: you'll lose referral traffic in your own analytics.
Recommended value: strict-origin-when-cross-origin
Breakage risks: minimal. Some third-party services may require a referrer to function (for example, payment gateways with source verification). You can track such cases through the browser console.
Permissions-Policy
Formerly called Feature-Policy. Restricts the web page's access to "hardware" browser APIs: microphone, camera, geolocation, USB, payment APIs, and so on.
Format: название-фичи=(разрешённый-источник). For example:
1 Permissions-Policy: camera=(), microphone=(), geolocation=(self)
This example completely blocks access to the camera and microphone for all sources, and allows geolocation only for the site itself (self).
Full list of features on MDN. For a WordPress site without video chats and PWA features, it's enough to block the camera, microphone, and geolocation; the rest is optional.
Recommended value: depends on the site's functionality. Minimum: camera=(), microphone=(), geolocation=(self).
Breakage risks: if the site has video calling, PWA, or WebUSB functionality, check which features it needs and don't block them.
Content-Security-Policy (CSP)
The most powerful and the most dangerous header to configure. Defines an allowlist of sources for scripts, styles, fonts, images, frames, and other resources.
Example of a minimal policy for WordPress:
1 Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; 2
*Why unsafe-inline and unsafe-eval:* WordPress, Gutenberg, and dozens of plugins generate inline scripts and use eval(). Without these directives, the admin panel simply won't work. This is the price of compatibility.
Recommendation: don't enable CSP first. Configure the other 5 headers first. Only implement CSP if you have the resources for testing and monitoring; start with Content-Security-Policy-Report-Only mode and collect violation logs for a couple of weeks before switching to enforce mode.
Breakage risks: high. A mistake in CSP can completely break the frontend and admin panel. Always test on a staging environment or via Report-Only.
How to check your site's current headers
Before configuring, an audit. Three tools, from simple to detailed:
securityheaders.com, enter the URL and get a score from A+ to F with a breakdown for each header. Free, no registration.
Mozilla Observatory, a more detailed audit: evaluates not only headers but also TLS settings, cookie attributes, CORS, and other aspects. Also free.
Chrome DevTools, open the Network tab, select any request to your site, and look at the Response Headers section. A quick way to check whether changes "took" after editing.htaccess.
Check your site right now. If the score is below B, you don't even have basic headers, and a third of the items below you'll set up in 5 minutes.
Ways to configure HTTP headers in WordPress
Three paths, from manual to automatic. Choose based on your situation.
Method 1: Via.htaccess (Apache/LiteSpeed)
The fastest method for sites on Apache or LiteSpeed. Add to the .htaccess file in the site root (before # BEGIN WordPress):
1 <IfModule mod_headers.c> 2 Header set X-Content-Type-Options "nosniff" 3 Header set X-Frame-Options "SAMEORIGIN" 4 Header set Strict-Transport-Security "max-age=31536000; includeSubDomains" 5 Header set Referrer-Policy "strict-origin-when-cross-origin" 6 Header set Permissions-Policy "camera=(), microphone=(), geolocation=(self)" 7 </IfModule>
The <IfModule mod_headers.c> wrapper ensures the site won't crash with a 500 error if the mod_headers module isn't enabled. If you know for certain it's active, you can omit it.
After saving, run in the terminal (if you have WP-CLI) or simply open the site and check the headers via securityheaders.com. Changes in .htaccess apply instantly; no server restart is needed.
Method 2: Via functions.php (Nginx / universal)
Works regardless of the web server, suitable for both Nginx and Apache. Add to the functions.php of your child theme or via the Code Snippets plugin:
1 function sdstudio_add_security_headers() { 2 header( 'X-Content-Type-Options: nosniff' ); 3 header( 'X-Frame-Options: SAMEORIGIN' ); 4 header( 'Strict-Transport-Security: max-age=31536000; includeSubDomains' ); 5 header( 'Referrer-Policy: strict-origin-when-cross-origin' ); 6 header( 'Permissions-Policy: camera=(), microphone=(), geolocation=(self)' ); 7 } 8 add_action( 'send_headers', 'sdstudio_add_security_headers' );
The send_headers hook fires before WordPress starts sending content, so headers go out in the very first server response.
For Nginx, this method is preferable to digging into the server configuration (especially on shared hosting, where access to nginx.conf is often unavailable). The downside: headers only work for PHP requests passing through WordPress. Static files (images, CSS, JS) are served directly by Nginx and won't get the headers. They require server-level configuration.
Method 3: Via the headers security advanced & hsts wp plugin
If you don't want to touch code, use a dedicated plugin. Headers Security Advanced & HSTS WP collects all security headers in one interface and enables them with a single button.

The plugin sets preset values for all key headers immediately after activation: X-Content-Type-Options, X-Frame-Options, HSTS, CSP, Referrer-Policy, Permissions-Policy, and several additional ones (CORP, COOP, COEP). Each header can be disabled or customized to your needs.
The advantage of this approach: the plugin doesn't depend on the web server and works identically on Apache, Nginx, LiteSpeed, and IIS. On deactivation, it returns the site configuration to its original state.
Alternative: Redirection
The Redirection plugin is known as a redirect manager, but it can also add HTTP headers. After installation and basic setup, go to the Site → Headers section; the interface lets you specify any "header: value" pairs.

This option works if you already have Redirection installed for link management and don't want to add another plugin. But a dedicated headers plugin gives more control and preset values.
In the video, a visual demonstration of configuring headers via the Headers Security Advanced plugin: from installation to verifying the result on securityheaders.com.
⁉️🤔 Frequent questions
What if the site breaks after enabling headers?
First, check the Content-Security-Policy header: it causes the vast majority of breakages. Disable CSP (or switch it to Report-Only mode) and check the site again. A 500 error after editing.htaccess: check the syntax for an extra space, unclosed quotes, or a missing mod_headers module. Via FTP, rename.htaccess, the site will come back to life, and you can calmly find the error.
In 9 out of 10 cases, the problem is specifically CSP, not the other headers. X-Content-Type-Options, X-Frame-Options, HSTS, and Referrer-Policy physically cannot break the site; they don't affect rendering or PHP logic. Always start with those.
Is a plugin mandatory, or can it be done via code?
It can and should be done via code if you know how to edit functions.php or.htaccess. Setting headers directly through server configuration works faster than a plugin, with no PHP layer in between. But a plugin is more convenient for those who don't want to touch code and provides a visual interface with presets for each header.
On our test sites, we use both approaches: on production,.htaccess (fewer points of failure); on client sites where the hosting doesn't allow editing the configuration, the plugin. The result on securityheaders.com is identical: A+ in both cases.
Do I need to configure CSP if the site is on WordPress?
For most sites, no. CSP on WordPress requires unsafe-inline and unsafe-eval for Gutenberg and dozens of plugins to work, which reduces the protective effect to symbolic. A full-fledged CSP makes sense on high-load projects with a dedicated security specialist who will analyze every violation report. For everyone else, the 5 basic headers provide the lion's share of practical protection.
How do I verify that the headers are working after installation?
Open the site in a browser, launch DevTools (F12) → Network tab → click the first request to your domain → Response Headers section. If you see the added headers, everything is working. For automated verification, use securityheaders.com: it shows not only presence but also correctness of values, plus a final score from A+ to F.
What if the hosting doesn't allow editing.htaccess and there's no access to functions.php?
Install Headers Security Advanced & HSTS WP via the WordPress admin panel. The plugin injects headers at the PHP level and doesn't require editing server files. This works on any hosting where WordPress runs, from cheap shared to Managed WordPress.
Do HTTP headers affect site speed?
Practically not. Headers are a couple of dozen bytes of text in the server response. They perform no calculations, make no database queries, and don't load the CPU. The delay they add is measured in microseconds. The only exception is CSP with a complex allowlist of hundreds of domains: the browser spends time parsing it, but even then, the impact on rendering speed doesn't exceed a couple of milliseconds.
What to set and where to start
HTTP security headers are not a one-time "set it and forget it" action. They are a baseline that should be on every site by default, like an SSL certificate.
The plan for today:
Check your site on securityheaders.com. If the score is D or F, you don't even have
X-Content-Type-Options.Add the 5 basic headers using any of the three methods above. If the site is on Apache, via.htaccess (30 seconds). Nginx without config access, via functions.php (2 minutes). If you don't want to touch code, the Headers Security Advanced plugin (3 minutes including installation).
Recheck the site. The score should rise to A or A+.
Put CSP aside. Come back to it once the basic headers have worked for a month without issues and you have time for testing via Report-Only.
One hour, and your WordPress site gains a layer of defense that the vast majority of sites on the internet lack. Start with a check right now.


