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:
/*
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
[[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:
| Context | Access to Env Vars | Security Implication |
|---|---|---|
| Build time | Yes | Variables can be baked into static files |
| Netlify Functions | Yes | Secure, 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.
// 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:
// 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:
# 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
# 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:
# 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
| Mistake | Risk | Prevention |
|---|---|---|
| No security headers | XSS, clickjacking | Add _headers file with security headers |
| Secrets in build output | Credential exposure | Use Functions for secret operations |
| Unprotected Functions | Unauthorized access | Add authentication to all Functions |
| Exposed build hooks | Unauthorized deploys | Keep hooks secret, regenerate if exposed |
| Open deploy previews | Information disclosure | Password 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