How Render handles scheduled tasks
Rethinking scheduled task architecture
Scheduled tasks, or cron jobs, are backend operations that run at predefined time intervals. On many platforms, you run a cron daemon inside the same long-running container as your web server. That ties your background work to your user-facing HTTP traffic, so the two compete for the same memory and CPU, and you lose visibility into when and how each job actually ran.
On Render, you decouple scheduled tasks from your web services entirely. A cron job is its own service type, built for isolated execution. Your background jobs run on their own compute, so they don't eat into the memory or CPU your web requests need. The result is a more resilient application and clearer insight into how your jobs behave.
Cron jobs as first-class services
A Render cron job is a dedicated service type that runs on its own compute, provisioned just for scheduled execution. This isolation matters for stability. When a single web server also handles heavy background work, like database aggregations or batch email dispatch, it takes RAM and compute away from the concurrent HTTP requests it's supposed to serve. Running that work in separate, ephemeral containers removes the contention.
This isolation extends to resource allocation and billing. You can provision specific memory and CPU limits for your cron service independently of the web application tier. Because these are dedicated workloads, execution pricing remains predictable. Billing is prorated by the second based on active running time, so you only pay for the exact compute duration consumed during task execution, subject to a minimum monthly charge of $1 per cron job service. For granular allocation specifics, refer to the Render pricing documentation.
A conceptual diagram showing how Render isolates scheduled tasks from web services, unlike traditional single-container setups.
Defining the schedule as code
You define when a job runs with a standard 5-field cron expression (minute, hour, day of month, month, day of week), the same syntax you already use with Unix cron. By default, Render evaluates every cron expression against Coordinated Universal Time (UTC). Because UTC has no Daylight Saving Time (DST) shifts, your schedules don't drift or run twice when clocks change, so normalize your scheduling logic to UTC before you deploy.
You can create a cron job in the dashboard, but defining it as code is the more durable approach. When you declare the schedule in a Blueprint render.yaml file, your configuration is version-controlled and stays in sync with the application code it runs against, instead of living in manual dashboard edits.
Here's a simplified cron job definition in a render.yaml file:
For production, add environment variables, a specific branch target, and an instance size suited to your workload. This snippet simply shows the shape of the configuration.
Execution, visibility, and failure handling
A run starts when the current time matches your cron expression. Render provisions a secure, ephemeral container, runs your start command, and tears the container down as soon as the process exits. Render guarantees that at most one run of a given cron job is active at a time. If a previous run is still going when the next interval arrives, Render delays the next run until the active one finishes. Render also stops any run that exceeds 12 hours. For work that needs to run longer than that, or continuously, use a background worker instead. To test a script without waiting for the next interval, trigger a run manually from the dashboard, though doing so while a run is active cancels the active run first.
Logging and failure handling come from the same isolated model. Your container's standard output (stdout) and standard error (stderr) flow into Render's log streams, kept separate from your web traffic logs. Failure tracking keys off the process exit code: when your script exits with a non-zero status, Render records the run as failed and can alert you over email or Slack through its notifications, with no third-party monitoring to wire up.
This minimal script shows a scheduled task that exits with a clear status code for Render to track:
For production, make your scripts idempotent and connect them to external logging or alerting if you need complex rollbacks.
The step up from Heroku Scheduler
Moving from the Heroku Scheduler add-on to Render is a shift from best-effort scheduling to guaranteed scheduling. Heroku Scheduler runs jobs on one-off dynos on a best-effort basis, which Heroku's own documentation notes can mean a job runs late or, in rare cases, not at all. Render's cron jobs are a built-in service type rather than a marketplace add-on, and they run on dedicated compute. That makes them a better fit for batch operations you actually depend on.
Designing robust scheduled workloads
A few principles keep scheduled jobs reliable as they grow. Keep these in mind when you design your workloads:
- Avoid intervals shorter than your runtime: If you schedule a job more frequently than it can finish, the single-run guarantee means Render delays each next run until the active one completes. Pick an interval with room to spare, or split the work.
- Treat storage as ephemeral: Cron jobs can't provision or access a persistent disk. Write any data you need to keep to an external database or object storage.
- Make your jobs idempotent for full reversibility: A retried or partially completed run shouldn't double-apply its effects. Design your scripts so that running the same job twice doesn't duplicate database writes or leave your application in a conflicting state.