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 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
- Strict-Transport-Security (HSTS)
- Content-Security-Policy (CSP)
- X-Frame-Options
- X-Content-Type-Options
- Referrer-Policy
- Permissions-Policy
- All Headers Together
- How to Verify Your Headers
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 yearincludeSubDomains— apply to all subdomainspreload— 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.comtoscript-srcandconnect-src - Google Fonts: add
https://fonts.googleapis.comtostyle-srcandhttps://fonts.gstatic.comtofont-src - Stripe: add
https://js.stripe.comtoscript-srcandhttps://js.stripe.comtoframe-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:
- Browser DevTools — Open Network tab, click on the main document request, and check the Response Headers section.
- curl — Run
curl -I https://yoursite.comto see response headers in your terminal. - Guardr — Scan 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.