Security headers explained: HSTS, CSP, X-Frame-Options and cookies

Last reviewed

Security headers are short instructions your site sends with every page, telling the visitor’s browser how to protect them. They cost nothing and take minutes to add.

Here are the ones we check, what each one does, and how to switch it on.

Check your own site for this in about a minute

Browsers aren’t told to always use encryption

What it is. Your site doesn’t send the Strict-Transport-Security header.

Why it matters. Without it, a visitor’s first request can be quietly downgraded to an unencrypted one.

Time to fix. About 5 minutes

Nginx

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

Apache / cPanel

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

Cloudflare

SSL/TLS > Edge Certificates > HTTP Strict Transport Security (HSTS) > Enable

Any host

Ask your host to add this response header:
Strict-Transport-Security: max-age=31536000; includeSubDomains

Your HTTPS-only instruction is too short or incomplete

What it is. Your Strict-Transport-Security header is set, but for less than 6 months or without covering subdomains.

Why it matters. Visitors are only protected while the instruction lasts, and unprotected subdomains can be used to attack the main site.

Time to fix. About 5 minutes

Nginx

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

Apache / cPanel

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

Cloudflare

SSL/TLS > Edge Certificates > HSTS > Max Age: 12 months, Include subdomains: On

Any host

Set the header to: Strict-Transport-Security: max-age=31536000; includeSubDomains

Your site doesn’t limit which scripts may run

What it is. There’s no Content-Security-Policy header telling browsers which scripts are allowed.

Why it matters. If someone injects a script into your pages, nothing stops it running in your visitors’ browsers.

Time to fix. About 1 hour (test first in report-only mode)

Nginx

# start in report-only mode, check nothing breaks, then switch to Content-Security-Policy
add_header Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self'" always;

Apache / cPanel

Header always set Content-Security-Policy-Report-Only "default-src 'self'; img-src 'self' data: https:; script-src 'self'"

Cloudflare

Rules > Transform Rules > Modify Response Header >
Set "Content-Security-Policy-Report-Only" to: default-src 'self'

Any host

Start with this header in report-only mode, then enforce it once nothing breaks:
Content-Security-Policy-Report-Only: default-src 'self'

Your script policy still lets risky scripts run

What it is. Your Content-Security-Policy allows inline scripts, eval(), or scripts from anywhere.

Why it matters. Those are exactly what injected code relies on, so the policy gives much less protection than it could.

Time to fix. About 1–2 hours with your developer

Nginx

# example; adjust the hosts to match your site
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com" always;

Apache / cPanel

Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://www.googletagmanager.com"

Any host

Remove 'unsafe-inline' and 'unsafe-eval' from script-src, list only the hosts you load scripts from,
and use nonces or hashes for any inline scripts you need. Test in report-only mode first.

Parts of your secure pages load without encryption

What it is. Your HTTPS pages load scripts, styles or frames over plain http://.

Why it matters. Browsers block these (breaking the page), or someone on the network can swap them for malicious code.

Time to fix. About 15 minutes

Nginx

# Tells browsers to upgrade any http:// resources automatically
add_header Content-Security-Policy "upgrade-insecure-requests" always;

Apache / cPanel

Header always set Content-Security-Policy "upgrade-insecure-requests"

Cloudflare

SSL/TLS > Edge Certificates > Automatic HTTPS Rewrites: On

Any host

Change every http:// link to a script, stylesheet or iframe into https://.
WordPress: run a search-and-replace for http://yourdomain.com, or use a plugin such as Really Simple SSL.

Other sites can embed your pages in a hidden frame

What it is. Your site doesn’t say whether other websites may show it inside a frame.

Why it matters. Attackers can overlay your page with fake buttons to trick visitors into clicking ("clickjacking").

Time to fix. About 5 minutes

Nginx

add_header X-Frame-Options "SAMEORIGIN" always;

Apache / cPanel

Header always set X-Frame-Options "SAMEORIGIN"

Cloudflare

Rules > Transform Rules > Modify Response Header > Set "X-Frame-Options" to "SAMEORIGIN"

Any host

Add the response header: X-Frame-Options: SAMEORIGIN

Browsers may guess file types on your site

What it is. The X-Content-Type-Options header is missing.

Why it matters. Browsers might run an uploaded file as a script when it shouldn’t be.

Time to fix. About 2 minutes

Nginx

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

Apache / cPanel

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

Cloudflare

Rules > Transform Rules > Modify Response Header > Set "X-Content-Type-Options" to "nosniff"

Any host

Add the response header: X-Content-Type-Options: nosniff

Your pages may leak full addresses to other sites

What it is. There’s no Referrer-Policy header, so the full address of each page can be passed on to other sites.

Why it matters. Addresses can contain private details like search terms, order numbers or reset links.

Time to fix. About 2 minutes

Nginx

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

Apache / cPanel

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

Cloudflare

Rules > Transform Rules > Modify Response Header > Set "Referrer-Policy" to "strict-origin-when-cross-origin"

Any host

Add the response header: Referrer-Policy: strict-origin-when-cross-origin

Your site doesn’t limit access to camera, microphone and location

What it is. There’s no Permissions-Policy header saying which browser features your pages may use.

Why it matters. If a script on your site is compromised, it could ask visitors for their camera, microphone or location.

Time to fix. About 5 minutes

Nginx

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

Apache / cPanel

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

Cloudflare

Rules > Transform Rules > Modify Response Header > Set "Permissions-Policy" to "camera=(), microphone=(), geolocation=()"

Any host

Add the response header: Permissions-Policy: camera=(), microphone=(), geolocation=()

What it is. One or more cookies are missing the Secure or HttpOnly flag.

Why it matters. Login cookies without these flags are easier to steal.

Time to fix. About 15 minutes

Nginx

# nginx 1.19.3+
proxy_cookie_flags ~ secure httponly samesite=lax;

Apache / cPanel

Header always edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax"

Any host

Set cookies with: Secure; HttpOnly; SameSite=Lax
In WordPress, a security plugin or your host’s settings usually handles this.