How to Secure Your Netlify App
Netlify provides hosting, serverless functions, and edge computing. While the platform handles infrastructure security, you need to configure security headers, protect your functions, and manage environment variables properly. This guide covers all the essentials.
Find security issues automatically before attackers do.
Follow These Steps
Add security headers with _headers file
Create a _headers file in your publish directory to add security headers to all responses.
# _headers (in your publish directory)
/*
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 0
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https:; frame-ancestors 'none'You can also configure headers in netlify.toml. The _headers file takes precedence over netlify.toml for the same paths.
Configure environment variables securely
Use Netlify environment variables for all secrets. Set different values for production and deploy previews.
# In Netlify Dashboard > Site settings > Environment variables
# Set scope for each variable:
# - Production: Real API keys
# - Deploy previews: Test/sandbox keys
# - Branch deploys: Test/sandbox keys
# In netlify.toml, reference them:
[build.environment]
NODE_ENV = "production"
# Never put secrets in netlify.toml - use the dashboardMark sensitive environment variables as "Secret" in the Netlify dashboard. This prevents them from appearing in build logs.
Secure Netlify Functions
Add authentication and input validation to every Netlify Function that handles sensitive operations.
// netlify/functions/create-order.ts
import { Handler } from '@netlify/functions'
import { z } from 'zod'
const OrderSchema = z.object({
productId: z.string().min(1),
quantity: z.number().int().min(1).max(99)
})
export const handler: Handler = async (event) => {
// Check auth
const token = event.headers.authorization?.replace('Bearer ', '')
if (!token) {
return { statusCode: 401, body: JSON.stringify({ error: 'Unauthorized' }) }
}
// Validate input
const parsed = OrderSchema.safeParse(JSON.parse(event.body || '{}'))
if (!parsed.success) {
return { statusCode: 400, body: JSON.stringify({ error: 'Invalid input' }) }
}
// Process order
return { statusCode: 200, body: JSON.stringify({ success: true }) }
}Protect deploy previews
Deploy previews are public by default. Use Netlify password protection or branch-specific access controls to restrict access.
# netlify.toml
[context.deploy-preview]
# Use different environment for previews
[context.deploy-preview.environment]
STRIPE_KEY = "sk_test_..."
DATABASE_URL = "postgresql://test-db/..."Configure redirect rules securely
Set up proper redirects to enforce HTTPS and prevent open redirects.
# _redirects file
# Force HTTPS
http://yourdomain.com/* https://yourdomain.com/:splat 301!
# Proxy API calls through Netlify (hides backend URL)
/api/* https://your-backend.com/api/:splat 200
# SPA fallback (must be last)
/* /index.html 200Add rate limiting with Netlify Edge Functions
Use Edge Functions to rate limit requests before they hit your serverless functions.
// netlify/edge-functions/rate-limit.ts
import { Context } from 'https://edge.netlify.com'
const WINDOW_MS = 60000
const MAX_REQUESTS = 60
const requests = new Map<string, { count: number; resetAt: number }>()
export default async (request: Request, context: Context) => {
const ip = context.ip
const now = Date.now()
const record = requests.get(ip)
if (record && record.resetAt > now) {
if (record.count >= MAX_REQUESTS) {
return new Response('Too Many Requests', { status: 429 })
}
record.count++
} else {
requests.set(ip, { count: 1, resetAt: now + WINDOW_MS })
}
return context.next()
}Scan your Netlify deployment
Run a VAS scan on your Netlify site URL to verify security headers, function protection, and overall security posture.
Check both your production domain and any deploy preview URLs.
What You'll Achieve
Your Netlify app now has security headers on all pages, protected environment variables, secured serverless functions, rate limiting, and deploy previews locked down. Your site is hardened against common web attacks.
Common Mistakes to Avoid
Mistake
Putting secrets in netlify.toml
Fix
netlify.toml is committed to git and visible to anyone with repo access. Always use the Netlify dashboard for secrets.
Mistake
Not setting the _headers file in the correct directory
Fix
The _headers file must be in your publish directory (usually build/ or dist/). If it is in the project root, it will not be deployed.
Mistake
Using the same API keys for deploy previews and production
Fix
Set different environment variable values for each deploy context. Use test/sandbox keys for previews to prevent accidental production data access.
Frequently Asked Questions
Does Netlify provide security headers by default?
Netlify provides HTTPS automatically but does not add application security headers. You must configure headers via a _headers file or netlify.toml.
Are Netlify Functions secure by default?
Netlify Functions are publicly accessible endpoints. You must add your own authentication and input validation. They have a default timeout and memory limit which provides some protection against abuse.
How do I hide my backend URL when using Netlify?
Use Netlify redirects to proxy API calls. Set up a redirect rule like /api/* -> https://your-backend.com/:splat with status 200. This hides the backend URL from the browser.
Ready to Secure Your App?
VAS automatically scans your deployed app for the security issues covered in this guide. Get actionable results in minutes.
Start Security Scan