Harden Your WordPress Site with Security Headers
At MegaHost, we believe security should never come at the cost of style—or sanity. This guide walks you through how we implemented HSTS and a suite of powerful HTTP security headers on a LiteSpeed-powered WordPress site, while preserving the visual integrity of the admin dashboard (yes, including that elusive LiteSpeed diamond icon).
Why Security Headers Matter
Security headers are small but mighty HTTP directives that tell browsers how to behave when interacting with your site. They help prevent:
- Clickjacking
- Cross-site scripting (XSS)
- MIME-type sniffing
- Leaky referrer data
- Insecure asset loading
But if misconfigured, they can also block legitimate assets—like your WordPress avatar or LiteSpeed’s admin icon.
Add Security Headers to LiteSpeed
LiteSpeed is Apache-compatible, so we use to inject headers. Here’s how we did it:
- Enable HSTS (Strict HTTPS Enforcement)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Place this above the WordPress block in , outside any plugin-managed sections.
This tells browsers to:
- Always use HTTPS
- Apply to all subdomains
- Remember this for 1 year
- Qualify for browser preload lists
- Add Additional Security Headers
<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"
Header always set X-XSS-Protection "1; mode=block"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>
These headers:
- Prevent your site from being embedded in iframes
- Block MIME-type sniffing
- Limit referrer leakage
- Harden against legacy XSS
- Disable access to sensitive browser APIs
- Craft a Custom Content-Security-Policy (CSP)
This is where things got tricky.
Our first CSP blocked LiteSpeed’s admin icon and the WordPress dashboard avatar. The result? A broken square where the diamond should be, and a missing profile image.
We fixed it by tailoring the and directives:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data: https://cdn.litespeedtech.com; img-src 'self' data: https://secure.gravatar.com; frame-ancestors 'none';"
This version:
- Allows LiteSpeed’s font assets
- Whitelists Gravatar for avatars
- Supports inline styles/scripts for WordPress
- Blocks framing for clickjacking protection
Testing Your Headers
Use these tools to verify your setup:
curl -I https://yourdomain.com
→ check headers- SecurityHeaders.com → score your site
- Browser Dev Tools → Console tab → look for CSP violations
Lessons Learne
- LiteSpeed’s icon font lives on a CDN—you must whitelist it.
- Gravatar avatars need explicit permission in
img-src
. - Always test after changes—a broken dashboard is a silent productivity killer.
Final .htaccess
Block
# Hardened headers for HTTPS enforcement, clickjack protection, XSS, and asset control
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
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"
Header always set X-XSS-Protection "1; mode=block"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data: https://cdn.litespeedtech.com; img-src 'self' data: https://secure.gravatar.com; frame-ancestors 'none';"
</IfModule>
#
Troubleshooting: WordPress Media Library Not Loading in Grid View
If your Media Library grid view is blank and your browser console shows an error like:
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script...
…it means your Content Security Policy is blocking unsafe-eval
, which WordPress’s Underscore.js templates need in the admin area.
Fix: Keep your public site secure, but allow 'unsafe-eval'
only in /wp-admin/
by splitting your CSP rules:
# Public site CSP (strict)
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data: https://cdn.litespeedtech.com; img-src 'self' data: blob: https://secure.gravatar.com https://megahost.xyz https://cdn.megahost.xyz https://user.megahost.xyz https://ps.w.org; frame-ancestors 'none';" "expr=%{REQUEST_URI} !~ m#^/wp-admin/#"
# Admin area CSP (looser for Media Library)
<If "%{REQUEST_URI} =~ m#^/wp-admin/#">
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; font-src 'self' data: https://cdn.litespeedtech.com; img-src 'self' data: blob: https://secure.gravatar.com https://megahost.xyz https://cdn.megahost.xyz https://user.megahost.xyz https://ps.w.org; frame-ancestors 'none';"
</If>
Why this works:
- Public pages stay strict —
'unsafe-eval'
remains blocked. - Admin pages allow
'unsafe-eval'
so the Media Library grid view works. blob:
inimg-src
ensures local previews display.ps.w.org
allows plugin/theme images from WordPress.org..
Troubleshooting: Elementor Not Loading or Missing Assets in Preview
If Elementor’s live preview wasn’t loading at all and now loads but background images, fonts, or Hostiko assets are still missing, the root cause is usually Content Security Policy (CSP) restrictions.
Initial Symptom
Elementor’s preview panel stayed blank or showed a browser console error like:
Refused to frame 'https://yourdomain.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'none'".
This happens because the default MegaHost CSP blocks all iframing of your site — including by Elementor’s own editor.
Secondary Symptom (after fixing iframe load)
Preview loads, but:
- Background images don’t appear.
- Hostiko theme images or fonts are missing.
- Console shows CSP violations for
img-src
,font-src
, orstyle-src
. - Some CSS/JS files fail to load due to MIME type errors.
Why it happens
First issue: frame-ancestors 'none'
prevented Elementor from embedding your site in its preview iframe.
Second issue: CSP img-src
and font-src
didn’t include Hostiko’s domains or Elementor’s CDN, so the browser blocked those assets.
Extra complication: Some CSS files may be served with the wrong MIME type (HTML instead of CSS), which CSP changes can’t fix — that’s a server/file issue.
What we changed
Allowed Elementor preview to embed the site
- Changed
frame-ancestors 'none'
→frame-ancestors 'self'
.
Whitelisted Hostiko’s domain in img-src
and font-src
.
Whitelisted Elementor’s CDN in img-src
.
Kept all other security headers intact for protection.
Left a note that MIME type errors must be fixed at the file/server level.
Updated MegaHost Security Headers block
Replace your existing .htaccess
MegaHost Security Headers section with this:
# BEGIN MegaHost Security Headers
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
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"
Header always set X-XSS-Protection "1; mode=block"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# Public site CSP (strict but allows Elementor preview + Hostiko/Elementor assets)
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data: https://cdn.litespeedtech.com https://hostiko.com; img-src 'self' data: blob: https://secure.gravatar.com https://megahost.xyz https://cdn.megahost.xyz https://user.megahost.xyz https://ps.w.org https://hostiko.com https://cdn.elementor.com; frame-ancestors 'self';" "expr=%{REQUEST_URI} !~ m#^/wp-admin/#"
# Admin area CSP (looser for Media Library + Elementor)
<If "%{REQUEST_URI} =~ m#^/wp-admin/#">
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://cdn.litespeedtech.com https://fonts.gstatic.com https://hostiko.com; img-src 'self' data: blob: https://secure.gravatar.com https://megahost.xyz https://cdn.megahost.xyz https://user.megahost.xyz https://ps.w.org https://hostiko.com https://cdn.elementor.com; frame-ancestors 'self';"
</If>
</IfModule>
# END MegaHost Security Headers
Extra tip
If you still see MIME type errors for CSS or JS:
- Visit the file URL directly in your browser.
- If you see an HTML error page instead of raw CSS/JS, restore the file or adjust rewrite rules so it serves the correct
Content-Type
.
Ready to Go Further?
Want to submit your domain to the HSTS Preload list? Or build a branded redirect logger that turns HTTP hits into ASCII splash screens? At MegaHost, we turn friction into flair.
Submit your domain to HSTS Preload List: