To add HTTP security headers to WordPress, either add them to your server configuration (.htaccess on Apache, the server block on Nginx) or send them from WordPress with a plugin. I wrote the free HTTP Security Header plugin for exactly this, and it now has more than 1,000 active installs. Scan your site first, switch on the safe headers, and add Content-Security-Policy last, with testing.
What are HTTP security headers?
They are instructions your server sends with every page. The browser reads them and enforces rules on your behalf: only load this site over HTTPS, do not let other sites frame this page, do not guess file types, do not send full URLs to other sites. A few lines of configuration close off whole classes of attack such as clickjacking, content sniffing and cross-site scripting.
Most WordPress sites send none of them, because adding headers normally means editing server files that site owners are rightly afraid to touch.
Which security headers does a WordPress site need?
| Header | What it protects against | Safe to switch on? |
|---|---|---|
| Strict-Transport-Security (HSTS) | Visitors being downgraded from HTTPS to HTTP | Yes, once the whole site works on HTTPS |
| Content-Security-Policy (CSP) | Injected scripts and cross-site scripting | Only after testing |
| X-Frame-Options | Clickjacking, where your site is loaded in a hidden frame | Yes, unless you embed your own pages elsewhere |
| X-Content-Type-Options | Browsers guessing a file's type and running it as script | Yes |
| Referrer-Policy | Leaking full page URLs to other sites | Yes |
| Permissions-Policy | Pages or embeds using camera, microphone or location without need | Yes, after listing the features you use |
The plugin also covers X-XSS-Protection, X-Permitted-Cross-Domain-Policies and Feature-Policy, the older name for Permissions-Policy, because some scanners and older browsers still look for them.
How do you check which headers your site sends?
Use my free HTTP Security Header Scanner. It fetches your headers on the server, which matters because a browser cannot read another site's response headers. It grades the site from A+ to F, shows each header's status and exact value, explains what it protects against and gives a step-by-step fix. Each finding links to an article in my 15-part series on individual headers.
If you prefer the terminal:
curl -sI https://example.com | grep -iE "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
Every header that does not appear is missing.
What are the ways to add security headers in WordPress?
| Method | Good for | Watch out for |
|---|---|---|
.htaccess on Apache | Developers on Apache hosting | A typo can take the whole site down |
| Nginx server block | Developers with server access | Needs a reload, and not possible on many managed hosts |
| A WordPress plugin | Site owners and agencies on any host | Check the host is not already sending the same headers |
For Apache, the safe trio looks like this:
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
On Nginx the equivalent is an add_header ... always; line per header inside the server block.
How does the HTTP Security Header plugin work?
After you install it, the settings live under Settings > Security Headers. It works on WordPress 5.0 and above.
- It sends headers from WordPress, not the web server, so it is portable across Apache, Nginx and managed hosts where you have no config access.
- Each header has its own settings with defaults that are safe to switch on.
- Values are validated before saving.
- It falls back instead of breaking. A bad Content-Security-Policy or a mistyped frame option cannot lock you out of your own dashboard, because the plugin falls back to a working value.
That last point matters most. A security plugin that breaks a site gets switched off and forgotten. One that cannot break the site stays on. I published it to WordPress.org in November 2024 and have shipped updates through version 3.1. Agencies I work with install it as a standard part of a build, and I run it on stores such as Sun Shoppers.
If you use page caching at the server or CDN level, re-scan after switching headers on, because some cache layers serve stored pages with their own headers.
How do you add Content-Security-Policy without breaking the site?
CSP tells the browser which domains may supply scripts, styles, images, fonts and frames. It is the most powerful header and the easiest to get wrong.
- 1
List what the site loads
Analytics, Google Tag Manager, HubSpot or other CRM scripts, fonts, video embeds, payment widgets and chat tools. Each one needs its domain allowed.
- 2
Start in report-only mode
Send Content-Security-Policy-Report-Only first. The browser reports what it would block without blocking anything.
- 3
Tighten in steps
Allow what is legitimate, remove what is not, and keep checking the browser console.
- 4
Test the important paths
Forms, checkout, logins and the WordPress admin, on desktop and mobile.
- 5
Enforce
Switch to the enforcing header once a full test pass shows nothing legitimate is blocked.
Sites that load a lot through Google Tag Manager need extra care. On Ravens Ait, for example, enquiry forms sync to HubSpot through Tag Manager, so every domain in that chain has to be allowed.
What else should you harden besides headers?
Headers are one layer. On client sites I also set up:
- Two-factor login with Wordfence, as on Sun Shoppers.
- Off-site backups, such as UpdraftPlus to Google Drive.
- An activity log, such as WP Activity Log on Samko Holdings.
- A supported PHP version. Two of my 2025 client sites moved from PHP 8.0 to 8.3, which also fixed a form bug.
- Fewer plugins. Every unused plugin is weight and attack surface. I removed a leftover WooCommerce install from the Raasa launch page for that reason.
For high-stakes sites, the biggest single gain is not serving WordPress publicly at all. The Janssen Neuroscience microsite was served statically, which removed the live database and admin from the public surface.
Need headers set up, or a plugin hardened for your own product? That falls under my custom WordPress plugins and maintenance work.



