# How to Set Up Self-Hosted OpenSEO with Cloudflare Access: Complete Deployment Guide

> Deploy self-hosted OpenSEO with Cloudflare Access in minutes. This guide covers provisioning D1, KV, R2, and Access for secure, private SEO analysis. Get started now.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: how-to-guide
- Published: 2026-09-06

---

**Self-hosted OpenSEO runs as a Cloudflare Worker protected by Cloudflare Access, using a single deployment command that provisions D1, KV, R2, and an Access application gated by the `ACCESS_ALLOWED_EMAILS` environment variable.**

This guide walks you through deploying OpenSEO as a secure, self-hosted SEO analytics platform on Cloudflare's edge infrastructure. The setup combines Cloudflare's serverless compute with enterprise-grade authentication, giving you full control over your data without infrastructure maintenance overhead.

## Architecture Overview

The self-hosted OpenSEO deployment consists of four core components working together on Cloudflare's platform:

- **Worker** – The application logic runs directly on Cloudflare's edge network via `pnpm deploy:selfhost`; requests execute close to users for minimal latency
- **Data Layer** – **D1** (SQLite) stores project metadata, **KV** handles configuration and caching, and **R2** stores binary assets and exports
- **Authentication** – **Cloudflare Access** creates an allow-policy from `ACCESS_ALLOWED_EMAILS`; users authenticate through Cloudflare's identity proxy before reaching the Worker
- **Managed OAuth** – Optional integration for **MCP (Model Context Protocol)** clients, enabling CLI and desktop agents to authenticate dynamically through the same Access application

