Client Area

Security Headers Explained: CSP, HSTS, X-Frame-Options, and More

ByDomain India Security Team
7 min read22 Apr 20264 views

In this article

  • 1Why headers matter
  • 2The eight essential headers
  • 31. Strict-Transport-Security (HSTS)
  • 42. Content-Security-Policy (CSP)
  • 53. X-Content-Type-Options

Security Headers Explained: CSP, HSTS, X-Frame-Options, and More

Security headers are the cheapest wins in web application security. Set them once, and they defend your users from an entire category of attacks forever. This guide covers the eight essential headers every production web app should send, with working examples for Apache .htaccess, nginx, and Express.

Why headers matter

Every HTTP response from your server carries metadata in headers. A few specific headers tell the browser how to handle your content — whether JavaScript from other sites can execute, whether your site can be embedded in an iframe, whether to force HTTPS. Used correctly, they block XSS, clickjacking, mixed-content attacks, and referrer leaks with no code change to your app. Used wrong (or not at all), they leave your users exposed.

If you do nothing else from this article, enable the first four.

The eight essential headers

1. Strict-Transport-Security (HSTS)

Tells the browser "from now on, always connect to this site over HTTPS, even if the user types http://". Protects against downgrade attacks on public Wi-Fi.

Recommended value:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000 — remember for 1 year
  • includeSubDomains — applies to subdomains too (only set if you are sure every subdomain has HTTPS)
  • preload — opt in to Chrome's preload list (submit at hstspreload.org after testing with a smaller max-age first)

Important: start with max-age=60 for a few days before jumping to a year. If you get HSTS wrong and push a year-long policy, you cannot fix it until the TTL expires in clients.

2. Content-Security-Policy (CSP)

The single most powerful header. Tells the browser what content sources are allowed (scripts, styles, images, fonts, iframes). A strict CSP blocks almost every XSS attack.

A reasonable starting policy:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-RANDOM'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'

Gotcha: CSP will break your site on first try because of inline scripts, analytics tags, etc. Deploy in report-only mode first:

Content-Security-Policy-Report-Only: (your policy)

Monitor report-uri for violations, fix what breaks, then flip to enforcing mode.

3. X-Content-Type-Options

Tells the browser not to "sniff" a file's MIME type, which prevents attackers from uploading, say, an image that the browser later executes as JavaScript.

X-Content-Type-Options: nosniff

One line, no downside, always on.

4. X-Frame-Options (or CSP frame-ancestors)

Prevents other sites from embedding yours in an iframe — the mechanism behind clickjacking.

X-Frame-Options: DENY

Or if you need to allow same-origin framing:

X-Frame-Options: SAMEORIGIN

Modern: use CSP's frame-ancestors directive instead and drop X-Frame-Options. Setting both is not harmful, but frame-ancestors is the successor.

5. Referrer-Policy

Controls how much of the URL is sent in the Referer header when users click a link to another site. Important for privacy if your URLs contain session IDs or sensitive paths.

Recommended value:

Referrer-Policy: strict-origin-when-cross-origin

Sends full URL on same-origin navigation, only the origin on cross-origin HTTPS navigation, nothing on HTTP downgrade.

6. Permissions-Policy

Lets you disable browser features you do not use (camera, microphone, geolocation, payment, USB). Smaller attack surface.

Reasonable baseline:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()

Each feature listed with empty () means "disallowed for all origins". Allow specifically where needed: geolocation=(self) to allow just your own site.

7. Cross-Origin-Opener-Policy

Isolates your browsing-context group from cross-origin documents. Defends against Spectre-class side-channel attacks.

Cross-Origin-Opener-Policy: same-origin

8. Cross-Origin-Embedder-Policy

Prevents loading cross-origin resources without explicit opt-in. Required alongside COOP for some modern browser features (SharedArrayBuffer, high-resolution timers).

Cross-Origin-Embedder-Policy: require-corp

Use only if you need SharedArrayBuffer. Otherwise skip — it breaks many third-party embeds.

Setting headers on Apache / LiteSpeed (.htaccess)

Paste this into your site's .htaccess file (usually in public_html/):

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "DENY"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
    Header always set Cross-Origin-Opener-Policy "same-origin"

    # CSP — test in report-only first before switching to enforcing
    Header always set Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'"
</IfModule>

All our cPanel and DirectAdmin plans support .htaccess overrides out of the box.

Setting headers on nginx

In your server block:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;
add_header Content-Security-Policy-Report-Only "default-src 'self'" always;

Reload nginx after changes: sudo nginx -t && sudo systemctl reload nginx.

Setting headers on Express (Node.js)

Use the helmet package — it sets all the above with safe defaults in one line:

javascript
import helmet from 'helmet';
app.use(helmet());

// Customise CSP:
app.use(helmet.contentSecurityPolicy({
  directives: {
    defaultSrc: ["'self'"],
    scriptSrc: ["'self'"],
    styleSrc: ["'self'", "'unsafe-inline'"],
    imgSrc: ["'self'", "data:", "https:"],
  }
}));

Testing your headers

  1. securityheaders.com — paste your URL, get an A/B/C/D grade with specific recommendations.
  2. Mozilla Observatory (observatory.mozilla.org) — more detailed than securityheaders.com; catches misconfigurations.
  3. Manualcurl -I https://yourdomain.com from a terminal. Read the returned headers.

Aim for grade A on securityheaders.com. A+ requires HSTS preload submission; A is usually the right target unless you run a payments or banking-level site.

Common pitfalls

  • HSTS with too-long max-age on HTTP-only staging — locks that subdomain to HTTPS for the entire TTL. Start with max-age=60 on staging.
  • CSP with `unsafe-inline` in script-src — defeats most of CSP's XSS protection. Use nonces or hashes instead.
  • Setting both X-Frame-Options and CSP frame-ancestors with conflicting values — browsers will pick one; just use frame-ancestors.
  • Missing `always` in nginx add_header — without always, the header is only set for 2xx and 3xx responses, not errors. Errors are when you most want headers set.

Frequently asked questions

Will these headers slow my site down?

No. They are metadata, not content. Impact is zero on performance, high on security.

Do these replace a Web Application Firewall (WAF)?

No. Headers are browser-side defences. A WAF is server-side. Use both. Our shared hosting plans include ModSecurity WAF by default.

How do I test CSP without breaking my site?

Use Content-Security-Policy-Report-Only mode. The header name is different; the browser reports violations via report-uri but does not block anything. Run report-only for a week, fix what breaks, then switch to enforcing.

Can I use the same .htaccess block on WordPress?

Yes. WordPress uses Apache's .htaccess for rewrites. You can add our headers block above or below the WordPress rewrite section. Test after to make sure the admin area still works.

What is a "nonce" in CSP?

A random value your server generates per response, embedded in both the CSP header ('nonce-XYZ') and each <script nonce="XYZ"> tag. Lets your own inline scripts run while blocking attacker-injected inline scripts that lack the right nonce.

Are security headers sufficient protection against XSS?

They are a strong secondary defence. The primary defence is output encoding (HTML-escaping user input before rendering). See our upcoming XSS prevention article. Headers stop many XSS attacks that slip past; they do not replace secure coding.


Questions? Email [email protected] or reply to any support ticket — our team will help review your header config.

Was this article helpful?

Your feedback helps us improve our documentation

Still need help? Submit a support ticket