Skip to main content
Back to blog
April 28, 2026|8 min read|Antoine Duno

Netlify Security Headers: Complete Configuration With Examples

Netlify security headers are configured through a `_headers` file or `netlify.toml` — both approaches take minutes to implement and activate critical browser security mechanisms that Netlify does not enable by default.

ZeriFlow Team

1,344 words

Netlify Security Headers: Complete Configuration With Examples

Netlify security headers are an essential but often skipped configuration step for sites deployed on the platform. Out of the box, Netlify serves your content with excellent HTTPS support but no security-related response headers. Adding those headers takes minutes and protects your visitors from XSS, clickjacking, MIME sniffing, and other browser-level attacks.

This guide covers both configuration methods — the _headers file and netlify.toml — with complete, copy-paste examples you can deploy immediately.

Scan your Netlify site with ZeriFlow first to see your current header posture, then scan again after applying these changes to verify every improvement.


Method 1: The _headers File

The _headers file is Netlify's simplest header configuration mechanism. Place it in your publish directory (the folder Netlify deploys — public/, dist/, build/, or _site/ depending on your build tool).

Complete production-ready _headers file:

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  X-XSS-Protection: 1; mode=block
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=(), interest-cohort=()
  Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://api.yourdomain.com; frame-ancestors 'none';

/index.html
  Cache-Control: public, max-age=0, must-revalidate

