How to Configure Cloudflare Access for Self-Hosted OpenSEO: Complete Setup Guide

Configure Cloudflare Access for self-hosted OpenSEO by setting the TEAM_DOMAIN and POLICY_AUD environment variables and deploying the Worker—the middleware in src/middleware/ensure-user/cloudflareAccess.ts automatically verifies the cf-access-jwt-assertion header against your Zero Trust policies.

When you self-host OpenSEO from the every-app/open-seo repository on Cloudflare Workers, the platform protects your deployment using Cloudflare Access (Zero Trust) to prevent unauthorized API usage. This guide explains how to configure the required environment variables, understand the JWT verification flow, and deploy your secure instance.

How Cloudflare Access Protects OpenSEO

OpenSEO runs as a Cloudflare Worker, and in self-hosted mode, all incoming requests pass through an Access gate before reaching your application logic. Cloudflare injects a cf-access-jwt-assertion header into every authenticated request, which the Worker middleware validates against your team's public keys.

The core verification logic resides in src/middleware/ensure-user/cloudflareAccess.ts. When a request arrives, the resolveCloudflareAccessContext function extracts this JWT, fetches the JSON Web Key (JWK) set from ${TEAM_DOMAIN}/cdn-cgi/access/certs, and validates the token's issuer (TEAM_DOMAIN) and audience (POLICY_AUD). Upon successful verification, the middleware extracts the user's sub (user ID) and email, creating a secure workspace context for the request lifecycle.

Required Environment Variables

