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:

  1. 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
  1. 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
  1. 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: in img-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, or style-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:

HSTS Preload List Submission

Post Your Comment