How to Self-Host OpenSEO on Cloudflare
The complete guide, with the real problems I solved — including a silent bug on Google Search Console that looked like a token problem and wasn’t.
By Valerio Aloi · Published: August 31, 2026 · Updated: October 22, 2026
Update, October 15, 2026. OpenSEO is now at version 0.1.13. I've added the main new features since September and a guide to updating your install. The Search Console bug described below now has an official fix too.
Serious SEO tools cost a lot. Semrush and Ahrefs start around a hundred dollars a month and climb fast once you actually need rank tracking, keyword research, and technical audits together. If you're a freelancer just starting out, that fixed monthly bill, before you've even invoiced a client, is often the reason you skip content tracking or do keyword research by gut feeling.
Setting up OpenSEO on Cloudflare wasn't straightforward. Some sections of the official documentation assume things that, if you've never run a Cloudflare deploy or a Google OAuth flow before, aren't obvious at all: I got stuck more than once on steps that two extra lines of explanation would have solved in five minutes instead of hours. I'm writing this guide mainly for people picking up tools like this for the first time. If you're already comfortable with Workers and OAuth, skip ahead to whatever section you need.
What is OpenSEO
OpenSEO is an open source SEO tool (code on GitHub), built as an alternative to Semrush and Ahrefs. The main difference is the cost model: no fixed monthly subscription, you only pay for the data you actually use through your own DataForSEO API key.

The core features:
- Keyword research: find the right keywords for a topic, with search volume and difficulty.
- Rank tracking: monitor your pages' Google positions over time.
- Competitor Insights: see what your competitors are doing, which keywords drive their traffic, where they're stronger than you.
- Backlinks: analyze who links to a site and the quality of those links.
- Site Audits: scan a site for technical SEO errors (broken links, missing meta tags, indexing issues).
- AI Visibility: check how and how often a site gets cited by AI search engines and language models, not just Google.
The project is also built to work alongside AI agents: it exposes an MCP server (the protocol that lets an assistant like Claude use your data and functions directly, without going through the web interface) and ready-made skills, guided procedures the agent follows to complete an SEO task.
There's a less obvious advantage to relying on DataForSEO as the data source, too. Google has started routing search result links through google.com/goto redirects, which obscure the real URL in the SERP specifically to make it harder for scrapers and third-party tools to read it, a move that seems aimed in particular at services like SerpApi. DataForSEO has already found a way around this redirect, so OpenSEO's rank tracking and SERP analysis keep returning real URLs instead of opaque links. There's no guarantee this holds if Google tightens things further, but for now it's a point in favor of relying on DataForSEO instead of other SERP data providers.
There's also a hosted version at $10/month if you don't want to manage anything yourself, at openseo.so. This guide covers the other route: taking the code and running it on your own infrastructure.
A quick look at the dashboard
Before getting into setup, here's what each of the features listed above actually looks like, taken from my self-hosted instance (client project name/domain blurred for privacy).








