Netlify Security Best Practices: Headers, Functions, and Deployment

Share

TL;DR

The #1 Netlify security best practice is configuring proper security headers via _headers file or netlify.toml. These 7 practices take about 40 minutes to implement and prevent 79% of common security issues in Netlify deployments. Focus on: adding security headers, protecting environment variables, securing Netlify Functions with authentication, and using deploy contexts for staging.

"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

Add security headers using either a _headers file or netlify.toml:

_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 environment variables are only available during build and in Functions:

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

Netlify Functions are serverless endpoints that need proper security:

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 can expose work in progress. Use these protections:

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

Netlify provides built-in security features. Enable them:

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 with header rules, or add [[headers]] sections to netlify.toml. Headers apply to matched paths and are served by Netlify's CDN.

Are Netlify environment variables secure?

Yes, environment variables are encrypted and only available during build and in Functions. However, be careful not to expose them by logging or including them in client-side code during build.

Should I use _headers or netlify.toml?

Both work. _headers is simpler for just headers, while netlify.toml offers more configuration options in one place. Choose based on your preference and project complexity.

How do I protect Netlify Functions?

Implement authentication (JWT, API keys) in your Functions, validate all input, use proper HTTP methods, and avoid exposing sensitive information in error responses. Never trust client input.

Verify Your Netlify Security

Scan your Netlify deployment for security headers and configuration issues.

Start Free Scan
Best Practices

Netlify Security Best Practices: Headers, Functions, and Deployment