Netlify Security Best Practices: Headers, Functions, and Deployment

TL;DR

The single highest-value thing you can do on Netlify is set security headers, via a _headers file or netlify.toml. Netlify ships none of them for you. The 7 practices below take about 40 minutes end to end. If you only get through two, make them the headers and Function authentication: those are what an attacker reaches from outside.

"Netlify makes deployment easy, but security is still your responsibility. Configure headers, protect your Functions, and never trust the client."

Best Practice 1: Configure Security Headers 5 min

You can do this in a _headers file or in netlify.toml. Either works:

_headers file (place in publish directory)
/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff
  X-XSS-Protection: 1; mode=block
  Referrer-Policy: strict-origin-when-cross-origin
  Permissions-Policy: camera=(), microphone=(), geolocation=()

# Stricter CSP for HTML pages
/*.html
  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

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

Alternative: netlify.toml Configuration

netlify.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"

[[headers]]
  for = "/*.html"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline'"

Best Practice 2: Secure Environment Variables 5 min

Netlify keeps environment variables to two places: the build, and your Functions. Nothing reaches the browser unless you put it there.

ContextAccess to Env VarsSecurity Implication
Build timeYesVariables can be baked into static files
Netlify FunctionsYesSecure, server-side only
Browser (static)No (unless baked in)Client-side code cannot read env vars

Important: Environment variables accessed during build can be included in your static bundle. Use Netlify Functions for operations requiring secrets instead of baking them into your frontend.

Safe pattern: Use Functions for secrets
// netlify/functions/send-email.js
exports.handler = async (event) => {
  // Secret only available server-side
  const apiKey = process.env.SENDGRID_API_KEY;

  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method not allowed' };
  }

  const { to, subject, body } = JSON.parse(event.body);

  // Send email using server-side secret
  // ...

  return { statusCode: 200, body: JSON.stringify({ success: true }) };
};

Best Practice 3: Secure Netlify Functions 10 min

A Netlify Function is a public HTTP endpoint, reachable by anyone who finds the URL whatever the UI in front of it does:

Secure Function pattern
// netlify/functions/protected-action.js
const jwt = require('jsonwebtoken');

exports.handler = async (event, context) => {
  // Only allow POST requests
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 405,
      body: JSON.stringify({ error: 'Method not allowed' })
    };
  }

  // Verify authentication
  const authHeader = event.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return {
      statusCode: 401,
      body: JSON.stringify({ error: 'Unauthorized' })
    };
  }

  try {
    const token = authHeader.substring(7);
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // Validate input
    const body = JSON.parse(event.body);
    if (!body.action || typeof body.action !== 'string') {
      return {
        statusCode: 400,
        body: JSON.stringify({ error: 'Invalid input' })
      };
    }

    // Process authenticated request
    const result = await processAction(decoded.userId, body.action);

    return {
      statusCode: 200,
      body: JSON.stringify(result)
    };
  } catch (error) {
    console.error('Function error:', error.message);
    return {
      statusCode: 401,
      body: JSON.stringify({ error: 'Invalid token' })
    };
  }
};

Best Practice 4: Use Deploy Contexts 5 min

Configure different settings for production, staging, and branch deploys:

netlify.toml with deploy contexts
# Production settings
[context.production]
  environment = { NODE_ENV = "production" }

[context.production.environment]
  API_URL = "https://api.yourdomain.com"

# Branch deploy settings (staging)
[context.branch-deploy]
  environment = { NODE_ENV = "staging" }

[context.branch-deploy.environment]
  API_URL = "https://staging-api.yourdomain.com"

# Deploy preview settings
[context.deploy-preview]
  environment = { NODE_ENV = "preview" }

# Set environment variables per context in Netlify Dashboard
# for sensitive values like API keys

Best Practice 5: Protect Deploy Previews 5 min

Deploy previews are how half-finished work ends up on a URL you forgot about. Two settings fix that:

Password Protection

  • Enable Site Protection in Site Settings > Access control
  • Set a password for branch deploys and deploy previews
  • Share password only with team members

Identity-Based Access

_redirects for Netlify Identity protection
# Protect staging site with Netlify Identity
/*  200!  Role=admin,editor

Best Practice 6: Configure Redirects Securely 5 min

Use _redirects or netlify.toml for secure routing:

_redirects with security considerations
# Force HTTPS
http://yourdomain.com/* https://yourdomain.com/:splat 301!
http://www.yourdomain.com/* https://yourdomain.com/:splat 301!
https://www.yourdomain.com/* https://yourdomain.com/:splat 301!

# Proxy API requests (hides backend URL)
/api/*  https://your-backend.com/api/:splat  200

# SPA fallback (but not for API routes)
/*  /index.html  200

Best Practice 7: Enable Security Features 5 min

Some of this is already built in. It just isn't switched on:

Netlify Security Features Checklist:

  • HTTPS enabled (automatic, but verify)
  • Asset optimization enabled (minification)
  • Deploy notifications configured
  • Build hooks secured (regenerate if exposed)
  • Forms spam protection enabled if using Netlify Forms
  • Audit log enabled (Team/Enterprise)

Common Netlify Security Mistakes

MistakeRiskPrevention
No security headersXSS, clickjackingAdd _headers file with security headers
Secrets in build outputCredential exposureUse Functions for secret operations
Unprotected FunctionsUnauthorized accessAdd authentication to all Functions
Exposed build hooksUnauthorized deploysKeep hooks secret, regenerate if exposed
Open deploy previewsInformation disclosurePassword protect previews

Official Resources: For the latest information, see Netlify Configuration Docs, Netlify Headers Documentation, and Netlify Functions Overview.

How do I add security headers on Netlify?

Create a _headers file in your publish directory, or add [[headers]] sections to netlify.toml. Either way you write rules against path patterns, and Netlify's CDN serves the headers on matched requests. There is nothing to deploy separately; it ships with your site.

Are Netlify environment variables secure?

They're encrypted at rest and only exposed to the build and to your Functions, so the storage side is fine. The leak almost always comes from your own code: a console.log in a Function, or a value pulled into the client bundle at build time because it carried a public prefix.

Should I use _headers or netlify.toml?

Both work. If headers are all you're configuring, _headers is less to read. If you're already using netlify.toml for redirects or build settings, put them there and keep the configuration in one file rather than two.

How do I protect Netlify Functions?

Check who is calling before you do anything else, with a JWT or an API key. Then validate the input, because a Function that trusts its request body is a Function anyone can drive. And strip detail from your error responses: a stack trace tells an attacker what to try next.

Further Reading

Put these practices into action with our step-by-step guides.

Verify Your Netlify Security

We'll check your live site for the headers this guide adds, and for the config that tends to slip through anyway.

Best Practices

Netlify Security Best Practices: Headers, Functions, and Deployment