Netlify Security Guide: Functions, Environment Variables, and Forms

Share

TL;DR

Netlify provides automatic SSL, DDoS protection, and secure infrastructure. Your focus should be on environment variables (keep secrets server-side), securing Netlify Functions with authentication, protecting forms from spam, and configuring security headers in netlify.toml. Deploy previews are public by default, so be careful with sensitive features.

What Netlify Handles Automatically

Netlify's infrastructure provides several security benefits out of the box:

  • Automatic SSL: All sites get free SSL certificates from Let's Encrypt
  • CDN: Content is served from a global CDN with built-in protection
  • DDoS mitigation: Enterprise-grade DDoS protection included
  • Atomic deploys: Deploys are atomic, preventing partial deployments
  • Immutable deploys: Each deploy is immutable and can be rolled back

Environment Variables on Netlify

Unlike Vercel's NEXT_PUBLIC_ convention, Netlify environment variables work differently depending on your framework:

FrameworkPublic PrefixExample
React (Create React App)REACT_APP_REACT_APP_API_URL
Next.jsNEXT_PUBLIC_NEXT_PUBLIC_SITE_URL
GatsbyGATSBY_GATSBY_API_KEY
Vue/ViteVITE_VITE_APP_URL

Key rule: Variables with framework-specific public prefixes are bundled into your JavaScript and visible in the browser. Never put secrets in these variables.

Setting Environment Variables

You can set environment variables in multiple places:

  1. Netlify Dashboard: Site settings > Build & deploy > Environment
  2. netlify.toml: For non-sensitive values only
  3. Netlify CLI: For local development
netlify.toml - non-sensitive only
[build]
  publish = "dist"
  command = "npm run build"

[build.environment]
  NODE_VERSION = "18"
  # OK: non-sensitive configuration
  SITE_URL = "https://mysite.netlify.app"

  # NEVER put secrets here - this file is in your repo!

Netlify Functions Security

Netlify Functions are serverless functions that run on AWS Lambda. They can access your environment variables securely:

Secure function with authentication
// netlify/functions/api.js
exports.handler = async (event, context) => {
  // Check for authentication
  const authHeader = event.headers.authorization;

  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return {
      statusCode: 401,
      body: JSON.stringify({ error: 'Unauthorized' }),
    };
  }

  const token = authHeader.substring(7);

  // Verify the token (example with JWT)
  try {
    const user = verifyToken(token);

    // Access secrets safely (server-side only)
    const apiKey = process.env.SECRET_API_KEY;

    // Process request...
    return {
      statusCode: 200,
      body: JSON.stringify({ data: 'Success' }),
    };
  } catch (error) {
    return {
      statusCode: 401,
      body: JSON.stringify({ error: 'Invalid token' }),
    };
  }
};

Input Validation in Functions

Validate input data
exports.handler = async (event) => {
  // Only allow POST
  if (event.httpMethod !== 'POST') {
    return { statusCode: 405, body: 'Method Not Allowed' };
  }

  // Parse and validate body
  let data;
  try {
    data = JSON.parse(event.body);
  } catch {
    return { statusCode: 400, body: 'Invalid JSON' };
  }

  // Validate required fields
  if (!data.email || !isValidEmail(data.email)) {
    return { statusCode: 400, body: 'Valid email required' };
  }

  if (!data.message || data.message.length > 1000) {
    return { statusCode: 400, body: 'Message required (max 1000 chars)' };
  }

  // Process valid data...
};

Netlify Forms Security

Netlify Forms is convenient but needs spam protection:

Form with honeypot spam protection
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field">
  <!-- Hidden honeypot field - bots fill this, humans don't -->
  <p class="hidden">
    <label>Don't fill this out: <input name="bot-field" /></label>
  </p>

  <p>
    <label>Email: <input type="email" name="email" required /></label>
  </p>
  <p>
    <label>Message: <textarea name="message" required></textarea></label>
  </p>
  <p>
    <button type="submit">Send</button>
  </p>
</form>

<style>
  .hidden { display: none; }
</style>

reCAPTCHA Integration

Form with reCAPTCHA
<form name="contact" method="POST" data-netlify="true" data-netlify-recaptcha="true">
  <p>
    <label>Email: <input type="email" name="email" required /></label>
  </p>
  <p>
    <label>Message: <textarea name="message" required></textarea></label>
  </p>
  <div data-netlify-recaptcha="true"></div>
  <p>
    <button type="submit">Send</button>
  </p>
</form>

Security Headers Configuration

Configure security headers in netlify.toml:

netlify.toml security headers
[[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 = "camera=(), microphone=(), geolocation=()"

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

[[headers]]
  for = "/api/*"
  [headers.values]
    Access-Control-Allow-Origin = "https://yourdomain.com"
    Access-Control-Allow-Methods = "GET, POST, OPTIONS"

Deploy Previews Security

Netlify creates deploy previews for pull requests. Security considerations:

  • Deploy previews are public by default
  • They share environment variables with production (unless configured otherwise)
  • URLs follow a predictable pattern

Deploy Preview Protection Options

Use different environment variables for deploy previews

Enable password protection (Team/Business plans)

Disable deploy previews for sensitive repos

Don't connect production databases to previews

Netlify Identity Security

If using Netlify Identity for authentication:

Protect routes with Netlify Identity
// Check identity in a function
const { user } = context.clientContext;

if (!user) {
  return {
    statusCode: 401,
    body: JSON.stringify({ error: 'Please log in' }),
  };
}

// Check user roles
if (!user.app_metadata.roles?.includes('admin')) {
  return {
    statusCode: 403,
    body: JSON.stringify({ error: 'Admin access required' }),
  };
}

Netlify Security Checklist

Before Going Live

No secrets in netlify.toml

No secrets in public environment variables

Functions validate all input

Functions check authentication

Forms have spam protection

Security headers configured

Deploy previews are protected or use test data

Redirect rules don't expose sensitive paths

Are environment variables in netlify.toml secure?

No. netlify.toml is typically committed to your repository and is public. Only put non-sensitive configuration there. Set secrets through the Netlify Dashboard or CLI.

Can people access my Netlify Functions code?

No, the function source code is not publicly accessible. Users can only interact with functions through HTTP requests. Your code runs securely on Netlify's infrastructure.

How do I prevent spam on Netlify Forms?

Use a combination of honeypot fields and reCAPTCHA. Netlify also has built-in spam filtering. For high-value forms, consider using a custom function with additional validation.

Are deploy preview URLs discoverable?

Deploy preview URLs follow a pattern (deploy-preview-{number}--{site-name}.netlify.app) so they can be guessed. For sensitive sites, use password protection or disable deploy previews.

Deploying to Netlify?

Scan your project for security issues before going live.

Start Free Scan
Tool & Platform Guides

Netlify Security Guide: Functions, Environment Variables, and Forms