1dollar.website

How to add security headers to your website

Security headers are short instructions your site sends with every page, telling browsers to always use HTTPS, refuse to be framed by other sites, and limit what scripts can do. You add them once in your hosting config, and the config for your platform is below.

Last updated October 30, 2026

Prefer to fill in a form? Use the free security headers generator or the CSP generator and checker.

Not sure whether your site has this?

Paste your address. The checkup is free and shows what's flagged.

What our checkup looks for

Our free checkup loads your home page and reads the headers that come back. It flags each recommended header that's missing, an HSTS setting that expires too quickly, and a Content Security Policy that still allows inline scripts.

Add the five basic headers in one go

These five headers are safe for almost every site: they don't change how your pages look or work. Pick your platform below and paste the block where it says.

Strict-Transport-Security tells browsers to only ever use HTTPS. X-Frame-Options stops other sites from showing your pages inside a frame. X-Content-Type-Options stops browsers from guessing a file is a script. Referrer-Policy keeps full page addresses private when visitors click a link to another site. Permissions-Policy stops embedded content from asking for the camera, microphone or location.

Choose where your site runs

Vercel
  1. Open (or create) vercel.json in the root of your project.
  2. Add the headers block below, commit, and redeploy.
json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=31536000; includeSubDomains"
        },
        {
          "key": "X-Frame-Options",
          "value": "SAMEORIGIN"
        },
        {
          "key": "X-Content-Type-Options",
          "value": "nosniff"
        },
        {
          "key": "Referrer-Policy",
          "value": "strict-origin-when-cross-origin"
        },
        {
          "key": "Permissions-Policy",
          "value": "camera=(), microphone=(), geolocation=()"
        }
      ]
    }
  ]
}
Netlify
  1. Create a file named _headers in your publish folder (often public/ or dist/).
  2. Paste the lines below and redeploy.
