Security · fix guide

How to add security headers to your website

Five response headers and two cookie flags tell browsers what to refuse on your site. Here's what each one does, the line to add on nginx, Apache, cPanel or Cloudflare, and how to roll them out without breaking anything.

Published Updated By Remidiy

Short answer: security headers are short instructions your server sends with each page, telling the browser what to refuse. For a typical small-business site, five headers and correct cookie flags cover the basics: Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN, a Content-Security-Policy and a Permissions-Policy.

All of them are rated Minor in the checker because none is a hole on its own. Each one closes off a whole kind of attack, though. Most take one line in your server, host or CDN settings, with the Content Security Policy as the one that needs testing first.

Every time a browser loads your page it receives these headers before the content. You can't see them on the page; you read them with curl -sI https://yourbusiness.com or in the browser's developer tools (F12, Network tab, click the page, Headers). The checker only looks at headers on the https version of your homepage, so make sure HTTPS works first.

Where to add security headers

Each finding below gives the header line to add. Where you put it depends on how your site is hosted:

  • nginx: add add_header Name "value" always; lines inside the server block, then nginx -t && systemctl reload nginx. A location block that has its own add_header lines doesn't inherit these, so repeat them there.
  • Apache and cPanel shared hosting: add Header always set Name "value" lines to .htaccess inside <IfModule mod_headers.c> (mod_headers).
  • Cloudflare: a Response Header Transform Rule can set any header for every page without touching the server (Cloudflare docs). HSTS has its own switch, covered below.
  • WordPress: use the server methods above, or a security or headers plugin. Pick one that shows you the exact header values it sets.
  • Wix, Squarespace, Shopify: the platform controls the headers and generally doesn't let you add your own. A missing header there is the platform's to fix.
# Apache / .htaccess: a safe starting set
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

While you're in there, also turn off the version number in your Server and X-Powered-By headers; see how to hide your server version.

No HSTS header

Minor Shown in the checker's Security tab as “No HSTS header”.

Your https page has no Strict-Transport-Security header. HSTS (HTTP Strict Transport Security, defined in RFC 6797) tells the browser to use https for your domain for a set time, even if someone types or clicks an http address.

Why it matters

Without it, a returning visitor's browser may still make one plain-http request before your redirect sends it to https. On shared Wi-Fi, that first request is the opening an attacker can use to keep the visitor on a fake insecure copy (MDN: Strict-Transport-Security).

How to fix it

First make sure every page works over https and http redirects to it. Then start with a short max-age (in seconds), check nothing breaks, and raise it to a year:

Strict-Transport-Security: max-age=300        # first: 5 minutes
Strict-Transport-Security: max-age=31536000   # then: 1 year

Add includeSubDomains only if every subdomain (shop., mail., booking.) works over https. Browsers will refuse plain http on all of them. Skip preload: hstspreload.org itself now says preloading is not recommended for most sites, and it is slow to undo. On Cloudflare, use the HSTS setting in the edge certificate options (Cloudflare docs).

How to check it's fixed

curl -sI https://yourbusiness.com | grep -i strict shows the header. Browsers ignore HSTS sent over plain http, so test the https address.

No X-Content-Type-Options header

Minor Shown in the checker's Security tab as “No X-Content-Type-Options header”.

The header is missing or doesn't say nosniff. Without it, browsers may guess (“sniff”) what kind of file they've been sent rather than trusting the declared type. A file uploaded as an image could then be run as a script.

How to fix it

X-Content-Type-Options: nosniff

There is only one valid value. With it set, browsers refuse to run a script or stylesheet whose declared type is wrong (MDN), so a stylesheet served as text/plain stops applying. That is rare, and the fix is to correct the type on the server. Look at the page once after adding the header.

No clickjacking protection

Minor Shown in the checker's Security tab as “No clickjacking protection”.

The checker flags this when there is neither an X-Frame-Options header nor a frame-ancestors rule in a Content Security Policy. Either one controls which sites may show your pages inside a frame.

Why it matters

Clickjacking loads your real page inside an invisible frame on another site, so a visitor who thinks they're clicking a harmless button actually clicks yours: a login, a payment or a form (OWASP).

How to fix it

X-Frame-Options: SAMEORIGIN
# or, in your Content Security Policy:
Content-Security-Policy: frame-ancestors 'self'

Use DENY or frame-ancestors 'none' if nothing should ever frame your site. If a partner legitimately embeds one of your pages, list their address in frame-ancestors. The old ALLOW-FROM option of X-Frame-Options no longer works in current browsers (MDN).

No Content Security Policy

Minor Shown in the checker's Security tab as “No Content Security Policy”.

Your page has no Content-Security-Policy header. A Content Security Policy (CSP) is a list of the places your page may load scripts, styles, images, fonts and frames from. The browser refuses everything else.

Why it matters

If an attacker slips code into your site through a vulnerable plugin, an old library or a form, the policy can stop that code loading from their server or running at all. It's the main defence against cross-site scripting (MDN: CSP guide).

How to fix it

  1. List what the site loads: analytics, fonts, maps, booking widgets, chat, video embeds.
  2. Write a policy and send it first as Content-Security-Policy-Report-Only. The browser enforces nothing but logs every would-be block in the Console (F12).
  3. Browse every page, add the domains that show up as blocked, then rename the header to Content-Security-Policy. The checker only counts the enforced header.
Content-Security-Policy: default-src 'self'; script-src 'self' https://www.googletagmanager.com; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; frame-ancestors 'self'; object-src 'none'; base-uri 'self'

This is an example to adapt, not a policy to paste. Many WordPress themes and plugins rely on inline scripts, which need 'unsafe-inline'. That weakens the policy, but it still blocks scripts from unknown domains. OWASP's CSP cheat sheet explains the stricter nonce-based approach.

No Permissions-Policy header

Minor Shown in the checker's Security tab as “No Permissions-Policy header”.

The Permissions-Policy header switches off browser features your site doesn't use, such as camera, microphone and location. Without it, a third-party script running on your page can prompt visitors for those, and the prompt shows your site's name.

How to fix it

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

Empty brackets mean “no one”. If a feature is genuinely used, such as a store finder that asks for location, allow your own site with geolocation=(self). Chrome and Edge enforce this header; some other browsers don't yet (MDN). It costs nothing in those browsers either way.

Minor Shown in the checker's Security tab as “2 of 3 cookies without Secure/HttpOnly flags” (with your own numbers).

The checker reads the Set-Cookie headers your homepage sends and counts each cookie missing either flag. Secure means the cookie is only sent over https. HttpOnly means scripts on the page can't read it. Cookies set by JavaScript, such as most analytics cookies, aren't in the headers and aren't counted.

Why it matters

A cookie without Secure can leak over an insecure connection. One without HttpOnly can be stolen by any script injected into the page. For a login or session cookie, either leak lets someone act as that visitor (MDN: Set-Cookie).

How to fix it

Find out which cookie it is (curl -sI https://yourbusiness.com | grep -i set-cookie). The name usually points to the source: PHPSESSID is PHP, and others belong to a plugin, your host's cache or a load balancer. Then:

  • PHP sessions: in php.ini (cPanel: MultiPHP INI Editor) set session.cookie_secure = 1, session.cookie_httponly = 1 and session.cookie_samesite = Lax.
  • A plugin or app: update it, or ask its developer. Cookies should be set with Secure; HttpOnly; SameSite=Lax.
  • nginx as a proxy (1.19.3 and later): proxy_cookie_flags ~ secure httponly samesite=lax;

One honest exception: a cookie your own scripts must read, such as a cookie-consent choice, can't be HttpOnly. It should still be Secure, and it shouldn't hold anything sensitive.

Sources

Common questions

What security headers should every website have?

At minimum Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options (or CSP frame-ancestors), a Content-Security-Policy and a Permissions-Policy, with Secure and HttpOnly on session cookies. The OWASP HTTP Headers Cheat Sheet lists recommended values.

Can security headers break my website?

Most can't. A Content Security Policy can block scripts the site needs, which is why you test it in Report-Only mode first. HSTS can lock visitors out of a subdomain that has no certificate, so start with a short max-age and leave out includeSubDomains until every subdomain works over https.

Should I submit my site to the HSTS preload list?

Usually not. hstspreload.org itself says preloading isn't recommended for most sites, removal takes months, and the regular HSTS header already gives most of the benefit.

Is X-Frame-Options still needed if I have a CSP?

If your policy includes frame-ancestors, modern browsers use that and ignore X-Frame-Options. Sending both does no harm and covers very old browsers.

How do I add security headers on WordPress?

Add them at the server (.htaccess on Apache or cPanel hosting, the server block on nginx), at Cloudflare with a Response Header Transform Rule, or with a security plugin that shows the exact values. Server-level is the most reliable because it covers every file, not just pages WordPress generates.

Do security headers help SEO?

Not directly; Google doesn't rank on them. HSTS supports HTTPS, which is a ranking signal, and the headers reduce the chance of a hacked site, which can lose rankings fast.

See which headers your site sends. The website checker reads every header a browser receives and links each missing one to its section here, along with your certificate, speed and SEO. If you'd rather hand the server side over, we set and maintain these on the sites we look after.

Explore Remidiy