How the Scheduled Rank Check Cron Job Executes Daily Rank Tracking in Open‑SEO

Open‑SEO runs its daily rank tracking through a Cloudflare Worker cron trigger that fires every five minutes, invoking a scheduled handler in src/server.ts which reconciles stale audits and dispatches runScheduledRankChecks() to queue due configurations within strict task-unit and time budgets.

The Open‑SEO platform automates SEO rank tracking through a lightweight, cron-driven scheduler. According to the every-app/open-seo source code, the system leverages Cloudflare Workers cron triggers to continuously monitor and launch rank checks without manual intervention.

Cron Configuration in wrangler.jsonc

The wrangler.jsonc file declares two cron schedules:

  • */5 * * * * — fires every 5 minutes to drive rank tracking
  • 17 3 * * * — fires once daily at 03:17 UTC for OAuth KV cleanup

The 5-minute cron is the primary trigger for the scheduled rank check cron job. Each time Cloudflare invokes the Worker with this schedule, the scheduled export in src/server.ts executes.

The scheduled Entry Point in src/server.ts

In src/server.ts, the Worker exports a scheduled handler that acts as the router for cron invocations. Its logic prioritizes system health before launching new work.

// src/server.ts – scheduled entrypoint
export default {
  fetch,
  async scheduled(controller, env, _ctx) {
    // OAuth KV cleanup runs on the daily 17 3 cron
    if (controller.cron === "17 3 * * *") { … }

    // First reconcile stale audits, then run rank checks
    await withPgClient(() => reconcileStaleAudits());
    await withPgClient(() => runScheduledRankChecks(env));
  },
};

The watchdog step (reconcileStaleAudits) runs first. This reconciles any audits stuck in a "running" state, ensuring that a slow tick cannot starve the cleanup process. Immediately after, it calls runScheduledRankChecks(env) to process due rank-tracking configurations.

Rank Check Scheduling Logic in scheduledRankChecks.ts

The core scheduling implementation lives in src/server/features/rank-tracking/services/scheduledRankChecks.ts. The runScheduledRankChecks function performs a batched loop that respects both API limits and wall-clock deadlines.

// src/server/features/rank-tracking/services/scheduledRankChecks.ts
export async function runScheduledRankChecks(env: Env) {
  const nowIso = new Date().toISOString();
  const dueConfigs = await RankTrackingRepository.getDueConfigsWithOrganization(nowIso);
  const deadline = Date.now() + TICK_DEADLINE_MS;
  let unitsStarted = 0;

  for (const config of dueConfigs) {
    if (Date.now() >= deadline) break;               // wall‑clock guard
    const taskUnits = (keywordCounts.get(config.id) ?? 0) *
                      devicesCount(config.devices);
    if (unitsStarted + taskUnits > SCHEDULED_TASK_UNIT_BUDGET) break; // budget guard

    const claimed = await RankTrackingRepository.claimDueConfig({ … });
    if (!claimed) continue;                         // already running elsewhere

    await beginRankCheckRun({
      workflow: env.RANK_CHECK_WORKFLOW,
      config,
      trigger: "scheduled",

    });
    unitsStarted += taskUnits;
  }

  console.log({
    event: "rank_tracking_scheduler_summary",
    started,
    unitsStarted,
// other statistics
  });
}

Fetching Due Configurations

The scheduler begins by querying RankTrackingRepository.getDueConfigsWithOrganization(nowIso). This returns every rank-tracking configuration whose nextCheckAt timestamp is in the past, ensuring only eligible projects enter the processing loop.

Budget and Deadline Enforcement

Two hard guards prevent the job from overwhelming the DataForSEO API or exceeding Cloudflare Worker limits:

  • Task-unit budget: SCHEDULED_TASK_UNIT_BUDGET caps each tick at 1,000 task units, calculated as the product of keyword count and device count per configuration.
  • Wall-clock deadline: TICK_DEADLINE_MS is set to 3 minutes. If Date.now() reaches the deadline, the loop aborts immediately.

These limits ensure the scheduled rank check cron job runs safely within API request caps and Worker execution constraints.

Plan Verification and Claiming

For hosted deployments, the scheduler verifies that the organization maintains a paid plan via customerHasPaidPlan. Configurations without a valid plan are skipped and tagged with lastSkipReason: "plan_required".

Eligible configurations are then claimed through RankTrackingRepository.claimDueConfig. This atomic claim step prevents duplicate work when multiple Worker instances or ticks overlap.

Workflow Invocation

Once claimed, each configuration is passed to beginRankCheckRun, which launches the RankCheckWorkflow via env.RANK_CHECK_WORKFLOW. The workflow receives the trigger: "scheduled" context, distinguishing it from manual or event-driven invocations.

Safety Guards and Observability

After processing, the scheduler emits a structured log entry with the event name "rank_tracking_scheduler_summary". This record includes:

  • Number of runs started
  • Total task units consumed
  • Skip counts and error counts
  • Age of the oldest due configuration

Because the cron fires every 5 minutes, the system continuously processes any configurations that become due. The combination of per-tick budgets, deadline guards, and atomic claiming ensures that the scheduled rank check cron job resumes gracefully on the next tick if interrupted.

Summary

  • Open‑SEO defines its cron schedules in wrangler.jsonc, using a 5-minute interval to drive continuous rank tracking.
  • The scheduled handler in src/server.ts reconciles stale audits before calling runScheduledRankChecks(env).
  • runScheduledRankChecks in src/server/features/rank-tracking/services/scheduledRankChecks.ts queries due configs, enforces a 1,000-unit task budget, and respects a 3-minute wall-clock deadline.
  • Configurations are atomically claimed via RankTrackingRepository.claimDueConfig to prevent duplicate runs.
  • Paid-plan checks filter out ineligible hosted organizations before workflow invocation.
  • Structured logging at "rank_tracking_scheduler_summary" provides observability into scheduler performance and backlog health.

Frequently Asked Questions

How often does Open‑SEO run the scheduled rank check cron job?

The cron job fires every 5 minutes via the */5 * * * * schedule defined in wrangler.jsonc. This sub-daily frequency allows the system to continuously process rank-tracking configurations as they become due, effectively providing daily or more frequent updates depending on the project schedule.

What happens if a rank check takes longer than one tick to process?

The scheduler enforces a 3-minute wall-clock deadline (TICK_DEADLINE_MS) and a 1,000 task-unit budget per tick. If limits are reached, the loop exits cleanly and any unprocessed due configs wait for the next 5-minute cron invocation. The atomic claimDueConfig step ensures that already-started work is not duplicated.

Where does the actual SEO rank checking happen?

The scheduler in scheduledRankChecks.ts does not perform the SEO queries directly. Instead, it invokes beginRankCheckRun to start the RankCheckWorkflow defined in src/server/workflows/RankCheckWorkflow.ts. This Cloudflare Workers Workflow executes the actual DataForSEO API calls and stores results.

Why does the scheduled handler reconcile stale audits before rank checks?

The reconcileStaleAudits call in src/server.ts runs first to recover any audits stuck in a "running" state from previous ticks. This ordering guarantees that a slow or overloaded tick cannot starve the watchdog, maintaining overall system health before new rank-check work is admitted.

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 →