Cron anti-patterns we keep seeing
A short field guide to the schedules that look fine in review but cause production incidents.
Patterns that get past code review and break in week three.
Top-of-the-hour everything
0 * * * * for every hourly job in the system. When you have five of these and they all hit the same downstream API or the same database at exactly the same second, you create a self-inflicted thundering herd. Stagger them: 0 * * * *, 7 * * * *, 13 * * * *, etc. The downstream side will thank you.
Midnight in "local time"
0 0 * * * in the system's local timezone. The local timezone changes when the host moves regions, when daylight saving fires, when somebody runs the same container in another data center. Always specify the zone explicitly in the cron config, or schedule in UTC.
Frequent polling that exists because someone never wrote a webhook
*/1 * * * * hitting a third-party API to check for a state change. The provider almost always has a webhook. Use it. If they don't, exponential backoff with ETag or If-Modified-Since usually works. A literal every-minute poll is rarely the right answer.
The single "cleanup" cron that does seven unrelated things
One script, scheduled at 03:00, that purges old sessions and rotates log files and rebuilds a search index and sends a daily digest email. When it breaks, six things break and you find out from six different stakeholders. Split it.
Cron jobs whose body is a single SQL DELETE
A scheduled DELETE FROM events WHERE created_at < NOW() - INTERVAL '90 days' run nightly on a large table will eventually take longer than the cron interval, overlap with itself, and deadlock the table. Add a row-limit per run, run in batches, and watch the execution time as a metric. Or move it out of cron and into a queue worker.
No idempotency
If a cron job fires twice (fall-back DST, controller retry, scheduler bug), the result should be the same as firing once. If it isn't — if you double-send the email, double-charge the customer, double-write the row — you have a bug that will eventually fire. Build idempotency into the job, not into the scheduler.