How to Add Security Headers to Your Website (Complete Guide)

A practical guide to configuring HSTS, CSP, X-Frame-Options, and other security headers — with copy-paste snippets for Cloudflare, Nginx, and Apache.

security headersguideweb security

Security headers are HTTP response headers that tell browsers how to behave when handling your website’s content. They’re one of the most effective ways to protect your site and your users — and most of them take just one line to configure.

What’s covered

This guide covers each security header, why it matters, and exactly how to add it on Cloudflare, Nginx, and Apache.

Strict-Transport-Security (HSTS)

HSTS tells browsers to always connect to your site over HTTPS. Without it, a user’s first visit could happen over unencrypted HTTP, making them vulnerable to man-in-the-middle attacks.

What to set:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000 — enforce for 1 year
  • includeSubDomains — apply to all subdomains
  • preload — opt into browser preload lists (permanent HTTPS enforcement)

Cloudflare (_headers file):

/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Apache:

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

Before you enable HSTS, make sure your site works fully over HTTPS — including all subdomains. Once browsers cache the HSTS policy, they will refuse to connect over HTTP until the max-age expires. Start with a short max-age (like 300 seconds) to test, then increase to a year once everything works.


Content-Security-Policy (CSP)

CSP controls which resources (scripts, styles, images, fonts, iframes) the browser is allowed to load. It’s the most powerful security header — and the most complex to configure.

Why it matters: CSP is the primary defense against Cross-Site Scripting (XSS). Even if an attacker finds a way to inject code into your page, CSP can prevent the browser from executing it.

A good starting point:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'

Important: start with report-only mode. CSP can break third-party integrations — analytics, payment processors, chat widgets, fonts, ads. Deploy in report-only mode first to see what would be blocked, then switch to enforced mode once you’ve added all necessary sources.

Cloudflare (_headers file):

/*
  Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'

Once tested, change Content-Security-Policy-Report-Only to Content-Security-Policy.

Nginx:

# Report-only first:
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'" always;

# Then enforce:
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'" always;

Apache:

# Report-only first:
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' https:; frame-ancestors 'self'"

# Then enforce:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'"

Common additions — if you use third-party services, you’ll need to add their domains:

  • Google Analytics: add https://www.google-analytics.com https://www.googletagmanager.com to script-src and connect-src
  • Google Fonts: add https://fonts.googleapis.com to style-src and https://fonts.gstatic.com to font-src
  • Stripe: add https://js.stripe.com to script-src and https://js.stripe.com to frame-src

Open your browser’s developer console after deploying — CSP violations show up as error messages telling you exactly which resource was blocked and which directive triggered it.


X-Frame-Options

X-Frame-Options prevents your site from being embedded in iframes on other domains. This blocks clickjacking attacks, where an attacker overlays your site with invisible elements to trick users into clicking things they didn’t intend to.

What to set:

X-Frame-Options: SAMEORIGIN

Use DENY if your site should never appear in any iframe. Use SAMEORIGIN if you embed your own pages in iframes.

Cloudflare (_headers file):

/*
  X-Frame-Options: SAMEORIGIN

Nginx:

add_header X-Frame-Options "SAMEORIGIN" always;

Apache:

Header always set X-Frame-Options "SAMEORIGIN"

Note: ALLOW-FROM is deprecated and no longer supported by modern browsers. If you need to allow specific domains to embed your site, use CSP’s frame-ancestors directive instead.


X-Content-Type-Options

This header prevents browsers from guessing (“sniffing”) the MIME type of a response. Without it, a browser might interpret a file differently than intended — for example, treating a plain text file as JavaScript and executing it.

What to set:

X-Content-Type-Options: nosniff

This is a single value, no configuration needed. There’s no reason not to add it.

Cloudflare (_headers file):

/*
  X-Content-Type-Options: nosniff

Nginx:

add_header X-Content-Type-Options "nosniff" always;

Apache:

Header always set X-Content-Type-Options "nosniff"

Referrer-Policy

Referrer-Policy controls how much URL information is sent to other sites when a user clicks a link or loads a resource from your page. Without it, the full URL (including query parameters that might contain tokens or user data) could be shared with third parties.

What to set:

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

This sends the origin (e.g., https://example.com) to other sites but keeps the full path private. It’s a good balance of functionality and privacy.

Cloudflare (_headers file):

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

Nginx:

add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Apache:

Header always set Referrer-Policy "strict-origin-when-cross-origin"

For maximum privacy, use no-referrer — but note this can break some analytics and affiliate tracking.


Permissions-Policy

Permissions-Policy lets you control which browser features your site can use — camera, microphone, geolocation, payment, USB, and more. Even if your site doesn’t use these features, restricting them prevents injected scripts from accessing them.

What to set:

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

The () means “no one can use this feature.” If your site does use a feature (like geolocation), change it to (self) to allow your own origin.

Cloudflare (_headers file):

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

Nginx:

add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;

Apache:

Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"

All Headers Together

Here’s a complete configuration with all six headers. You can copy this as a starting point and adjust CSP for your specific needs.

Cloudflare (_headers file):

/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'
  X-Frame-Options: SAMEORIGIN
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=()

Nginx:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;

Apache:

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https:; frame-ancestors 'self'"
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 Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()"

How to Verify Your Headers

After deploying, check that your headers are working:

  1. Browser DevTools — Open Network tab, click on the main document request, and check the Response Headers section.
  2. curl — Run curl -I https://yoursite.com to see response headers in your terminal.
  3. GuardrScan your site for free to get an instant security grade with specific findings and fix instructions for any missing or misconfigured headers.

Most of Security headers take minutes to configure and protect your users for every visit. Start with the quick wins — X-Content-Type-Options, Referrer-Policy, and Permissions-Policy — then work through HSTS and CSP with appropriate testing.

Check your website's security score

Free scan — no signup required.

Scan your site →