Cloudflare Guide

Configure Better Auth for Cloudflare.

This guide covers what Better Auth needs on Cloudflare. Set up Better Auth with the installation guide first.

Node.js Compatibility

Better Auth uses Node.js APIs such as node:crypto and AsyncLocalStorage. Cloudflare Workers enable Node.js compatibility by default from compatibility date 2026-08-04. For an earlier date, add the nodejs_compat flag to your Wrangler configuration:

wrangler.jsonc
{
  "compatibility_flags": ["nodejs_compat"]
}

Environment Variables

Better Auth reads environment variables from process.env. With Node.js compatibility, Workers add variables and secrets to process.env from compatibility date 2025-04-01. Set the secret with Wrangler, and put local values in .dev.vars or .env:

npx wrangler secret put BETTER_AUTH_SECRET

For an earlier date, pass the values from env instead:

auth.ts
import { env } from "cloudflare:workers";
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  secret: env.BETTER_AUTH_SECRET,
});

Client IP

Requests to a Worker don't carry the client address in X-Forwarded-For, which Better Auth reads by default. Without it, rate limiting falls back to one shared bucket per path. Read the address from CF-Connecting-IP instead:

auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  advanced: {
    ipAddress: {
      ipAddressHeaders: ["cf-connecting-ip"],
    },
  },
});

Background Tasks

To run non-critical work after the response is sent, pass waitUntil from cloudflare:workers as advanced.backgroundTasks.handler.

Database Connections

Workers don't allow I/O across requests, so an auth instance at module scope cannot hold a client that keeps connections open, such as a pg or mysql2 pool, even through Hyperdrive. Use a driver that queries over HTTP instead. For details, see Cloudflare's connection guidance.

Cloudflare Workers KV

Cloudflare Workers KV is eventually consistent, cannot expire a key sooner than 60 seconds, and has no atomic operations, so it cannot meet the secondaryStorage contract. Use a store that can, such as Redis.