# How to Troubleshoot Open-SEO Issues: A Complete Technical Guide for Cloudflare Workers and Docker Deployments

> Troubleshoot Open-SEO issues with our technical guide. Learn to run health checks, inspect logs, validate env vars, and verify database migrations for Cloudflare Workers and Docker.

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

---

**To troubleshoot Open-SEO issues, run a health check at `/.well-known/health`, inspect logs via `wrangler tail` (Workers) or `docker compose logs` (Docker), validate all required environment variables against `.env.example`, and verify database migrations are applied.**

Open-SEO is a full-stack, self-hostable SEO debugging platform that deploys to either **Cloudflare Workers** (production default) or **Docker containers** for local environments. Understanding its layered architecture—from the edge runtime through the MCP server to workflow orchestrators—lets you pinpoint problems fast without guessing. This guide walks through diagnosing the most common Open-SEO troubleshooting scenarios using actual source paths and commands from the every-app/open-seo repository.

## Understanding Open-SEO's Architecture Before Debugging

Every troubleshooting session starts with knowing which layer failed. Open-SEO's request flows through these components, each with distinct failure modes:

| Layer | Function | Critical Source File |
|-------|----------|----------------------|
| **Edge runtime** | Serves public API via Cloudflare Worker or Docker container; routes to MCP server | [`src/server/mcp/server.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/server.ts) |
| **MCP server** | Handles authentication, routing, validation; forwards to feature services | [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) |
| **Feature workflows** | Background jobs (rank-tracking, site-audit) using **Drizzle ORM** | [`src/server/workflows/RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/src/server/workflows/RankCheckWorkflow.ts) |
| **Database layer** | SQLite/D1 (Workers) or PostgreSQL (Docker) via shared Drizzle schemas | [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) |
| **Third-party integrations** | DataForSEO API, Google Search Console OAuth, Google Analytics 4 OAuth | [`src/server/mcp/tools/search-console-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/search-console-tools.ts), [`google-analytics-tools.ts`](https://github.com/every-app/open-seo/blob/main/google-analytics-tools.ts) |
| **Frontend** | Vite + React UI in `web/` folder; talks to edge API via TanStack Query | [`web/vite.config.ts`](https://github.com/every-app/open-seo/blob/main/web/vite.config.ts) |
| **Telemetry/billing** | Svix webhooks; disable with `OPENSEO_TELEMETRY_DISABLED=1` | [`src/server/billing/svix.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/svix.ts) |

When symptoms appear, map them to the correct layer first. A 401 error points to MCP server auth; missing rank data points to workflows or DataForSEO integration; frontend blank screens point to CORS or API connectivity.

## Diagnosing Common Open-SEO Failure Points

### 401/403 API Authentication Errors

**Symptom:** API calls return unauthorized despite appearing to send credentials.

**Root cause:** Invalid or missing **API key** or **session cookie** in the `Authorization: Bearer` header.

**Quick verification:**

```bash
curl -H "Authorization: Bearer $OPENSEO_API_KEY" https://<your-host>/mcp/projects

```

**Deep fix:** Check [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) for the authentication middleware. The server validates Bearer tokens against stored credentials; ensure your key matches the environment where the worker runs (local, staging, or production). Cross-reference error responses with [`src/client/components/AuthConfigErrorCard.tsx`](https://github.com/every-app/open-seo/blob/main/src/client/components/AuthConfigErrorCard.tsx) for specific misconfigurations.

### Health Endpoint Returns 500

**Symptom:** `/.well-known/health` fails with server error instead of `{"status":"ok"}`.

**Root cause:** Worker crash during startup—almost always **missing required environment variables**.

**Critical variables to verify:**
- `DATAFORSEO_API_KEY` – required for rank-tracking and keyword research
- `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET` – required for Search Console/Analytics integration
- `DATABASE_URL` (Docker) or D1 binding configuration (Workers)

**Verification command:**

```bash
curl https://<your-host>/.well-known/health

```

**Docker-specific check:**

```bash
docker compose exec open-seo curl -I http://localhost:1355/.well-known/health

```

For Docker deployments, consult `docs/SELF_HOSTING_DOCKER.md#health-and-troubleshooting` for the complete environment variable checklist.

### Database Migration Failures

**Symptom:** Queries fail with "table not found" or schema mismatch errors; features appear to save data that disappears.

**Root cause:** Migrations not applied before service startup.

**Docker resolution:**

```bash
pnpm drizzle status          # Check pending migrations

pnpm drizzle up              # Apply migrations

```

**Cloudflare Workers resolution:**

```bash
wrangler d1 list             # Confirm D1 database binding

wrangler d1 execute <db-name> --file=./migrations/0000_initial.sql

```

The repository uses **Drizzle ORM** with database-agnostic schemas. The same [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts) generates migrations for both SQLite/D1 (Workers) and PostgreSQL (Docker). Never skip the migration step when deploying fresh instances.

### Rank-Tracking Workflow Stuck or Failing

**Symptom:** Keywords show "pending" indefinitely; scheduled checks never complete.

**Root cause:** Scheduler failure in [`src/server/features/rank-tracking/services/scheduledRankChecks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/scheduledRankChecks.ts), typically from **DataForSEO rate limiting** (HTTP 429) or authentication errors.

**Log inspection:**

```bash

# Cloudflare Workers

wrangler tail --override

# Docker

docker compose logs open-seo | grep -i "rank\|dataforseo\|429"

```

**Verification request:**

```typescript
import fetch from "node-fetch";

const API_KEY = process.env.OPENSEO_API_KEY!;
const projectId = "your_project_id";

async function diagnoseRankTracking() {
  const res = await fetch(
    `https://<your-host>/mcp/projects/${projectId}/keywords`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  
  if (!res.ok) {
    console.error("Rank API error:", res.status, await res.text());
    return;
  }
  
  const data = await res.json();
  console.log("Keyword statuses:", data.map((k: any) => ({
    keyword: k.keyword,
    lastChecked: k.lastCheckedAt,
    status: k.status
  })));
}

diagnoseRankTracking();

```

DataForSEO implements strict rate limits. If you see 429 responses, reduce check frequency or upgrade your DataForSEO plan. The [`RankCheckWorkflow.ts`](https://github.com/every-app/open-seo/blob/main/RankCheckWorkflow.ts) orchestrator queues jobs but cannot proceed when the upstream API rejects requests.

### Missing Google Search Console Data

**Symptom:** Search Console reports show no data despite site ownership confirmed in Google's interface.

**Root cause:** Incomplete OAuth flow or **insufficient OAuth scopes** granted to the service account.

**Re-authentication procedure:**

```bash
npm run gsc:auth

```

Then verify in [`src/server/mcp/oauth-registration.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-registration.ts) that your callback URL matches exactly—protocol, port, and path. Mismatches cause silent failures where tokens never propagate to [`src/server/mcp/tools/search-console-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/search-console-tools.ts).

### Frontend Cannot Reach API (CORS Errors)

**Symptom:** Browser console shows CORS policy errors; UI loads but data fetching fails.

**Root cause:** Origin not in `allowedOriginHostnames` list in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts).

**Verification:**

```bash
curl -I https://<your-host>/mcp | grep -i "access-control-allow-origin"

```

**Fix:** Add your domain to the CORS configuration in [`transport.ts`](https://github.com/every-app/open-seo/blob/main/transport.ts) and redeploy. The default configuration permits localhost for development but requires explicit whitelisting for production domains.

## Debugging Scripts and Commands

### Automated Health Monitoring

```typescript
// health-check.ts - Run in CI or monitoring systems
import fetch from "node-fetch";

async function checkHealth(baseUrl: string): Promise<void> {
  const res = await fetch(`${baseUrl}/.well-known/health`);
  
  if (!res.ok) {
    throw new Error(`Health check failed: HTTP ${res.status}`);
  }
  
  const json = await res.json() as { status: string };
  
  if (json.status !== "ok") {
    throw new Error(`Unhealthy status: ${json.status}`);
  }
  
  console.log("✅ Service healthy:", json);
}

// Usage
checkHealth(process.env.OPENSEO_URL || "http://localhost:1355");

```

### Live Log Streaming

**Cloudflare Workers:**

```bash
wrangler tail --override --format=pretty

```

Typical error patterns to watch for:
- `ERROR src/server/mcp/transport.ts:115` – missing environment variable
- `ERROR src/server/workflows/RankCheckWorkflow.ts:NN` – DataForSEO integration failure
- `WARN src/server/billing/svix.ts:NN` – webhook delivery issues (non-critical if not using billing)

**Docker:**

```bash
docker compose logs -f open-seo --tail=100

```

Filter for your specific issue:

```bash
docker compose logs open-seo | grep -E "(ERROR|WARN|rank|mcp|database)" | tail -50

```

### Database Connection Verification

```bash

# Docker PostgreSQL

docker compose exec open-seo psql $DATABASE_URL -c "SELECT COUNT(*) FROM projects;"

# Verify Drizzle schema synchronization

docker compose exec open-seo pnpm drizzle check

```

## Environment Variable Reference for Troubleshooting

| Variable | Required For | Validation | Where Checked |
|----------|--------------|------------|---------------|
| `DATAFORSEO_API_KEY` | Rank-tracking, keyword research, backlinks | 32-character hex string | [`src/server/mcp/tools/dataforseo-tools.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/tools/dataforseo-tools.ts) |
| `GOOGLE_OAUTH_CLIENT_ID` | Search Console, Analytics integration | OAuth 2.0 client ID format | [`src/server/mcp/oauth-registration.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-registration.ts) |
| `GOOGLE_OAUTH_CLIENT_SECRET` | Search Console, Analytics integration | OAuth 2.0 client secret | [`src/server/mcp/oauth-registration.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/oauth-registration.ts) |
| `DATABASE_URL` | Docker deployments only | PostgreSQL connection string | [`drizzle.config.ts`](https://github.com/every-app/open-seo/blob/main/drizzle.config.ts), startup health check |
| `OPENSEO_API_KEY` | API authentication | User-generated in UI | [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) auth middleware |
| `OPENSEO_TELEMETRY_DISABLED` | Opt-out of telemetry | `1` or unset | [`src/server/billing/svix.ts`](https://github.com/every-app/open-seo/blob/main/src/server/billing/svix.ts) |
| `ALLOWED_ORIGINS` | CORS configuration | Comma-separated hostnames | [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts) |

Compare your actual environment against `.env.example` and `.env.selfhost.example` in the repository root. These template files document all optional and required variables with inline comments.

## Summary

- **Start every troubleshooting session** with `GET /.well-known/health`—200 with `{"status":"ok"}` confirms the edge runtime and MCP server started correctly.
- **Missing environment variables** cause 500 errors on health checks and startup crashes; validate against `.env.example` before deep debugging.
- **Database migrations** must be applied explicitly—`pnpm drizzle up` for Docker, `wrangler d1 execute` for Cloudflare Workers.
- **Rank-tracking failures** usually indicate DataForSEO rate limits (429) or authentication errors; inspect logs with `wrangler tail` or `docker compose logs`.
- **CORS errors** require adding your domain to `allowedOriginHostnames` in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts).
- **Third-party OAuth flows** (Google Search Console, Analytics) can silently fail—re-run `npm run gsc:auth` and verify callback URL matches exactly.

## Frequently Asked Questions

### How do I check if my Open-SEO instance is running correctly?

Send a GET request to `/.well-known/health` on your instance URL. A healthy response returns HTTP 200 with body `{"status":"ok"}`. If you receive 500, check logs immediately—this indicates a startup crash, typically from missing environment variables like `DATAFORSEO_API_KEY` or database connection failures.

### Why is my rank-tracking data not updating?

The scheduler in [`src/server/features/rank-tracking/services/scheduledRankChecks.ts`](https://github.com/every-app/open-seo/blob/main/src/server/features/rank-tracking/services/scheduledRankChecks.ts) likely encountered a DataForSEO API error. Run `wrangler tail` (Workers) or `docker compose logs` (Docker) and search for "429" (rate limit) or authentication errors. Verify your `DATAFORSEO_API_KEY` is valid and has sufficient credits. You can also trigger a manual check via the MCP API to bypass the scheduler temporarily.

### How do I fix CORS errors when connecting the frontend to my self-hosted Open-SEO API?

Add your frontend's exact origin—including protocol and port—to the `allowedOriginHostnames` array in [`src/server/mcp/transport.ts`](https://github.com/every-app/open-seo/blob/main/src/server/mcp/transport.ts), then redeploy. The default configuration only permits `localhost` origins. Use `curl -I https://<host>/mcp` to verify the `Access-Control-Allow-Origin` header matches your domain before testing from the browser.