n4nAI

Cloudflare Workers cron triggers for scheduled LLM jobs

Learn how to build Cloudflare Workers cron triggers that run scheduled LLM jobs, from scaffolding to deployment and verification, with runnable code.

n4n Team3 min read649 words

Audio narration

Coming soon — every post will get a voice note here.

Running cloudflare workers cron llm jobs eliminates the need to keep a server alive just to fire a prompt at 9am. This guide gives you a complete, copy-pasteable path from an empty directory to a deployed scheduled Worker that calls an LLM API and ships the result downstream.

Step 1: Scaffold the Worker project

Start with the official Cloudflare scaffolding tool. It generates a TypeScript Worker with a local dev server and Wrangler configured.

npm create cloudflare@latest llm-cron-worker
cd llm-cron-worker
npm install

Pick the “Hello World” scheduled Worker template if prompted, or just use the default worker and edit it. You only need src/index.ts and wrangler.toml.

Step 2: Declare cron triggers

Cron triggers are attached to the Worker via the [[triggers]] table in wrangler.toml. Cloudflare evaluates them in UTC. A single expression can be a standard five-field cron string or a named alias like "@daily".

name = "llm-cron-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"

[[triggers]]
crons = ["0 9 * * *", "0 17 * * *"]

This runs the job at 09:00 and 17:00 UTC every day. You can list multiple strings. The free tier permits one cron expression; paid plans allow more.

Step 3: Store LLM credentials securely

Never embed API keys in source. Push them as encrypted secrets; Wrangler makes them available on the env object at runtime.

wrangler secret put LLM_API_KEY
# prompt will ask for the secret value

For the base URL, a non-sensitive var is fine in wrangler.toml:

[vars]
LLM_API_BASE = "https://api.openai.com/v1"

If you’d rather not wire provider failover yourself, point LLM_API_BASE at n4n.ai’s OpenAI-compatible endpoint that covers 240+ models and auto-falls back when a provider is rate-limited. The request shape below stays identical.

Step 4: Implement the scheduled handler

The Worker exports a scheduled method. Use ctx.waitUntil so the runtime keeps the function alive while the async job runs after the response is returned (cron invocations don’t have a client waiting, but this still matters for tail logs and error reporting).

interface Env {
  LLM_API_KEY: string;
  LLM_API_BASE: string;
}

export default {
  async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
    ctx.waitUntil(handleJob(env));
  },
};

async function handleJob(env: Env) {
  const res = await fetch(`${env.LLM_API_BASE}/chat/completions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${env.LLM_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4o-mini",
      messages: [
        { role: "system", content: "You are a concise summarizer." },
        { role: "user", content: "Summarize the top 5 Hacker News stories." },
      ],
      max_tokens: 300,
    }),
  });

  if (!res.ok) {
    throw new Error(`LLM request failed: ${res.status} ${await res.text()}`);
  }

  const data = await res.json();
  const downstream = await fetch("https://my-app.example.com/ingest", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ summary: data.choices[0].message.content }),
  });

  if (!downstream.ok) {
    throw new Error(`Downstream ingest failed: ${downstream.status}`);
  }
}

Calling an OpenAI-compatible endpoint

The /chat/completions route is stable across OpenAI and most gateways. The model field accepts any string the backend supports. If you switch LLM_API_BASE to a gateway that honors client routing directives, you can pass route hints in headers or body depending on the gateway’s spec—check its docs.

Error handling and idempotency

Cron runs are at-least-once in practice. If your downstream write is not idempotent, store a hash of the date + job name in KV and skip if present:

const key = `job:${event.cron}:${new Date().toISOString().slice(0, 10)}`;
if (await env.KV.get(key)) return;
await env.KV.put(key, "done", { expirationTtl: 86400 });

Wrap the whole handleJob in try/catch and log with console.error. Wrangler tail will surface those.

Step 5: Test locally with simulated cron

Wrangler can fire the scheduled event without waiting for the clock.

wrangler dev --test-scheduled

In another shell:

curl "http://localhost:8787/__scheduled?cron=0+9+*+*+*"

The Worker’s scheduled handler runs immediately. Watch the dev terminal for fetch errors or malformed JSON. If you use KV locally, add a [[kv_namespaces]] binding and a wrangler.toml entry, then wrangler dev picks it up.

Step 6: Deploy and verify success

Deploy with one command:

wrangler deploy

Cloudflare registers the cron triggers from wrangler.toml automatically. To confirm the job actually ran, stream logs:

wrangler tail

You should see the scheduled invocation and any console.log output. Verification checklist:

  • wrangler tail shows no LLM request failed errors at the cron time.
  • Your downstream service (/ingest) reports a new record with the expected summary text.
  • If you added KV idempotency, the key exists after the first run and the job is a no-op on manual re-trigger.

For a faster loop, re-run the local simulated cron after deploy using the production-like environment via wrangler dev --remote if you need real secrets.

Operational notes

Cron triggers on Cloudflare run within a few minutes of the scheduled time, not precisely on the second. Don’t design jobs that assume tight ordering across regions.

Keep the handler lean. A scheduled Worker has a CPU time limit (currently 10ms on free, more on paid with cpu_ms billing), but wall-clock can be longer if you waitUntil. Long LLM calls are fine because they are network-bound, not CPU-bound.

If you need per-token cost visibility, route through a gateway that meters usage. The earlier note about n4n.ai applies: it forwards provider cache-control hints and meters per token, so your Worker code stays the same while billing becomes observable.

Finally, version your wrangler.toml cron expressions. A typo like "0 9 * *" (four fields) fails silently at deploy. Validate with wrangler deploy --dry-run before pushing to production.

Tagscloudflare-workerscron-triggersautomationllm-api

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All cloudflare workers llm integration posts →