What's new, September to October 2026
Since this guide was published, OpenSEO has shipped seven new versions, up to 0.1.13 on October 14, 2026. Below are the additions that matter most if you use it every day; the full list, version by version, is on the releases page on GitHub.
- AI Visibility (beta). Research and tracking of how a brand shows up in answers from ChatGPT, Gemini and Google AI Overviews, and since 0.1.12 Claude and Perplexity too, which cost more. A manual check returns answers within minutes, and for each citation you can see which prompts produced it. Careful with generic brand names: they can produce false mentions.
- Website monitoring. A dashboard for the website saved in your project; saving the website starts a site audit on its own.
- More reliable site audits. JavaScript rendering for sites built client-side (React and the like). Bot challenges now show up as blocked pages instead of missing content, and temporary request limits are no longer mistaken for blocking. Audit times show in your timezone, and there's a crawler signature so you can audit Shopify sites.
- Keyword research. City and county search volumes, also through MCP; close variants stay out unless you turn on “Group keywords”; deeper SERP results on demand.
- Rank tracking. Pick the day and time scheduled checks run, pin your most important keywords to the top, open the Google result directly, and keep capitalization with “Match case”. An incomplete check no longer looks like a lost ranking.
- Saved reports. Reports generated from Claude Code, Codex or other MCP clients are saved inside the project, readable in the app and exportable to PDF. You can create reusable report templates for each project.
- Search Console, Analytics and MCP. Position and impression filters through MCP, search for Search Console and Analytics properties by name, mobile SERPs in the
get_serp_resultstool. And clear messages when a permission is missing or the API isn't enabled: exactly the problem described in section 6. If you missed it, Google Analytics 4 has been there since 0.1.6, with organic traffic on the dashboard. - Look and feel. A refreshed interface and a better dark mode.
I updated my own instance on October 15: how to do it, with a backup first, is explained further down in How to update OpenSEO.
How to download OpenSEO
The code lives on GitHub, in the every-app/open-seo repository. You'll clone it in step 1 below. If you plan to update it often or customize it, start from your own fork on GitHub and clone that instead, so you have a repository you control.
Either way, you'll need a DataForSEO API key, the service that provides the actual SEO data (search volumes, SERPs, backlinks). OpenSEO is the interface and the logic on top of that data, not the data source itself. This guide assumes you already have one: if you don't, the project's official guide covers it in two minutes.
Two ways to self-host
Self-hosting means running the software on your own infrastructure instead of paying for a subscription service. OpenSEO supports two paths, both documented in the repo:
- Docker, locally. Runs on your computer, no internet exposure, authentication disabled by design (
AUTH_MODE=local_noauth). The simplest starting point if you just want to try it out or use it from a single device. - Cloudflare Workers, on the internet. A Worker is simply an application that runs on Cloudflare's servers instead of your computer: it stays reachable from any device, protected by Cloudflare Access (the login system that decides who gets in).
I use the Cloudflare route, because I want OpenSEO available from my phone, my laptop, wherever I am, not just on the computer where I installed it. It's also, worth saying upfront, the more complicated of the two: it means managing a service exposed to the internet, so authentication, secrets, and a Google Search Console OAuth flow that has more than one way to fail silently. If you just want to get started without thinking about any of that, go with Docker.
The rest of this guide covers the Cloudflare route, step by step, including every point where I got stuck.
Prerequisites
- Node 22.6 or newer and pnpm (
corepack enablesets it up). - A Cloudflare account with R2 enabled. Activating R2 requires a payment method on file, even within its free tier: if you've never used R2, open the R2 section in the Cloudflare dashboard once before proceeding.
- A DataForSEO account for the API key.
What it really costs
The software is free, but using it has costs: here are the ones I recorded myself in October 2026 and the ones from the price lists.
| Item | Cost |
|---|---|
| OpenSEO self-hosted | free (open source) |
| OpenSEO hosted on openseo.so | €10 a month |
| Cloudflare Workers, free plan | €0, but at most 50 outbound requests per run |
| Cloudflare Workers, paid plan | $5 a month, 10,000 requests per run |
| DataForSEO data | pay-as-you-go, with a $50 minimum top-up (price list) |
| One AI Visibility check, 8 prompts on 4 engines | about $0.42 (the tool’s estimate before running it) |
| 80 AI answers (20 prompts on 4 engines) with OpenSEO, October 15 | $1.04, and only 44 answers arrived: 36 failed because of Worker limits |
| The same 80 answers calling DataForSEO directly | $0.36, 80 out of 80 |
What I concluded: for the everyday dashboard (Search Console, rankings, audits) I use OpenSEO; for large collections of AI answers I call DataForSEO directly, which costs less and doesn’t hit the Worker limits. How I work in that case is in the guide to Claude Code and DataForSEO.
1. How to clone the repository
git clone https://github.com/every-app/open-seo.git
cd open-seo
corepack enable
pnpm installIf you made a fork to control it yourself, clone that instead of the upstream repo.
2. How to log in to Cloudflare
pnpm alchemy login # answer yes to "Customize OAuth scopes?" and enable access:write
pnpm alchemy cloudflare bootstrap # deploys alchemy's state-store Worker to your accountOpenSEO uses Alchemy for deployment: a tool that, with a single command, creates ("provisions") all the Cloudflare resources you need (database, storage, Worker, login) for you, instead of configuring each one by hand from the dashboard.
During login, Cloudflare asks which permissions ("scopes") to grant Alchemy on your account: access:write is the one it needs to create the Cloudflare Access application on its own later. If you've already logged in before without that scope, a plain repeat login won't ask for it again. Use pnpm alchemy login --configure to force it.
3. How to configure .env.selfhost
cp .env.selfhost.example .env.selfhostOpen the file and set at least these two required variables:
- DATAFORSEO_API_KEY: not the raw key you see in the DataForSEO dashboard, but your email:password credentials base64-encoded. On app.dataforseo.com/api-access, click "Send by email" and use the value labeled "Base64" you receive, not the plaintext one.
- ACCESS_ALLOWED_EMAILS: the emails allowed to log in, comma-separated. This is the list of who gets into your OpenSEO.
4. How to deploy
pnpm deploy:selfhost --yesThis one command does everything: creates the D1 database, the KV namespaces, the R2 bucket, applies the migrations, deploys the Worker, and creates the Cloudflare Access application protecting it, limited exactly to ACCESS_ALLOWED_EMAILS. If your account has no Zero Trust team yet, one gets created.
If you'd rather manage the Access application yourself instead of having it created automatically, set TEAM_DOMAIN and POLICY_AUD in .env.selfhost before deploying: in that case the command doesn't touch Access resources.
5. How to verify everything works
- Open the Worker URL printed at the end of the deploy.
- Sign in with Cloudflare Access.
- OpenSEO should load after login.
If something's off, https://<your-worker>/api/health reports configuration and database status, and pnpm exec wrangler tail shows live logs.
6. How to connect Google Search Console
Before the steps, it's worth understanding how Google Search Console works behind the scenes: OpenSEO doesn't "hold" your data, it reads it live from your Google account through an OAuth authorization you grant once. The full flow is documented step by step in the official Search Console guide. Here I'll flag the step that, if you skip it, gives you no error right away but costs you time later.
Creating the OAuth credentials
- On console.cloud.google.com, create or reuse a project.
- Enable the Google Search Console API for that project (APIs & Services → Library, search "Google Search Console API", click Enable). This is the first step in the official guide, and the one that's easiest to treat as a formality and skip mentally. It isn't: creating the OAuth credentials and enabling the API are two separate settings in the same Google Cloud project.
- Configure the OAuth consent screen (External, and if you stay in Testing mode, add your account as a test user).
- Create a web application OAuth client ID, with redirect URI:
https://<your-worker>.workers.dev/api/gsc/oauth/callback - Save the Client ID and Client secret.
Configuring the deploy
In .env.selfhost, uncomment and set:
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
BETTER_AUTH_SECRET=BETTER_AUTH_SECRET is a random string of at least 32 characters, generated with openssl rand -base64 32: it's only needed for Search Console, to encrypt the stored tokens. Rerun pnpm deploy:selfhost --yes to apply the new secrets, then go to Integrations and connect the account.
What happens if you skip step 2
This is where I lost the most time, and it's worth telling because the symptom has nothing to do with the cause.
If the OAuth credentials are correct but the API isn't enabled, the Google login still works: consent completes, the redirect is correct, a token gets saved in the database with a one-hour expiry, refresh token included. Everything looks like it succeeded. Then you open Integrations and still see "Connection expired. Reconnect to continue.", even right after reconnecting.
OAuth consent (login, permissions, tokens) is a Google service separate from the individual API. Only when OpenSEO tries to read the real data (sites.list), does Google respond with a 403, because the API isn't active on the project. The code treats that 403 as "grant expired or revoked" and shows the reconnect message, which is technically correct but sends you looking in the wrong place: it looks like a token problem, it's a Google Cloud project configuration problem.
Fix: console.cloud.google.com → APIs & Services → Library → Google Search Console API → Enable. Then retry the reconnect, no need to regenerate any credentials.
Update, October 15, 2026: since version 0.1.13, OpenSEO recognizes this case and, instead of “Connection expired”, tells you what to fix when a permission is missing or the API isn't enabled. If you're on an earlier version, everything above still applies.
7. How to connect MCP tools (optional)
OpenSEO exposes its tools via MCP at /mcp, so you can use them directly from Claude, Claude Code, or other MCP clients. For it to work, the Cloudflare Access application protecting the Worker needs Managed OAuth turned on. It isn't by default, even if you had it auto-created by the deploy.
- Zero Trust (one.dash.cloudflare.com) → Access controls → Applications.
- Open the app protecting your Worker, Edit.
- Additional settings → OAuth, turn on Managed OAuth.
- In the allowed redirect URIs, add the ones your clients use: for CLI and desktop clients like Claude Code, the local clients that register
http://localhost:PORT/callback; for claude.ai, the callback URL its connector setup page shows you. - Save.
Then, from Claude Code:
claude mcp add --transport http --scope user openseo https://<your-worker>.workers.dev/mcpSkip the Managed OAuth step and the client gets a generic OAuth registration error and can't complete the connection.
Ongoing maintenance
How to update OpenSEO (updated October 15, 2026)
OpenSEO ships a new version every week or two. Before updating, read what changed in the release notes on GitHub: that's where any new variables you need to set are listed.
1. Back up first. Two minutes, and if something goes wrong you still have a copy of your projects. You'll find the database name in the Cloudflare dashboard, under D1:
pnpm exec wrangler d1 export <db-name> --remote --output=openseo-backup-YYYY-MM-DD.sql2a. If you installed with this guide (Alchemy):
git pull # or, if you forked: git fetch upstream && git merge upstream/main
pnpm install
pnpm deploy:selfhost --yesThe deploy applies the database migrations on its own. Your Cloudflare resources stay identified by .env.selfhost, not by values inside the repo, so there are no config files to save separately.
2b. If you installed manually with Wrangler, before the Alchemy route existed (that's my own instance): your database, KV and R2 IDs live in wrangler.jsonc and wrangler.audit.jsonc, repo files you edited yourself. Copy them, set them aside while you pull the new code, and put them back right after:
cp wrangler.jsonc wrangler.jsonc.bak
git stash # sets aside your changes to the wrangler files
git pull
git stash pop # puts them back on top of the new code
pnpm install
pnpm run deploy # migrations, main Worker and audit WorkerIf git stash pop reports a conflict in the wrangler files, keep your IDs and accept the rest of the changes. pnpm run deploy also publishes a second Worker, open-seo-audit, which runs site audits: wrangler.audit.jsonc needs the same DB, KV and R2 bindings as wrangler.jsonc, and the DataForSEO key has to be set there once too, with pnpm exec wrangler secret put DATAFORSEO_API_KEY --name open-seo-audit. If your repo came from the old “Deploy to Cloudflare” button, the steps are different: follow the official guide for older installs.
3. Check that everything works. Open https://<your-worker>/api/health and try a feature you use often. Some new features need extra settings:
- auditing Shopify sites with the crawler signature needs
BETTER_AUTH_SECRETand, since 0.1.13,CRAWLER_ACCESS_SECRET(at least 32 characters): in.env.selfhostwith Alchemy, as a Worker secret with Wrangler; - JavaScript rendering in site audits needs the paid Workers plan;
- AI Visibility prompt suggestions use an OpenRouter key (
OPENROUTER_API_KEY).
If you're updating from a much older version, the dashboard may show a one-time banner offering to move every user's projects into one shared workspace: that's expected.
Cloudflare's free plan and site audits
Something I noticed after updating: on the free Workers plan, an audit of my own site stopped after reading 37 of 409 pages, with every other page failing. The free plan allows a Worker at most 50 outgoing requests per invocation (10,000 on the paid plan, from $5 a month), and 37 pages plus robots.txt and the sitemaps add up to about 50. That's the most likely cause, though I haven't confirmed it in the logs yet. If your audits stop early, check the audit Worker's logs before changing anything else: pnpm exec wrangler tail open-seo-selfhost-audit with Alchemy, open-seo-audit with a manual Wrangler install.
How to give teammates access
Add the email to ACCESS_ALLOWED_EMAILS inside .env.selfhost and rerun pnpm deploy:selfhost --yes. If you edit the Access policy directly from the dashboard instead, the next deploy overwrites it: .env.selfhost stays the single source of truth.
How to tear everything down
pnpm alchemy destroy --env-file .env.selfhost --stage selfhostDeletes the Worker, the D1 database, the KV namespaces, the R2 bucket, and the Access application, data included. There's no undo after this, so only use it if you genuinely want to start over or abandon the project.
How to diagnose a problem without changing anything
A couple of commands that helped me understand what was actually happening, without touching code or configuration:
# Live Worker logs, including requests that don't throw exceptions
pnpm exec wrangler tail
# Read one row from the production database, read-only
pnpm exec wrangler d1 execute <your-db-name> --remote --command "SELECT * FROM account WHERE provider_id = 'google-search-console';"The first tells you whether a request actually reaches the Worker and whether it throws an exception. The second shows you the real state of the stored data, instead of guessing from how the UI behaves.
How Claude Code can help you set this up
I diagnosed the whole Search Console section above, including the hidden 403 bug, together with Claude Code, inside the OpenSEO repository cloned locally. If you've never used an AI agent to configure something like this, it's worth trying: unlike following a guide on your own, you can have it read the project's actual code instead of stopping at what the documentation says, and have it run diagnostic commands for you.
A concrete starting point, inside the cloned repo folder:
npx skills add every-app/open-seo --skill '*' --agent claude-codeThis installs the project's official skills (including setup and diagnostic ones) as commands available in Claude Code. Then you can just ask, in plain language, things like:
- "Help me configure .env.selfhost for the Cloudflare deploy."
- "I'm getting this error during deploy, what does it mean?"
- "GSC says 'Connection expired' even after reconnecting, help me figure out why."
For diagnostics, the pattern that worked for me was: have it read the source code at the point that's breaking (not just the documentation, which can drift from the actual code), check the real state with read-only commands like wrangler tail or wrangler d1 execute --remote, and only then propose a change. If you're following this guide and something doesn't add up, ask the agent to verify step by step before changing configuration at random: the real cause is often two or three steps back from where the symptom shows up.
Self-hosting FAQ
Do I need to enable the Google Search Console API?
Yes, always, regardless of how you configure the OAuth credentials. It’s step 2 in the Search Console section, and it’s the most common cause of connections that look successful but end up stuck on "Connection expired." Since version 0.1.13, OpenSEO tells you itself.
How do I update OpenSEO without losing data?
Back up the database first with wrangler d1 export, then pull the new code and redeploy: with Alchemy, pnpm deploy:selfhost --yes is enough; with a manual Wrangler install, pnpm run deploy, after setting your wrangler files aside. The full steps are in the Ongoing maintenance section.
Is Cloudflare Access required?
Yes, if you choose the Cloudflare route. The deploy creates it automatically and limits access to the emails in ACCESS_ALLOWED_EMAILS.
Can I skip all this complexity?
Yes: use Docker locally. You lose the ability to access it from any device, but you skip authentication, secrets, and OAuth almost entirely.
Where can I find more resources?
The official site is openseo.so, and the code and full documentation live in the every-app/open-seo repository on GitHub.
Need a hand with SEO or technical setup?
A free audit to work out where to start, no commitment.