The deployment logic lives in the **alchemy** infrastructure-as-code scripts. The [`alchemy.run.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts) file creates the Access application using your configured email allow-list ([lines 170-250](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts)), while [`alchemy.access.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.access.ts) validates that `ACCESS_ALLOWED_EMAILS` is properly set at runtime ([lines 45-48](https://github.com/every-app/open-seo/blob/main/alchemy.access.ts)).

## Prerequisites

Before deploying, ensure you have:

- **Node.js 22.6+** installed
- **pnpm** enabled via corepack
- A **Cloudflare account** with Workers and Zero Trust Access enabled
- **DataForSEO API key** for SEO data retrieval

## Step-by-Step Deployment

### 1. Clone and Prepare the Repository

Fork the repository if you need a personal copy for modifications, then clone and install dependencies:

```bash
git clone https://github.com/every-app/open-seo.git   # or your fork URL

cd open-seo
corepack enable
pnpm install

```

### 2. Authenticate with Cloudflare

The alchemy CLI manages Cloudflare resource provisioning. Run the login flow once to authorize deployment permissions:

```bash
pnpm alchemy login                # answer "yes" to customize OAuth scopes

pnpm alchemy cloudflare bootstrap   # deploys the alchemy state-store Worker

```

The bootstrap command creates a state-store Worker that tracks your infrastructure state across deployments.

### 3. Configure Environment Variables

Create your self-hosting configuration from the example template:

```bash
cp .env.selfhost.example .env.selfhost

```

Edit `.env.selfhost` with your required values:

```bash

# Critical: comma-separated list of authorized email addresses

ACCESS_ALLOWED_EMAILS="alice@example.com,bob@example.com"

# Required: SEO data provider credentials

DATAFORSEO_API_KEY="your-key"

# Optional: manual Access management (skips auto-provisioning)

# TEAM_DOMAIN="your-team.cloudflareaccess.com"

# POLICY_AUD="your-access-policy-audience"

```

The `ACCESS_ALLOWED_EMAILS` variable directly drives the Access allow-policy. If you omit `TEAM_DOMAIN` and `POLICY_AUD`, alchemy automatically creates and manages the Access application for you.

### 4. Deploy OpenSEO

A single command provisions all resources and deploys the application:

```bash
pnpm deploy:selfhost --yes

```

This execution:
- Creates the **D1 database** and runs schema migrations
- Provisions **KV namespaces** for caching and configuration
- Sets up the **R2 bucket** for asset storage
- Deploys the **Worker** with bundled application code
- Creates or updates the **Cloudflare Access** application with your email allow-policy

### 5. Validate the Deployment

Open the printed Worker URL in your browser:

```bash
open https://YOUR_WORKER_HOSTNAME

```

You'll be redirected to the **Cloudflare Access** login page. After authenticating with an email from your `ACCESS_ALLOWED_EMAILS` list, the Access proxy forwards your request to the Worker with a signed JWT. The OpenSEO dashboard should load, confirming successful deployment.

## Post-Deployment Configuration

### Enable MCP Client Access (Optional)

To allow CLI and desktop agents to connect through Cloudflare Access:

1. Visit **Cloudflare Zero Trust dashboard** → **Access** → **Applications**
2. Select your OpenSEO application → **Edit**
3. Navigate to **OAuth** settings
4. Enable **Managed OAuth**
5. Add redirect URIs (e.g., `http://localhost:3000/callback`)

This configuration is documented in the [operations guide](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md) under "Connect the MCP server through Cloudflare Access."

### Add Team Members

Expand access by appending new emails to `ACCESS_ALLOWED_EMAILS`:

```bash

# Edit .env.selfhost

ACCESS_ALLOWED_EMAILS="alice@example.com,bob@example.com,charlie@example.com"

# Redeploy to update the Access policy

pnpm deploy:selfhost --yes

```

Redeployment applies the updated allow-policy without data loss—D1, KV, and R2 resources persist across updates.

### Upgrade to Latest Version

Pull upstream changes and redeploy:

```bash
git pull        # or fetch + merge from your upstream remote

pnpm install    # install any new dependencies

pnpm deploy:selfhost --yes

```

Your existing data and Access configuration remain intact.

## Key Configuration Files

| File | Purpose | Location |
|------|---------|----------|
| `.env.selfhost.example` | Template for environment variables including `ACCESS_ALLOWED_EMAILS` | [GitHub](https://github.com/every-app/open-seo/blob/main/.env.selfhost.example) |
| [`docs/SELF_HOSTING_CLOUDFLARE.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE.md) | Complete deployment instructions | [GitHub](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE.md) |
| [`docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md`](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md) | MCP setup, telemetry, and maintenance | [GitHub](https://github.com/every-app/open-seo/blob/main/docs/SELF_HOSTING_CLOUDFLARE_OPERATIONS.md) |
| [`alchemy.run.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts) | Core deployment script; creates Access application | [GitHub](https://github.com/every-app/open-seo/blob/main/alchemy.run.ts) |
| [`alchemy.access.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.access.ts) | Runtime validation of Access configuration | [GitHub](https://github.com/every-app/open-seo/blob/main/alchemy.access.ts) |

## Summary

- **Self-hosted OpenSEO** deploys to Cloudflare Workers with `pnpm deploy:selfhost`, provisioning D1, KV, R2, and Access in one command
- **Authentication** uses Cloudflare Access with an email allow-policy from `ACCESS_ALLOWED_EMAILS` in `.env.selfhost`
- **MCP clients** connect through Managed OAuth enabled on the same Access application
- **Upgrades** are non-destructive: pull, install, and redeploy to update while preserving data

## Frequently Asked Questions

### What happens if I don't set ACCESS_ALLOWED_EMAILS?

The deployment fails. The [`alchemy.access.ts`](https://github.com/every-app/open-seo/blob/main/alchemy.access.ts) validation enforces this variable at runtime ([lines 45-48](https://github.com/every-app/open-seo/blob/main/alchemy.access.ts)). You must provide at least one authorized email address for the Access allow-policy.

### Can I use my existing Cloudflare Access application?

Yes. Set `TEAM_DOMAIN` and `POLICY_AUD` in `.env.selfhost` to skip automatic Access provisioning and use your pre-configured application instead. The Worker will still validate the JWT from your existing Access deployment.

### Is my SEO data stored in Cloudflare's infrastructure?

Project metadata resides in your provisioned **D1 database**, while cached results and configuration use **KV**. Binary exports and larger assets store in **R2**. All data stays within your Cloudflare account—you retain full ownership and can export or delete it at any time.

### How do I troubleshoot Access authentication failures?

Check three common issues: (1) your email is exactly matched in `ACCESS_ALLOWED_EMAILS` (case-sensitive), (2) your browser session isn't caching an old JWT—try incognito mode, and (3) the Access application shows as "Active" in the Zero Trust dashboard. Redeploy with `pnpm deploy:selfhost --yes` to sync any configuration changes.