Copy this
/*
  Strict-Transport-Security: max-age=31536000; includeSubDomains
  X-Frame-Options: SAMEORIGIN
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()
Cloudflare
  1. In the Cloudflare dashboard, open your domain, then Rules > Transform Rules > Modify Response Header.
  2. Create a rule that matches all incoming requests.
  3. For each header below choose 'Set static', enter the name and value, then Deploy.
Copy this
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Nginx
  1. Open your site's server block (often /etc/nginx/sites-available/your-site).
  2. Add these lines inside the server { } block for port 443.
  3. Test with sudo nginx -t, then reload with sudo systemctl reload nginx.
nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" 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=()" always;
Apache
  1. Open your site's .htaccess file or virtual host config.
  2. Paste the block below near the top, outside the '# BEGIN WordPress' section, and save.
  3. Make sure mod_headers is enabled (sudo a2enmod headers), then reload Apache.
apache
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  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=()"
</IfModule>
WordPress
  1. Open the .htaccess file in your WordPress root folder (via your host's file manager or FTP). Make a backup copy first.
  2. Paste the block below near the top, outside the '# BEGIN WordPress' section, and save.
  3. If your host runs Nginx instead of Apache, or doesn't allow .htaccess, a plugin like 'Headers Security Advanced & HSTS WP' can set these instead.
apache
<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  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=()"
</IfModule>
Next.js
  1. Open next.config.js (or next.config.mjs).
  2. Add the headers() function below and redeploy.
javascript
module.exports = {
  async headers() {
    return [{
      source: "/(.*)",
      headers: [
        { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains" },
        { key: "X-Frame-Options", value: "SAMEORIGIN" },
        { key: "X-Content-Type-Options", value: "nosniff" },
        { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
        { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
      ],
    }];
  },
};
Express
  1. Add this middleware before your routes, or install the helmet package, which sets these for you.
javascript
app.use((req, res, next) => {
  res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
  res.setHeader("X-Frame-Options", "SAMEORIGIN");
  res.setHeader("X-Content-Type-Options", "nosniff");
  res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
  res.setHeader("Permissions-Policy", "camera=(), microphone=(), geolocation=()");
  next();
});
IIS
  1. Open web.config in your site root.
  2. Add the block below inside <system.webServer> and save; IIS picks it up immediately.
xml
<httpProtocol>
  <customHeaders>
    <add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains" />
    <add name="X-Frame-Options" value="SAMEORIGIN" />
    <add name="X-Content-Type-Options" value="nosniff" />
    <add name="Referrer-Policy" value="strict-origin-when-cross-origin" />
    <add name="Permissions-Policy" value="camera=(), microphone=(), geolocation=()" />
  </customHeaders>
</httpProtocol>
GitHub Pages
  1. GitHub Pages doesn't let you set response headers.
  2. The usual fix is to put your domain behind Cloudflare (free plan) and add the headers there with a Transform Rule.
  3. As a partial fix for the Content-Security-Policy only, you can add a meta tag to your page's <head> (frame-ancestors won't work in a meta tag).
Copy this
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Shopify
  1. Shopify controls your site's server, so you can't add these headers directly.
  2. Send the request below to Shopify support. If this matters for your business (e.g. a compliance review), a proxy such as Cloudflare in front of your domain can add them.
Copy this
Hi Shopify support, a security scan of my store/site flagged missing security headers. Could you confirm whether Shopify can set these, or whether they're planned?

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Squarespace
  1. Squarespace controls your site's server, so you can't add these headers directly.
  2. Send the request below to Squarespace support. If this matters for your business (e.g. a compliance review), a proxy such as Cloudflare in front of your domain can add them.
Copy this
Hi Squarespace support, a security scan of my store/site flagged missing security headers. Could you confirm whether Squarespace can set these, or whether they're planned?

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Wix
  1. Wix controls your site's server, so you can't add these headers directly.
  2. Send the request below to Wix support. If this matters for your business (e.g. a compliance review), a proxy such as Cloudflare in front of your domain can add them.
Copy this
Hi Wix support, a security scan of my store/site flagged missing security headers. Could you confirm whether Wix can set these, or whether they're planned?

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Webflow
  1. Webflow controls your site's server, so you can't add these headers directly.
  2. Send the request below to Webflow support. If this matters for your business (e.g. a compliance review), a proxy such as Cloudflare in front of your domain can add them.
Copy this
Hi Webflow support, a security scan of my store/site flagged missing security headers. Could you confirm whether Webflow can set these, or whether they're planned?

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()

HSTS: always use HTTPS

Even when your site redirects to HTTPS, a visitor's very first request can go out over plain HTTP, and on public Wi-Fi someone can intercept it. Strict-Transport-Security makes the browser skip that unsafe first step on every later visit.

Use max-age=31536000 (one year). Shorter values, like a day, mean visitors lose the protection between visits. includeSubDomains applies it to every subdomain too, so only add it once each subdomain you use works over HTTPS.

Clickjacking: stop other sites framing yours

Without frame protection, a scam page can load your site in an invisible frame and trick visitors into clicking your buttons, like confirming an order or changing a setting. X-Frame-Options: SAMEORIGIN lets only your own site frame your pages.

If you use a Content Security Policy, its frame-ancestors setting does the same job. Keeping both is fine.

Content Security Policy: add it in report-only mode first

A Content Security Policy lists where your pages may load scripts, styles and images from. If an attacker manages to inject a script, through a plugin bug or a compromised ad, the browser refuses to run it.

It's the one header that can break things: a strict policy blocks anything you forgot to list, like a chat widget or analytics. So start with Content-Security-Policy-Report-Only, which only reports problems in the browser console. After a week with nothing broken, rename it to Content-Security-Policy.

A policy containing 'unsafe-inline' for scripts allows exactly what injected attacks use, so it protects very little. Removing it usually means moving inline scripts into files, which is a job for a developer.

Choose where your site runs

Netlify
  1. A Content Security Policy can break parts of a site if it's too strict, so start in report-only mode, which changes nothing for visitors.
  2. Create a file named _headers in your publish folder (often public/ or dist/).
  3. Paste the lines below and redeploy.
  4. After a week with no broken features (check your browser's console), rename the header to Content-Security-Policy to enforce it.
Copy this
/*
  Content-Security-Policy-Report-Only: default-src 'self' https:; img-src 'self' https: data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; upgrade-insecure-requests
Vercel
  1. A Content Security Policy can break parts of a site if it's too strict, so start in report-only mode, which changes nothing for visitors.
  2. Open (or create) vercel.json in the root of your project.
  3. Add the headers block below, commit, and redeploy.
  4. After a week with no broken features (check your browser's console), rename the header to Content-Security-Policy to enforce it.
json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Content-Security-Policy-Report-Only",
          "value": "default-src 'self' https:; img-src 'self' https: data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
        }
      ]
    }
  ]
}
Nginx
  1. A Content Security Policy can break parts of a site if it's too strict, so start in report-only mode, which changes nothing for visitors.
  2. Open your site's server block (often /etc/nginx/sites-available/your-site).
  3. Add these lines inside the server { } block for port 443.
  4. Test with sudo nginx -t, then reload with sudo systemctl reload nginx.
  5. After a week with no broken features (check your browser's console), rename the header to Content-Security-Policy to enforce it.
nginx
add_header Content-Security-Policy-Report-Only "default-src 'self' https:; img-src 'self' https: data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; upgrade-insecure-requests" always;
Apache
  1. A Content Security Policy can break parts of a site if it's too strict, so start in report-only mode, which changes nothing for visitors.
  2. Open your site's .htaccess file or virtual host config.
  3. Paste the block below near the top, outside the '# BEGIN WordPress' section, and save.
  4. Make sure mod_headers is enabled (sudo a2enmod headers), then reload Apache.
  5. After a week with no broken features (check your browser's console), rename the header to Content-Security-Policy to enforce it.
apache
<IfModule mod_headers.c>
  Header always set Content-Security-Policy-Report-Only "default-src 'self' https:; img-src 'self' https: data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
</IfModule>

How to check it worked

Run this in a terminal (swap in your address), or run our free checkup again. Each header you added should appear.

bash
curl -sI https://example.com | grep -iE 'strict-transport|x-frame|x-content-type|referrer-policy|permissions-policy|content-security'

Common mistakes

  • Adding the headers in two places (say, your host and Cloudflare) with different values. Browsers may see both; pick one place.
  • Switching on an enforced Content Security Policy straight away and breaking checkout or forms. Use report-only first.
  • Submitting your domain to the HSTS preload list before every subdomain supports HTTPS. Removal from the list takes months.
  • Adding headers to a staging copy and forgetting production. Check the live address.

What this can't tell you

Headers protect visitors' browsers. They don't fix bugs in your site's code or plugins, and they can't protect a page whose server is already compromised.

Check your own site, free

The free checkup shows how many results are flagged and how serious. For $1, the report picks the ones that matter for your site and writes each fix for your platform, with your own domain filled in.