Put the schedule and the work in separate processes: let a cron trigger enqueue a small, idempotent job record, then let queue workers claim that record and run the long cleanup or report outside the scheduler's 15-minute limit. The deciding constraint is ownership of execution time. A scheduler should own when a job becomes eligible, not the entire lifetime of the job.

Short answer: use cron as a trigger, a durable queue as the handoff, and an idempotency key plus a lease as the boundary around long-running background work.

This pattern applies cleanly to a Node.js service even though the sample below is Go; the important contract is the message envelope and claim protocol, not an SDK. Don't make the web process wait for report generation. It should be able to restart without losing the schedule or creating a second logical run.

How should a Node.js cron trigger enqueue long-running background jobs?

Treat each scheduled occurrence as data. For a cleanup scheduled at 02:00 UTC, derive a stable key such as cleanup:2026-08-07T02:00:00Z. Insert or enqueue that key atomically. If the trigger runs twice, both attempts refer to the same logical job and only one insert wins. This is the first idempotency boundary.