Before deploying, you must configure two critical variables that enable Access verification:

  • TEAM_DOMAIN: Your Cloudflare Access team URL (e.g., https://myteam.cloudflareaccess.com). The validator in src/shared/selfhost-checks.ts enforces HTTPS protocol via the validateTeamDomain function.
  • POLICY_AUD: The audience tag from your Zero Trust Access application. This identifies the specific Access policy protecting the Worker.

If either variable is missing or malformed, resolveCloudflareAccessContext throws an AppError with code AUTH_CONFIG_MISSING, directing you to docs/SELF_HOSTING_CLOUDFLARE.md.

Step-by-Step Configuration

Follow these steps to configure Cloudflare Access for your self-hosted OpenSEO instance.

1. Prepare Your Environment File

Copy the example configuration and add your Cloudflare Access credentials:

cp .env.selfhost.example .env.selfhost

Edit .env.selfhost to include:


# Required API keys

DATAFORSEO_API_KEY=your_api_key_here
ACCESS_ALLOWED_EMAILS=admin@yourdomain.com

# Cloudflare Access configuration

TEAM_DOMAIN=https://your-team.cloudflareaccess.com
POLICY_AUD=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The validateTeamDomain function strictly requires a full HTTPS URL. If you omit the protocol, the validator returns a specific error message prompting you to add the https:// prefix.

2. Authenticate with Cloudflare

Log in to Cloudflare using the Alchemy CLI with the appropriate scopes:

pnpm alchemy login

When prompted, grant the access:write scope. If you previously authenticated without this permission, re-run:

pnpm alchemy login --configure

Then bootstrap the internal state store:

pnpm alchemy cloudflare bootstrap

This deploys the necessary infrastructure for managing deployment state.

3. Deploy the Self-Hosted Stack

Execute the deployment command:

pnpm deploy:selfhost --yes

This script provisions D1, KV, and R2 resources, runs database migrations, and deploys the Workers. If TEAM_DOMAIN and POLICY_AUD are present, the script skips automatic Access application creation, assuming you manage the policy manually in the Zero Trust dashboard.

4. Verify the Integration

After deployment, visit the Worker URL printed in the terminal. You should be redirected to the Cloudflare Access login screen. After authentication, OpenSEO loads normally.

For troubleshooting, query the health endpoint:

curl https://<worker-hostname>/api/health

This reports runtime configuration checks and database connectivity status.

5. (Optional) Manual Access Policy Management

If you prefer direct control over your Zero Trust policies, create the Access application manually in the Cloudflare dashboard. Add the emails specified in ACCESS_ALLOWED_EMAILS to the allow list. During redeployment, the script detects your manual configuration via the presence of TEAM_DOMAIN and POLICY_AUD and preserves your policy settings.

Understanding the JWT Verification Code

The middleware implementation demonstrates precise security controls. Here is the critical verification path from src/middleware/ensure-user/cloudflareAccess.ts:

export async function resolveCloudflareAccessContext(
  headers: Headers,
): Promise<EnsuredUserContext> {
  const teamDomain = env.TEAM_DOMAIN
    ? getValidatedTeamDomain(env.TEAM_DOMAIN)
    : null;
  const policyAud = env.POLICY_AUD?.trim() || null;

  if (!teamDomain || !policyAud) {
    throw new AppError(
      "AUTH_CONFIG_MISSING",
      `Missing Cloudflare Access configuration: set ${missing} on the deployment. See docs/SELF_HOSTING_CLOUDFLARE.md.`,
    );
  }

  const token = headers.get("cf-access-jwt-assertion");
  // ... JWT verification using jose library ...
}

The getValidatedTeamDomain helper calls validateTeamDomain from src/shared/selfhost-checks.ts, which performs strict URL parsing:

export function validateTeamDomain(value: string): TeamDomainResult {
  const normalized = value.trim().replace(/\/+$/, "");
  try {
    const parsed = new URL(normalized);
    if (parsed.protocol !== "https:") {
      throw new Error("TEAM_DOMAIN must use https");
    }
    return { ok: true, origin: parsed.origin };
  } catch {
    return {
      ok: false,
      message:
        "TEAM_DOMAIN must be a full https URL like https://your-team.cloudflareaccess.com" +
        (normalized && !normalized.includes("://")
          ? ` — add the https:// prefix to "${normalized}"`
          : ""),
    };
  }
}

This validation ensures your team domain uses HTTPS and formats correctly before any JWT verification occurs.

Summary

  • Cloudflare Access protects self-hosted OpenSEO by verifying JWTs injected by Cloudflare's edge network.
  • Configure TEAM_DOMAIN and POLICY_AUD in .env.selfhost to enable authentication; the validateTeamDomain function in src/shared/selfhost-checks.ts enforces strict HTTPS validation.
  • The middleware in src/middleware/ensure-user/cloudflareAccess.ts handles cf-access-jwt-assertion verification and extracts user contexts including sub and email.
  • Use pnpm deploy:selfhost --yes to provision infrastructure; the script respects manually configured Access policies when environment variables are pre-set.
  • Reference docs/SELF_HOSTING_CLOUDFLARE.md in the repository for additional deployment scenarios, and note that src/server/mcp/transport.ts uses the Access context with authMode: "cloudflare_access" to route authenticated requests.

Frequently Asked Questions

What happens if I don't set TEAM_DOMAIN and POLICY_AUD?

If these variables are missing, the resolveCloudflareAccessContext function throws an AppError with code AUTH_CONFIG_MISSING. The deployment will fail to authenticate requests, and the error message directs you to the self-hosting documentation to complete the configuration.

Can I use Cloudflare Access without the automatic deployment script?

Yes. If you manually create the Access application in the Cloudflare Zero Trust dashboard, simply provide the TEAM_DOMAIN and POLICY_AUD values in your environment file. The pnpm deploy:selfhost command detects these variables and skips Access application creation, preserving your manual policy configurations including allowed email lists.

How does OpenSEO validate the Cloudflare Access JWT?

The Worker fetches the JWK set from ${TEAM_DOMAIN}/cdn-cgi/access/certs and verifies the cf-access-jwt-assertion header using the jose library. It validates the issuer matches your TEAM_DOMAIN and the audience matches your POLICY_AUD. This logic is implemented in src/middleware/ensure-user/cloudflareAccess.ts.

Where can I find the POLICY_AUD value for my Access application?

The POLICY_AUD is the audience tag displayed in your Cloudflare Zero Trust dashboard under Access > Applications. Select your application and copy the Audience Tag value. This unique identifier ensures the Worker only accepts tokens generated for your specific Access policy.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →