<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ronen Cypis</title>
    <description>The latest articles on DEV Community by Ronen Cypis (@ronency).</description>
    <link>https://dev.to/ronency</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3975320%2Fd8536c7d-7442-4fcf-9cb2-678e612cbceb.jpeg</url>
      <title>DEV Community: Ronen Cypis</title>
      <link>https://dev.to/ronency</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ronency"/>
    <language>en</language>
    <item>
      <title>Cron Job Monitoring Tools Compared: From DIY to Fully Managed</title>
      <dc:creator>Ronen Cypis</dc:creator>
      <pubDate>Tue, 09 Jun 2026 12:20:00 +0000</pubDate>
      <link>https://dev.to/ronency/cron-job-monitoring-tools-compared-from-diy-to-fully-managed-3ma1</link>
      <guid>https://dev.to/ronency/cron-job-monitoring-tools-compared-from-diy-to-fully-managed-3ma1</guid>
      <description>&lt;p&gt;Cron's biggest problem isn't scheduling — it's silence. A cron job can fail every night for a month, and unless you're manually checking logs on the server, you won't know. No alert, no dashboard, no audit trail. Just a backup that doesn't exist when you need it, or a data sync that quietly stopped three weeks ago.&lt;/p&gt;

&lt;p&gt;Monitoring fixes this. But "cron job monitoring" means different things depending on the tool. Some watch for missing heartbeats. Some track full execution history. Some just page you when something breaks. This article compares six approaches — from writing your own monitoring scripts to using a fully managed scheduler with built-in observability — so you can pick the right one for your workload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heartbeat Monitoring vs. Execution Monitoring
&lt;/h2&gt;