/*.html
  Cache-Control: public, max-age=0, must-revalidate

/static/*
  Cache-Control: public, max-age=31536000, immutable

/assets/*
  Cache-Control: public, max-age=31536000, immutable

/.netlify/functions/*
  Cache-Control: no-store
  Content-Security-Policy: default-src 'none'

File placement by framework: - Gatsby: static/_headers (Gatsby copies the static/ directory to public/ during build) - Hugo: static/_headers - Jekyll: Place in the site root (not in _layouts or _includes) - Create React App: public/_headers - Astro: public/_headers - Eleventy: root of your project if your output is _site/


Method 2: netlify.toml Headers

The netlify.toml file at your project root provides the same functionality with TOML syntax, and it has one advantage: it also covers your Netlify Functions endpoints and can be version-controlled alongside your deployment configuration.

toml
[[headers]]
  for = '/*'
  [headers.values]
    X-Frame-Options = 'DENY'
    X-Content-Type-Options = 'nosniff'
    X-XSS-Protection = '1; mode=block'
    Referrer-Policy = 'strict-origin-when-cross-origin'
    Permissions-Policy = 'geolocation=(), microphone=(), camera=()'
    Strict-Transport-Security = 'max-age=31536000; includeSubDomains; preload'
    Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none';"

[[headers]]
  for = '/*.html'
  [headers.values]
    Cache-Control = 'public, max-age=0, must-revalidate'

[[headers]]
  for = '/static/*'
  [headers.values]
    Cache-Control = 'public, max-age=31536000, immutable'

[[headers]]
  for = '/.netlify/functions/*'
  [headers.values]
    Cache-Control = 'no-store'

Which method should you use? If you already have a netlify.toml in your project for build settings, add headers there to keep everything in one file. If you prefer simplicity, the _headers file is slightly easier to read and edit. Both are equally supported by Netlify.


HTTPS Redirects on Netlify

Netlify automatically provisions TLS certificates and enables HTTPS for all sites. HTTP to HTTPS redirects are also enabled by default for production domains. However, you should verify this and add explicit redirect rules for belt-and-suspenders protection.

In netlify.toml:

toml
[[redirects]]
  from = 'http://yourdomain.com/*'
  to = 'https://yourdomain.com/:splat'
  status = 301
  force = true

[[redirects]]
  from = 'http://www.yourdomain.com/*'
  to = 'https://yourdomain.com/:splat'
  status = 301
  force = true

The force = true setting ensures the redirect applies even if a matching file exists in your publish directory.

In the _headers file approach, add the redirect in a separate _redirects file in your publish directory:

http://yourdomain.com/* https://yourdomain.com/:splat 301!
http://www.yourdomain.com/* https://yourdomain.com/:splat 301!

The ! at the end of a redirect rule is Netlify's equivalent of force = true.


Content Security Policy: Practical Configuration

A generic CSP is a starting point, but you need to tune it for your specific stack and third-party integrations. Here are the most common scenarios:

Gatsby with Google Analytics:

/*
  Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https: https://www.google-analytics.com; connect-src 'self' https://www.google-analytics.com; frame-ancestors 'none';

Static site with Netlify Forms: Netlify Forms works by intercepting your HTML form submissions — no external script required, so no CSP changes needed for forms themselves. However, if you use reCAPTCHA with Netlify Forms:

  script-src 'self' 'unsafe-inline' https://www.google.com https://www.gstatic.com;
  frame-src https://www.google.com;

E-commerce with Stripe Elements:

  script-src 'self' 'unsafe-inline' https://js.stripe.com;
  connect-src 'self' https://api.stripe.com;
  frame-src 'self' https://js.stripe.com;

CSP violation reporting: Add a report endpoint to catch violations before they affect users:

  Content-Security-Policy: default-src 'self'; ...; report-uri https://your-report-endpoint.com/csp-violations;

Services like Report URI (report-uri.com) provide free CSP violation reporting dashboards.


Netlify Edge Functions for Dynamic Headers

For headers that need to be dynamic — based on the user's geography, authentication state, or A/B test variant — Netlify Edge Functions provide a powerful solution:

javascript
// netlify/edge-functions/security-headers.js
export default async (request, context) => {
  const response = await context.next()
  
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
  response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
  
  // Dynamic nonce for CSP
  const nonce = crypto.randomUUID()
  response.headers.set(
    'Content-Security-Policy',
    `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'; frame-ancestors 'none';`
  )
  
  return response
}

export const config = { path: '/*' }

Register this function in your netlify.toml:

toml
[[edge_functions]]
  function = 'security-headers'
  path = '/*'

Verifying With ZeriFlow

After deploying your header configuration, run a ZeriFlow scan to confirm: - All 6 core security headers are present - HSTS max-age is at least 6 months - CSP is present and syntactically valid - HTTP to HTTPS redirect returns a 301 - No mixed content issues


FAQ

### Q: What is the difference between the _headers file and netlify.toml headers? A: Functionally identical for static sites. netlify.toml has a slight advantage for teams because it consolidates all Netlify configuration in one file and can also configure Netlify Functions, build settings, and environment variables. The _headers file is marginally simpler for projects that do not use other Netlify-specific features.

### Q: Do headers in _headers override headers my SSG generates? A: Netlify applies _headers rules after your site is built. If your framework (Gatsby, Next.js on Netlify, etc.) generates its own response headers, the _headers file rules take precedence. Use ! at the start of a header value to force-override any application-generated value.

### Q: How do I verify my _headers file is in the correct directory? A: In the Netlify dashboard, navigate to your site → Deploys → select the latest deploy → and look at the deploy log. Netlify logs a message if it detects and processes a _headers file. You can also view the deploy file listing to confirm _headers is present in the root of your published directory.

### Q: Why does my Netlify site still show a security warning after adding headers? A: Common causes: the _headers file is in your source root but not your publish directory; the header syntax is incorrect (check for typos in header names); or a cached response is being served. Try a hard refresh (Ctrl+Shift+R) and verify with curl -I https://yourdomain.netlify.app.


Conclusion

Netlify security headers are quick to configure and immediately impactful. Whether you use the _headers file or netlify.toml, the result is the same: every response your site sends includes the browser security directives that protect your visitors from the most common web attacks.

The configurations in this guide are production-tested and cover the most common Netlify deployment patterns — static sites, Gatsby, Hugo, and sites using Netlify Functions.

Scan your Netlify site with ZeriFlow after deploying to get an independent, objective verification that your headers are correctly configured and complete.

Ready to check your site?

Run a free security scan in 30 seconds.

Related articles

Keep reading