# How to Set Up Cron Jobs in the Cloud
Every application eventually needs scheduled work: nightly reports, cache warming, expiring sessions, sending reminder emails. The old answer was a line in a server's crontab. In the cloud, that's the wrong answer — here's how to do it properly.
Why not just use a server crontab?
A traditional crontab on a VM has real problems in modern deployments:
- No redundancy — if that one server dies, your jobs silently stop.
- Scales wrong — run three app servers and your job runs three times.
- Invisible failures — cron emails output to a local mailbox nobody reads.
- Couples scheduling to a host — your schedule lives in infrastructure, not code.
Cloud cron jobs solve these by running scheduled work as isolated, observable, single-execution tasks.
Cron syntax refresher
The five-field cron expression still rules scheduling:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sun=0)
│ │ │ │ │
* * * * *Common examples:
| Expression | Meaning |
|---|---|
*/15 * * * * | Every 15 minutes |
0 * * * * | Every hour, on the hour |
0 2 * * * | Daily at 02:00 |
0 9 * * 1 | Every Monday at 09:00 |
0 0 1 * * | First day of each month |
If you're ever unsure, [crontab.guru](https://crontab.guru/) is the standard sanity-check tool. And always confirm the timezone your platform uses — many run cron in UTC, which trips people up around daylight saving.
Rule 1: Make your jobs idempotent
The most important rule of scheduled work: assume your job may run more than once. Networks retry, schedulers occasionally double-fire, and you'll trigger manually while debugging. Design so a second run does no harm.
# Bad: blindly charges every matching invoice
def bill_customers():
for inv in unpaid_invoices():
charge(inv) # double-run = double charge
# Good: guard with a state transition
def bill_customers():
for inv in unpaid_invoices():
if claim_for_billing(inv.id): # atomic: PENDING -> PROCESSING
charge(inv)Use database row locking, status columns, or a unique key on the work item to ensure exactly-once *effects* even with at-least-once *execution*.
Rule 2: Set timeouts and bound the work
A job that hangs forever is worse than one that fails. Set a maximum runtime, and process work in bounded batches rather than "all rows ever."
async function processBatch() {
const deadline = Date.now() + 4 * 60 * 1000; // 4 min budget
let rows;
do {
rows = await fetchPending(100);
for (const row of rows) {
if (Date.now() > deadline) return; // resume next run
await process(row);
}
} while (rows.length === 100);
}Rule 3: Make failures visible
A cron job that fails silently is a future incident. At minimum:
- Log structured output to a system you actually monitor.
- Emit a success heartbeat (e.g. ping a dead-man's-switch service) so you're alerted when a run *doesn't* happen.
- Alert on consecutive failures, not single transient ones.
The "didn't run" failure is the sneakiest — without a heartbeat, a job that silently stops looks identical to a job that has nothing to do.
Setting up cron jobs on PandaStack
PandaStack includes cron jobs as a first-class app type — on every plan, including the free tier. You define the schedule with standard cron syntax and the platform runs your task as an isolated container execution. Each run is built and executed the same way your deployments are, with live execution logs so you can see exactly what happened on every run, rather than digging through a server mailbox.
Because each run is its own isolated execution rather than a line on a shared host, you avoid the "runs three times because I have three app servers" problem entirely — the scheduler fires once. You still own idempotency in your own code, since the platform guarantees the schedule, not the business logic.
The typical flow:
- 1Point a cron job at your repo (or a command in your existing image).
- 2Set the cron expression, e.g.
0 2 * * *. - 3Deploy — the platform schedules it and runs it on time.
- 4Watch live logs per run; review history to confirm executions.
A checklist before you ship a cron job
- ✅ Is the schedule in the right timezone?
- ✅ Is the job idempotent (safe to run twice)?
- ✅ Does it have a runtime budget and batch the work?
- ✅ Are failures logged and alertable?
- ✅ Is there a heartbeat so you notice a *missed* run?
- ✅ Does it read secrets from env, not hardcoded values?
References
- [crontab.guru — cron schedule expression editor](https://crontab.guru/)
- [Wikipedia: Cron](https://en.wikipedia.org/wiki/Cron)
- [Kubernetes CronJob documentation](https://kubernetes.io/docs/concepts/workloads/controllers/cron-jobs/)
- [POSIX crontab specification](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html)
Need scheduled tasks without managing a server? Cron jobs are included on PandaStack's free tier — get started at [dashboard.pandastack.io](https://dashboard.pandastack.io).