Cloudflare Pages Security Guide: Edge Deployment Best Practices

TL;DR

Cloudflare Pages runs at the edge with DDoS protection and automatic SSL built in. Put secrets in environment variables (they're encrypted), check authentication inside your Pages Functions, and add WAF rules on top if your plan has them. Preview deployments are public. Protect anything sensitive.

Cloudflare's Security Advantage

Deploy on Pages and you inherit a lot of Cloudflare's security infrastructure without configuring anything:

  • 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

You can set different variables for production and preview. Do it:

Environment variable scopes
# 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. If a preview secret leaks, you want it to be one that can't touch real data.

Accessing Variables in Functions

Pages Functions environment access
// 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

Protected function with auth check
// 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

Validate request data
// 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

The simplest way to add security headers is a _headers file at the root of your build output. Pages Functions work too:

_headers file
/*
  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

Cloudflare Access puts a login in front of a whole site or path. With it you can:

  • 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 nobody reaches the site until they've signed in. It's the right fit for internal tools and staging environments.

WAF Configuration

On Cloudflare Pro and above you can write Web Application Firewall rules. Common ones:

  • Block SQL injection attempts
  • Prevent XSS attacks
  • Rate limit aggressive requests
  • Block known bad user agents
Example WAF rule (Dashboard or API)
# 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 them at rest, and only your Pages Functions can read them at runtime. They don't end up 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 Workers under the hood, so they run at the edge with the same security model. The difference is setup: Functions give you file-based routing inside a Pages project with no extra config.

Does Cloudflare see my traffic?

It processes requests on your behalf, so yes, traffic passes through Cloudflare. If you need end-to-end encryption for sensitive data, encrypt it client-side before it's sent. Cloudflare holds security certifications and doesn't access your data inappropriately.

Deploying to Cloudflare Pages?

Scan your project for security issues before going live.

Tool & Platform Guides

Cloudflare Pages Security Guide: Edge Deployment Best Practices