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=()
Some cookies aren’t protected
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.