&lt;p&gt;Before comparing tools, understand the two fundamentally different approaches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Heartbeat monitoring (dead man's switch)&lt;/strong&gt; is passive. Your cron job pings a monitoring URL after each run. If the ping doesn't arrive on schedule, you get an alert. This tells you &lt;em&gt;whether&lt;/em&gt; a job ran — but not &lt;em&gt;what happened&lt;/em&gt;. If the job runs but returns bad data, the ping still fires and the monitor stays green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution monitoring&lt;/strong&gt; is active. The scheduler fires the job, captures the response, records the outcome, and alerts on failure. You get the full picture: status code, response body, duration, retry count, and a timeline of every execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to use each:&lt;/strong&gt; Heartbeat monitoring makes sense when you're stuck with system cron. Execution monitoring makes sense when you're choosing a scheduler — you get monitoring, retries, and logging as part of the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Alerts&lt;/th&gt;
&lt;th&gt;Execution Logs&lt;/th&gt;
&lt;th&gt;Retries&lt;/th&gt;
&lt;th&gt;Free Tier&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DIY scripts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;⚠️ Whatever you build&lt;/td&gt;
&lt;td&gt;⚠️ Whatever you build&lt;/td&gt;
&lt;td&gt;⚠️ Whatever you build&lt;/td&gt;
&lt;td&gt;✅ Free (your time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Healthchecks.io&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heartbeat&lt;/td&gt;
&lt;td&gt;✅ Email, Slack, webhooks&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;✅ 20 checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cronitor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heartbeat + telemetry&lt;/td&gt;
&lt;td&gt;✅ Email, Slack, PagerDuty&lt;/td&gt;
&lt;td&gt;⚠️ Basic (duration, exit code)&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;⚠️ 5 monitors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Better Stack&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Uptime + heartbeat&lt;/td&gt;
&lt;td&gt;✅ Email, Slack, PagerDuty&lt;/td&gt;
&lt;td&gt;⚠️ Basic (heartbeat log)&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;⚠️ 5 monitors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PagerDuty / Opsgenie&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Incident mgmt&lt;/td&gt;
&lt;td&gt;✅ Full escalation&lt;/td&gt;
&lt;td&gt;❌ Requires integration&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runhooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Execution (scheduler)&lt;/td&gt;
&lt;td&gt;✅ Email, webhook&lt;/td&gt;
&lt;td&gt;✅ Full (status, body, duration)&lt;/td&gt;
&lt;td&gt;✅ Exponential backoff&lt;/td&gt;
&lt;td&gt;✅ 3 jobs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  DIY Monitoring
&lt;/h2&gt;

&lt;p&gt;The simplest approach: wrap each cron job in a script that checks exit codes and sends alerts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
/scripts/nightly-backup.sh &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/backup.log 2&amp;gt;&amp;amp;1
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; 0 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://hooks.slack.com/services/T00/B00/xxx &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"text": "Backup job failed on prod-1"}'&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free in dollars, expensive in time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; You're building monitoring for every job. No dashboard, no execution history, no retries unless you write them. When the server dies, the monitoring dies with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Solo developers with a handful of non-critical jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Healthchecks.io
&lt;/h2&gt;

&lt;p&gt;A dedicated heartbeat monitoring service. Create a check, get a ping URL, append it to your cron job. If the ping doesn't arrive on schedule, you get an alert. Supports start/success/fail signals so you can detect jobs that start but never finish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier includes 20 checks with unlimited alert integrations. Paid plans start at $20/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Doesn't execute your jobs — only watches for pings. No execution logs, no retries, no scheduling. If the job runs but produces bad results, the heartbeat still fires and the monitor stays green.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams using system cron or platform-native schedulers who need basic "is it running?" monitoring without changing their scheduler.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cronitor
&lt;/h2&gt;

&lt;p&gt;Heartbeat monitoring with added telemetry. Integrate via ping URLs or install the Cronitor CLI to automatically capture duration, exit code, and output size from your crontab entries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier includes 5 monitors. Paid from $12.50/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; A monitoring layer, not a scheduler. Doesn't capture HTTP response bodies or provide retries. The CLI agent requires server installation — if the server dies, the agent dies with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams that want duration trends and exit code tracking beyond raw heartbeat pings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Better Stack (formerly Better Uptime)
&lt;/h2&gt;

&lt;p&gt;Primarily an uptime monitoring and incident management platform that includes heartbeat monitoring as a secondary feature. Your job pings a URL, Better Stack alerts if the ping is late — routed through on-call schedules and escalation policies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier includes 5 monitors. Paid plans start at $24/month per user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Designed around uptime monitoring, not cron. No execution logs, retries, or scheduling. If you're only using it for cron monitoring, you're paying for functionality you don't need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams already using Better Stack for uptime monitoring who want to add heartbeat checks without another tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  PagerDuty / Opsgenie
&lt;/h2&gt;

&lt;p&gt;Enterprise incident management platforms that don't monitor cron jobs directly — they're where cron alerts &lt;em&gt;end up&lt;/em&gt;. Your monitoring tool fires an event on failure, and PagerDuty/Opsgenie handles routing, escalation, and acknowledgment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; PagerDuty free tier covers 5 users. Paid from $21/user/month. Opsgenie starts at $9.45/user/month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; These are alert &lt;em&gt;destinations&lt;/em&gt;, not monitoring &lt;em&gt;sources&lt;/em&gt;. You still need something to detect the failure. Adding PagerDuty is justified for enterprise on-call workflows but overkill for most cron monitoring needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Organizations with established on-call rotations that need cron alerts routed through existing incident management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runhooks Built-In Monitoring
&lt;/h2&gt;

&lt;p&gt;Runhooks combines scheduling and monitoring into a single layer. The scheduler fires the HTTP request, inspects the response, logs the result, retries on failure, and alerts when retries are exhausted. There's no separate ping step — the scheduler &lt;em&gt;is&lt;/em&gt; the monitor.&lt;/p&gt;

&lt;p&gt;Every execution is recorded: HTTP status code, response body (up to 64 KB), duration in milliseconds, attempt number, and error details. Failed jobs enter a dead-letter state and trigger alerts via email or webhook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free plan includes 5 jobs with retries and alerts. Paid plans add more jobs, longer log retention (up to 30 days), and higher retry limits. See &lt;a href="https://runhooks.app/pricing" rel="noopener noreferrer"&gt;full pricing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; HTTP-only — if your job isn't callable via HTTP, you'd need to wrap it in an endpoint first. Natural fit for web services and serverless functions, less suited for legacy shell scripts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams who want scheduling, retries, logging, and alerting in one tool without stitching together a monitoring stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing the Right Approach
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Locked into system cron?&lt;/strong&gt; Add heartbeat monitoring. Healthchecks.io is the strongest option — generous free tier, simple integration, wide alert support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Already use an observability platform?&lt;/strong&gt; Check if it supports heartbeat checks (Better Stack, Datadog). No need for another tool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Need to know &lt;em&gt;why&lt;/em&gt; a job failed?&lt;/strong&gt; You need execution-level monitoring — either extensive logging in every script (fragile) or a scheduler that captures execution details natively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduling HTTP endpoints?&lt;/strong&gt; A managed HTTP scheduler like Runhooks gives you scheduling, retries, logs, and alerts in one layer. No heartbeat URLs, no wrapper scripts, no separate monitoring tool.&lt;/p&gt;

&lt;p&gt;The monitoring gap in most cron setups isn't technical — it's organizational. Pick the approach that requires the least ongoing effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;If you're running scheduled HTTP jobs and want monitoring that's built into the scheduler rather than bolted on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://runhooks.app/register" rel="noopener noreferrer"&gt;Create a Runhooks account&lt;/a&gt; — the free plan includes 5 jobs with retries and alerts&lt;/li&gt;
&lt;li&gt;Test your cron expressions with the &lt;a href="https://runhooks.app/tools/cron-visualizer" rel="noopener noreferrer"&gt;cron expression visualizer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://runhooks.app/pricing" rel="noopener noreferrer"&gt;Compare plans&lt;/a&gt; when you're ready to scale&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I know if my cron job stopped running?
&lt;/h3&gt;

&lt;p&gt;You need external monitoring. Cron has no built-in failure detection. The two main approaches are heartbeat monitoring (your job pings a URL after each run — if the ping stops, you get an alert) and execution monitoring (the scheduler itself tracks every run and alerts on failures). Heartbeat tools like Healthchecks.io catch missing runs. Execution-aware schedulers like Runhooks catch missing runs, HTTP errors, timeouts, and retry exhaustion.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between heartbeat monitoring and execution monitoring?
&lt;/h3&gt;

&lt;p&gt;Heartbeat monitoring is passive — your cron job pings a URL when it completes, and the monitor alerts you if the ping is late or missing. Execution monitoring is active — the scheduler fires the job, inspects the response, and logs the outcome. Heartbeat monitoring tells you a job didn't run. Execution monitoring tells you why it failed, how long it took, and what the response was.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best free cron job monitoring tool?
&lt;/h3&gt;

&lt;p&gt;Healthchecks.io offers a generous free tier with 20 checks, and it handles basic heartbeat monitoring well. For execution-level monitoring with logs and retries, Runhooks includes a free plan with 5 jobs, automatic retries, and failure alerts. The best choice depends on whether you need passive heartbeat checks or active execution tracking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need cron monitoring if I already have uptime monitoring?
&lt;/h3&gt;

&lt;p&gt;Yes. Uptime monitoring checks that your server responds to HTTP requests. Cron monitoring checks that scheduled tasks actually execute on time and succeed. A server can have 100% uptime while every cron job on it is silently failing — wrong permissions, expired credentials, full disk, or a killed cron daemon. These are different failure modes that require different monitoring.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm the founder of &lt;a href="https://runhooks.app" rel="noopener noreferrer"&gt;Runhooks&lt;/a&gt;, one of the tools compared in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cron</category>
      <category>monitoring</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Best External Cron Job Services Compared (2026)</title>
      <dc:creator>Ronen Cypis</dc:creator>
      <pubDate>Tue, 09 Jun 2026 12:10:00 +0000</pubDate>
      <link>https://dev.to/ronency/best-external-cron-job-services-compared-2026-8a3</link>
      <guid>https://dev.to/ronency/best-external-cron-job-services-compared-2026-8a3</guid>
      <description>&lt;p&gt;You have an API endpoint that needs to run on a schedule. Maybe it syncs data from a third party, sends a digest email, or cleans up expired records. You don't want to manage a cron server. You want something external to call your endpoint on time, retry if it fails, and tell you when something goes wrong.&lt;/p&gt;

&lt;p&gt;That's what external cron job services do. But the options range from free community tools to enterprise cloud products, and they make very different tradeoffs. This article compares six of them honestly — what they're good at, where they fall short, and who should use each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Pricing&lt;/th&gt;
&lt;th&gt;Retries&lt;/th&gt;
&lt;th&gt;Execution Logs&lt;/th&gt;
&lt;th&gt;Failure Alerts&lt;/th&gt;
&lt;th&gt;Vendor Lock-in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cron-job.org&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;⚠️ Limited&lt;/td&gt;
&lt;td&gt;⚠️ 1 day history&lt;/td&gt;
&lt;td&gt;⚠️ Email only&lt;/td&gt;
&lt;td&gt;✅ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EasyCron&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;From $12/mo&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Email, webhook&lt;/td&gt;
&lt;td&gt;✅ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cronhooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free + paid&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;td&gt;⚠️ Email only&lt;/td&gt;
&lt;td&gt;✅ None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Cloud Scheduler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.10/job/mo&lt;/td&gt;
&lt;td&gt;✅ Via Pub/Sub&lt;/td&gt;
&lt;td&gt;⚠️ Cloud Logging&lt;/td&gt;
&lt;td&gt;⚠️ Cloud Monitoring&lt;/td&gt;
&lt;td&gt;❌ GCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS EventBridge&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$1/M invocations&lt;/td&gt;
&lt;td&gt;⚠️ Via destinations&lt;/td&gt;
&lt;td&gt;⚠️ CloudWatch&lt;/td&gt;
&lt;td&gt;⚠️ CloudWatch&lt;/td&gt;
&lt;td&gt;❌ AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runhooks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free + paid&lt;/td&gt;
&lt;td&gt;✅ Exponential backoff&lt;/td&gt;
&lt;td&gt;✅ Full response&lt;/td&gt;
&lt;td&gt;✅ Email, webhook&lt;/td&gt;
&lt;td&gt;✅ None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Cron-job.org
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; A free, community-run service that sends HTTP requests to URLs on cron schedules. One of the most well-known options for basic cron pinging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Completely free. Funded by donations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt; Unlimited cron jobs, standard cron expressions, basic execution history (last 24 hours), email notifications on failure, timezone support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; No SLA or uptime guarantees — it's a community project. Retry behavior is basic with no exponential backoff. No webhook alerting, no API for programmatic management, no response body capture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Hobby projects and non-critical tasks where a missed execution won't cause business impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  EasyCron
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; One of the longest-running paid cron job services. HTTP-based scheduling with more features and reliability than free alternatives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; From $12/month. Scales by job count, minimum interval, and execution timeout. No free tier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt; Configurable retries, execution logging with response details, email and webhook notifications, REST API, custom headers and bodies, IP whitelisting, cron expression builder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; No free tier. The interface is dated. Pricing can escalate for teams with many jobs or short intervals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams that need a proven, reliable paid service. EasyCron's long track record matters if you've been burned by free tools disappearing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cronhooks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; HTTP-based cron scheduling focused on webhooks. Supports both recurring and one-time scheduled requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier with limited jobs, plus paid plans for higher volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt; Recurring and one-time scheduled HTTP requests, retry on failure, execution history, email notifications, REST API, timezone support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Smaller user base than EasyCron or cloud providers. Documentation is limited. Webhook alerting and configurable retry strategies are less mature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers who need a straightforward HTTP scheduler with a free tier. A reasonable middle ground between Cron-job.org and enterprise solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Cloud Scheduler
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Google's fully managed cron service within GCP. Triggers HTTP endpoints, Pub/Sub topics, and App Engine routes on cron schedules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; $0.10/job/month (first three free). Downstream costs (Cloud Functions, Pub/Sub, Cloud Run) add up depending on what jobs trigger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt; Tight GCP integration (Pub/Sub, Cloud Functions, Cloud Run), configurable retry policies with exponential backoff, IAM-based auth, logs via Cloud Logging, alerting via Cloud Monitoring, enterprise SLA (99.5%+).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; Requires a GCP project and billing account even for basic HTTP scheduling. IAM roles, service accounts, and Pub/Sub topics add configuration overhead. Vendor lock-in. Overkill if you're not already on GCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams already on GCP who want scheduling integrated with their IAM, logging, and monitoring stack. If your backend is Cloud Run or Cloud Functions, this is the natural choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS EventBridge Scheduler
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; AWS's managed scheduling service. Invokes over 270 AWS services on a schedule, including Lambda, Step Functions, SQS, and HTTP endpoints (via API Destinations).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; $1/million invocations (first 14 million/month free). Extremely cheap at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt; Deep AWS integration, one-time and recurring schedules, cron and rate expressions, retry policies with dead-letter queues, IAM security, massive scale (millions of schedules per account).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt; External HTTP endpoints require API Destinations setup (connection resources, IAM roles, OAuth). The AWS console is complex for simple HTTP scheduling. Monitoring is split across CloudWatch and EventBridge consoles — no unified execution dashboard. Full vendor lock-in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams already on AWS who need to trigger Lambda, Step Functions, or other AWS services. For simple "hit this URL every hour" outside AWS, the setup overhead isn't worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Runhooks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt; Runhooks is a managed HTTP job scheduler built specifically for developers who need to fire HTTP requests on cron schedules. It focuses on the core workflow: define a URL, set a schedule, get retries and alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free tier available, with paid plans for more jobs and longer log retention. See &lt;a href="https://runhooks.app/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; for current details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP-first design — every job is a URL + method + headers + body + cron expression&lt;/li&gt;
&lt;li&gt;Automatic retries with exponential backoff (configurable attempts)&lt;/li&gt;
&lt;li&gt;Full execution logging: HTTP status, response body, duration in milliseconds, attempt number&lt;/li&gt;
&lt;li&gt;Failure alerts via email and webhook when retries are exhausted&lt;/li&gt;
&lt;li&gt;Timezone support with &lt;a href="https://runhooks.app/tools/cron-visualizer" rel="noopener noreferrer"&gt;visual cron expression preview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Clean, developer-focused dashboard&lt;/li&gt;
&lt;li&gt;No cloud vendor coupling — works with any endpoint, anywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Limitations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP-only — no native integration with AWS Lambda invocations, GCP Pub/Sub, or database-level triggers&lt;/li&gt;
&lt;li&gt;Newer service compared to EasyCron or the cloud schedulers&lt;/li&gt;
&lt;li&gt;Feature set is focused on HTTP scheduling — if you need to orchestrate multi-step workflows or fan-out to queues, a cloud-native tool may be a better fit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers and small teams who need a straightforward HTTP scheduler without cloud provider complexity. If your scheduled tasks are API endpoints, Runhooks does exactly what you need with less configuration overhead than cloud-native alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;If you're evaluating cron job services, here's the fastest way to test Runhooks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://runhooks.app/register" rel="noopener noreferrer"&gt;Create an account&lt;/a&gt; — free tier available, no credit card required&lt;/li&gt;
&lt;li&gt;Add a job with your endpoint URL and cron expression&lt;/li&gt;
&lt;li&gt;Watch the execution log to verify it works&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test your cron expressions with the &lt;a href="https://runhooks.app/tools/cron-visualizer" rel="noopener noreferrer"&gt;cron expression visualizer&lt;/a&gt; before creating jobs, and check the &lt;a href="https://runhooks.app/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; when you need to scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an external cron job service?
&lt;/h3&gt;

&lt;p&gt;An external cron job service sends HTTP requests to your API endpoints on a schedule you define — typically using cron expressions. Instead of managing your own cron server, the service handles scheduling, retries, and monitoring. Your application logic stays in your backend as regular HTTP endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is cron-job.org reliable enough for production?
&lt;/h3&gt;

&lt;p&gt;Cron-job.org is community-run and free, which makes it suitable for hobby projects and non-critical tasks. However, it offers no SLA, limited retry logic, and minimal execution history. For production workloads where missed executions have business impact, a paid service with retries, alerting, and guaranteed uptime is a safer choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the best EasyCron alternative?
&lt;/h3&gt;

&lt;p&gt;It depends on your priorities. If you want a developer-focused HTTP scheduler with automatic retries, execution logs, and failure alerts, &lt;a href="https://runhooks.app/register" rel="noopener noreferrer"&gt;Runhooks&lt;/a&gt; is the closest alternative. If you need deep cloud integration, Google Cloud Scheduler or AWS EventBridge Scheduler fit better. If you need free, Cron-job.org is the main option.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a cron job service if I use AWS or GCP?
&lt;/h3&gt;

&lt;p&gt;AWS EventBridge Scheduler and Google Cloud Scheduler are solid options if your infrastructure is already on those platforms. However, they require cloud-specific configuration (IAM roles, service accounts, Pub/Sub topics) that adds complexity. A standalone cron job service is simpler if you just need to hit an HTTP endpoint on a schedule without cloud vendor coupling.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm the founder of &lt;a href="https://runhooks.app" rel="noopener noreferrer"&gt;Runhooks&lt;/a&gt;, one of the services compared in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cron</category>
      <category>devops</category>
      <category>webdev</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Scheduling Cloudflare Workers Beyond Cron Triggers</title>
      <dc:creator>Ronen Cypis</dc:creator>
      <pubDate>Tue, 09 Jun 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/ronency/scheduling-cloudflare-workers-beyond-cron-triggers-1gd7</link>
      <guid>https://dev.to/ronency/scheduling-cloudflare-workers-beyond-cron-triggers-1gd7</guid>
      <description>&lt;p&gt;Cloudflare Workers Cron Triggers let you run code on a schedule without managing infrastructure. You define a cron expression in &lt;code&gt;wrangler.toml&lt;/code&gt;, write a &lt;code&gt;scheduled&lt;/code&gt; event handler, deploy, and Cloudflare runs it. For simple, low-frequency tasks inside a single worker, this works well.&lt;/p&gt;

&lt;p&gt;Then your project grows. You need a sixth scheduled task, or your nightly data sync fails silently and nobody notices for three days, or you want to schedule calls across multiple workers. That's where Cron Triggers start hitting walls.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Cloudflare Cron Triggers Work
&lt;/h2&gt;

&lt;p&gt;Cron Triggers are configured in your &lt;code&gt;wrangler.toml&lt;/code&gt; file and handled by a &lt;code&gt;scheduled&lt;/code&gt; event in your worker code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# wrangler.toml&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-worker"&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"src/index.ts"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2024-01-01"&lt;/span&gt;

&lt;span class="nn"&gt;[triggers]&lt;/span&gt;
&lt;span class="py"&gt;crons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"0 */6 * * *"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"0 0 * * MON"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/index.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScheduledEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cron&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 */6 * * *&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncExternalData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 0 * * MON&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWeeklyReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;ExportedHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cloudflare evaluates the cron expressions and invokes your worker's &lt;code&gt;scheduled&lt;/code&gt; handler at the specified times. You can use the &lt;code&gt;event.cron&lt;/code&gt; property to differentiate between triggers when a single worker has multiple schedules.&lt;/p&gt;

&lt;p&gt;This is straightforward for one or two tasks. The problems surface as you add more.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Limitations of Cron Triggers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trigger limits per worker
&lt;/h3&gt;

&lt;p&gt;The free plan allows &lt;strong&gt;3 cron triggers per worker&lt;/strong&gt;. The paid Workers plan ($5/month) increases this to &lt;strong&gt;5 cron triggers per worker&lt;/strong&gt;. If your project needs 8 scheduled tasks, you must split them across multiple workers — each with its own &lt;code&gt;wrangler.toml&lt;/code&gt;, deployment, and codebase. This adds operational overhead for what should be a scheduling concern, not an architecture concern.&lt;/p&gt;

&lt;h3&gt;
  
  
  No retries on failure
&lt;/h3&gt;

&lt;p&gt;If your &lt;code&gt;scheduled&lt;/code&gt; handler throws an error, exceeds the CPU time limit, or fails for any reason, Cloudflare does not retry the execution. The failed run is gone. The next attempt happens at the next scheduled tick — which could be hours or days away depending on the cron expression.&lt;/p&gt;

&lt;p&gt;For a task that cleans up temporary files, a missed run is tolerable. For a task that reconciles billing data or sends time-sensitive notifications, a silent failure can have real consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limited execution visibility
&lt;/h3&gt;

&lt;p&gt;Cron Triggers provide basic invocation metrics through Workers Analytics — you can see that a trigger fired and whether the worker returned successfully. But there's no structured log of what happened inside the execution: how many records were processed, what the response payload was, or how long individual operations took. Getting that level of detail requires wiring up a separate logging pipeline to a service like Logpush or a third-party provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  No failure alerts
&lt;/h3&gt;

&lt;p&gt;When a cron trigger fails, Cloudflare doesn't send you an email or a webhook. You find out when you check the dashboard, or when a downstream system breaks because the scheduled task stopped running. There's no built-in way to get notified when a scheduled handler starts failing consistently.&lt;/p&gt;

&lt;h3&gt;
  
  
  CPU time constraints
&lt;/h3&gt;

&lt;p&gt;CPU time limits vary by plan:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Free plan:&lt;/strong&gt; 10ms CPU time per execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundled (paid):&lt;/strong&gt; 50ms CPU time per execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Standard/Unbound (paid):&lt;/strong&gt; 30 seconds CPU time per execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the free plan, 10ms of CPU time is tight for anything beyond trivial operations. Even on the paid Bundled plan, 50ms limits what you can do in a single scheduled execution. The Standard usage model gives 30 seconds, which covers most workloads, but you need to be on the paid plan to access it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Cron Triggers Are Enough
&lt;/h2&gt;

&lt;p&gt;Cron Triggers work well when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have &lt;strong&gt;5 or fewer&lt;/strong&gt; scheduled tasks within a single worker&lt;/li&gt;
&lt;li&gt;Tasks are &lt;strong&gt;simple and fast&lt;/strong&gt; — cache purges, KV store updates, lightweight API calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missed runs are tolerable&lt;/strong&gt; — the next scheduled tick will catch up&lt;/li&gt;
&lt;li&gt;You don't need &lt;strong&gt;per-execution logs&lt;/strong&gt; or &lt;strong&gt;failure notifications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Your workload fits within the &lt;strong&gt;CPU time limits&lt;/strong&gt; of your plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your scheduled tasks check these boxes, Cron Triggers are the simplest solution. No external dependencies, no extra services, no additional cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Need More
&lt;/h2&gt;

&lt;p&gt;The moment you need any of the following, Cron Triggers become insufficient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;More than 5 scheduled tasks&lt;/strong&gt; without splitting across workers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic retries&lt;/strong&gt; when a scheduled handler fails&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution logs&lt;/strong&gt; with HTTP status, response body, and duration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure alerts&lt;/strong&gt; via email or webhook&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling across multiple workers&lt;/strong&gt; or mixing Workers with other services (APIs on Railway, Cloud Functions, Vercel routes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex scheduling&lt;/strong&gt; with timezone awareness and DST handling&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The External Scheduler Approach
&lt;/h2&gt;

&lt;p&gt;Every Cloudflare Worker with a &lt;code&gt;fetch&lt;/code&gt; handler is an HTTP endpoint. Instead of relying on the &lt;code&gt;scheduled&lt;/code&gt; handler, you can expose your task logic through &lt;code&gt;fetch&lt;/code&gt; and trigger it externally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/index.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Verify the request is from your scheduler&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRON_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/tasks/sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncExternalData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sync&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/tasks/report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateWeeklyReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;report&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;404&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;ExportedHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes the architecture: your worker becomes an HTTP API with multiple task endpoints, and an external scheduler handles the timing. You get separate URLs for separate tasks, structured JSON responses, and standard HTTP status codes that an external system can act on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding authorization
&lt;/h3&gt;

&lt;p&gt;Protect your worker from unauthorized requests using a secret stored in Workers secrets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler secret put CRON_SECRET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;fetch&lt;/code&gt; handler checks the &lt;code&gt;Authorization&lt;/code&gt; header against this secret (shown above). Configure the same &lt;code&gt;Bearer &amp;lt;token&amp;gt;&lt;/code&gt; header in your external scheduler. For additional security, restrict requests by IP using Cloudflare Access or validate a signed HMAC.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step-by-Step Setup With Runhooks
&lt;/h2&gt;

&lt;p&gt;Runhooks is a scheduled HTTP execution service. It calls your endpoints on a schedule with retries, logging, and failure alerts. Here's how to schedule a Cloudflare Worker:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Deploy your worker with a fetch handler&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use the &lt;code&gt;fetch&lt;/code&gt; handler pattern shown above. Each task gets its own path (&lt;code&gt;/tasks/sync&lt;/code&gt;, &lt;code&gt;/tasks/report&lt;/code&gt;, etc.). Deploy with &lt;code&gt;npx wrangler deploy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Set a secret for authorization&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler secret put CRON_SECRET
&lt;span class="c"&gt;# Enter a strong random string when prompted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Create a job in Runhooks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; "Sync external data"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;code&gt;https://my-worker.your-subdomain.workers.dev/tasks/sync&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method:&lt;/strong&gt; &lt;code&gt;GET&lt;/code&gt; or &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Headers:&lt;/strong&gt; &lt;code&gt;Authorization: Bearer &amp;lt;your-cron-secret&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule:&lt;/strong&gt; &lt;code&gt;0 */6 * * *&lt;/code&gt; (every 6 hours)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries:&lt;/strong&gt; 3 attempts with exponential backoff&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Create a second job for the weekly report&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; "Weekly report"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL:&lt;/strong&gt; &lt;code&gt;https://my-worker.your-subdomain.workers.dev/tasks/report&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule:&lt;/strong&gt; &lt;code&gt;0 0 * * MON&lt;/code&gt; (every Monday at midnight)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timezone:&lt;/strong&gt; your preferred timezone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries:&lt;/strong&gt; 3 attempts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each job runs independently with its own schedule, retry policy, and alerting. No cron trigger limits. No splitting tasks across multiple workers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What you gain over Cron Triggers
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No trigger limits&lt;/strong&gt; — schedule 20 tasks against one worker, or spread them across 10 workers. Each is a separate HTTP job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic retries&lt;/strong&gt; — if a cold start, network blip, or transient error causes a failure, the next attempt fires within seconds instead of waiting for the next cron tick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution logs&lt;/strong&gt; — every invocation is recorded with HTTP status code, response body, and duration in milliseconds. When your sync processed 2,400 records in 1.8 seconds, you see it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failure alerts&lt;/strong&gt; — email or webhook notifications when a job starts failing. You find out in minutes, not days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timezone-aware scheduling&lt;/strong&gt; — set a job to run at 9 AM &lt;code&gt;America/New_York&lt;/code&gt; and it stays at 9 AM through DST transitions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-service scheduling&lt;/strong&gt; — schedule a Cloudflare Worker, a Vercel API route, and a Railway endpoint from the same dashboard. Cron Triggers only work within Cloudflare.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Keeping the Scheduled Handler as a Fallback
&lt;/h2&gt;

&lt;p&gt;You don't have to remove your &lt;code&gt;scheduled&lt;/code&gt; handler when switching to external scheduling. A worker can have both — the external scheduler handles the primary schedule with retries and logging, while a cron trigger fires as a lower-frequency fallback (say, once daily) to catch anything missed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// External scheduler entry point&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CRON_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncExternalData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;scheduled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ScheduledEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Fallback: runs even if the external scheduler is down&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;syncExternalData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;satisfies&lt;/span&gt; &lt;span class="nx"&gt;ExportedHandler&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your task logic lives in a shared function, so there's no code duplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Cloudflare Workers are fast and cheap to run — the cron trigger limitations are the main constraint for scheduled workloads, and they're straightforward to work around:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Expose your task logic through a &lt;code&gt;fetch&lt;/code&gt; handler with authorization&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://runhooks.app/register" rel="noopener noreferrer"&gt;Try Runhooks&lt;/a&gt; and schedule your workers at any frequency&lt;/li&gt;
&lt;li&gt;Get retries, execution logs, and failure alerts that Cron Triggers don't provide&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Preview your cron expressions with the &lt;a href="https://runhooks.app/tools/cron-visualizer" rel="noopener noreferrer"&gt;cron expression visualizer&lt;/a&gt;, and &lt;a href="https://runhooks.app/pricing" rel="noopener noreferrer"&gt;compare plans&lt;/a&gt; when you need more jobs or longer log retention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How many cron triggers can a Cloudflare Worker have?
&lt;/h3&gt;

&lt;p&gt;On the free plan, each Cloudflare Worker can have up to 3 cron triggers. On the paid Workers plan ($5/month), the limit increases to 5 cron triggers per worker. If you need more scheduled tasks, you must split them across multiple workers or use an external scheduler to trigger your workers via HTTP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Cloudflare Cron Triggers have built-in retries?
&lt;/h3&gt;

&lt;p&gt;No. If a scheduled handler throws an error or times out, Cloudflare does not retry the execution. The failed run is lost and the next attempt happens at the next scheduled tick. For tasks where missed runs are costly — data syncs, billing reconciliation, alert checks — you need an external scheduler that retries on failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I trigger a Cloudflare Worker on a cron schedule without using Cron Triggers?
&lt;/h3&gt;

&lt;p&gt;Yes. Every Cloudflare Worker with a fetch handler is a standard HTTP endpoint. You can use an external HTTP scheduler like &lt;a href="https://runhooks.app/register" rel="noopener noreferrer"&gt;Runhooks&lt;/a&gt; to send a request to your Worker's URL on any cron schedule. This bypasses the cron trigger limit entirely and adds retries, logging, and failure alerts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the CPU time limits for Cloudflare Workers Cron Triggers?
&lt;/h3&gt;

&lt;p&gt;On the free plan, each cron trigger execution is limited to 10ms of CPU time. On the paid Bundled plan, the limit is 50ms. If you use the Standard (unbound) usage model, CPU time extends to 30 seconds per execution. These limits apply equally to both scheduled and fetch handlers.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I'm the founder of &lt;a href="https://runhooks.app" rel="noopener noreferrer"&gt;Runhooks&lt;/a&gt;, one of the tools mentioned in this article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudflare</category>
      <category>serverless</category>
      <category>cron</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
