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 tracking17 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_BUDGETcaps each tick at 1,000 task units, calculated as the product of keyword count and device count per configuration. - Wall-clock deadline:
TICK_DEADLINE_MSis set to 3 minutes. IfDate.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
scheduledhandler insrc/server.tsreconciles stale audits before callingrunScheduledRankChecks(env). runScheduledRankChecksinsrc/server/features/rank-tracking/services/scheduledRankChecks.tsqueries due configs, enforces a 1,000-unit task budget, and respects a 3-minute wall-clock deadline.- Configurations are atomically claimed via
RankTrackingRepository.claimDueConfigto 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →