TL;DR
Cloudflare Pages runs at the edge with built-in DDoS protection and automatic SSL. Use environment variables for secrets (they're encrypted), secure Pages Functions with proper authentication, and leverage Cloudflare's WAF for additional protection. Preview deployments are public, so protect sensitive features.
Cloudflare's Security Advantage
Deploying on Cloudflare Pages gives you access to Cloudflare's security infrastructure:
- Global edge network: Content served from 300+ locations worldwide
- DDoS protection: Automatic mitigation at no extra cost
- Automatic SSL: Free TLS certificates for all domains
- WAF integration: Web Application Firewall (on paid plans)
- Bot management: Protection against automated attacks
Environment Variables
Production vs Preview Variables
Cloudflare Pages lets you set different variables for production and preview:
# Production only
API_KEY=prod_key_xxxxx
# Preview only
API_KEY=test_key_xxxxx
# Both environments
NEXT_PUBLIC_SITE_URL=https://example.com
Use different secrets for preview and production. Preview deployments are public, and if secrets are compromised, you want to limit the damage.
Accessing Variables in Functions
// functions/api/data.js
export async function onRequest(context) {
// Access environment variables from context.env
const apiKey = context.env.API_KEY;
const dbUrl = context.env.DATABASE_URL;
// Use the secrets...
const response = await fetch('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});
return new Response(JSON.stringify(await response.json()), {
headers: { 'Content-Type': 'application/json' }
});
}
Pages Functions Security
Authentication in Functions
// functions/api/protected.js
export async function onRequest(context) {
const authHeader = context.request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
const token = authHeader.substring(7);
try {
// Verify the token (example with JWT)
const payload = await verifyJWT(token, context.env.JWT_SECRET);
// Continue with the request
return new Response(JSON.stringify({ user: payload.sub }), {
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
return new Response(JSON.stringify({ error: 'Invalid token' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
}
Input Validation
// functions/api/submit.js
export async function onRequest(context) {
if (context.request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
let body;
try {
body = await context.request.json();
} catch {
return new Response(JSON.stringify({ error: 'Invalid JSON' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
// Validate required fields
if (!body.email || typeof body.email !== 'string') {
return new Response(JSON.stringify({ error: 'Email required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(body.email)) {
return new Response(JSON.stringify({ error: 'Invalid email' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
// Process valid input...
}
Security Headers
Add security headers using a _headers file or Pages Functions:
/*
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=()
/api/*
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Cloudflare Access Integration
For additional protection, use Cloudflare Access to require authentication:
- Protect preview deployments with login
- Restrict access to specific email domains
- Add MFA requirements
- Integrate with identity providers (Google, Okta, etc.)
Cloudflare Access sits in front of your application, so users must authenticate before even reaching your site. Great for internal tools or staging environments.
WAF Configuration
On Cloudflare Pro and above, you can configure Web Application Firewall rules:
- Block SQL injection attempts
- Prevent XSS attacks
- Rate limit aggressive requests
- Block known bad user agents
# Block requests with SQL injection patterns
Rule: (http.request.uri.query contains "UNION SELECT")
Action: Block
# Rate limit API endpoints
Rule: (http.request.uri.path matches "^/api/")
Rate Limit: 100 requests per minute per IP
Cloudflare Pages Security Checklist
Before Going to Production
All secrets in environment variables
Different secrets for preview vs production
Functions validate all input
Functions check authentication where needed
Security headers configured
CORS limited to your domain
Consider Cloudflare Access for sensitive paths
WAF rules reviewed (if available)
Are my environment variables secure on Cloudflare Pages?
Yes, Cloudflare encrypts environment variables at rest. They're only accessible to your Pages Functions at runtime. They're not included in your static build output.
Can I protect preview deployments?
Preview deployments are public by default. Use Cloudflare Access to require authentication for preview URLs, or use different (non-sensitive) environment variables for previews.
How do Pages Functions compare to Workers?
Pages Functions are built on Workers technology. They run at the edge with the same security model. Pages Functions are just easier to set up for file-based routing within a Pages project.
Does Cloudflare see my traffic?
Cloudflare processes requests on your behalf. For end-to-end encryption of sensitive data, consider client-side encryption before transmission. Cloudflare has security certifications and doesn't access your data inappropriately.
Deploying to Cloudflare Pages?
Scan your project for security issues before going live.
Start Free Scan