BackendNode.jsServerBackendDevOpsTutorial

Node.js Scheduled Tasks: Automate Work with node-cron and Job Queues

Recurring jobs like email digests, database cleanup, cache warming, and report generation require reliable scheduling. Learn node-cron, BullMQ, and production patterns.

Abdur Razzak

Abdur Razzak

Full-Stack Web Developer

May 26, 2026 8 min read

Many backend applications require work that runs on a schedule rather than in response to an HTTP request. Sending a weekly newsletter requires running at a specific time every week. Cleaning up expired database records should run nightly to prevent tables from growing indefinitely. Generating daily reports must complete before business hours begin. Renewing SSL certificates must happen before the current certificate expires. Synchronizing data from an external API should run on a regular interval. Building reliable scheduled task infrastructure requires more than calling setTimeout in a startup file. You need cron expression scheduling for complex timing patterns, persistence so jobs survive application restarts, distributed locking so a job does not run on multiple clustered instances simultaneously, monitoring to detect when jobs fail or run unexpectedly long, and alerting to notify the team when a critical scheduled job misses its execution window.

node-cron: Simple In-Process Task Scheduling

The node-cron package provides a simple API for scheduling tasks inside a Node.js process using standard cron expression syntax. A cron expression is a string of five or six space-separated fields representing second, minute, hour, day of month, month, and day of week. The expression zero zero eight asterisk asterisk asterisk represents eight o'clock every morning. Ranges, step values, and lists of values in each field enable complex patterns: zero zero eight asterisk asterisk one through five runs at 8am on weekdays only. Initialize a scheduled task by calling cron.schedule with the expression and the task function. The task runs in the same Node.js event loop as the rest of the application. For tasks that perform I/O operations, use async functions as the task handler and ensure errors are caught and logged rather than allowed to produce unhandled promise rejections. node-cron is suitable for simple, low-risk tasks in single-instance deployments.

BullMQ: Reliable Scheduled Jobs with Redis Persistence

BullMQ is a production-grade job queue library that uses Redis for job persistence. It supports scheduling recurring jobs with cron expressions through its repeat option, and unlike node-cron, jobs scheduled in BullMQ persist in Redis across application restarts. If the application was down when a scheduled job was supposed to run, BullMQ can be configured to run the missed job immediately when the application comes back online. Workers that process queued jobs run in separate processes from the web server, meaning a slow or failing scheduled job does not impact request handling. Configure a job with a repeat cron expression and BullMQ automatically re-enqueues the job at each scheduled interval, ensuring the job runs even when individual application instances start and stop. Multiple workers can process different queues concurrently, with each queue having its own concurrency setting and retry configuration.

Distributed Locking: Preventing Duplicate Execution

In a clustered or multi-instance deployment, multiple processes may try to execute the same scheduled job at the same time if each instance runs its own scheduler independently. A database cleanup job running on four instances simultaneously could produce incorrect results or violate unique constraints. Distributed locking prevents this by requiring the executing process to acquire an exclusive lock before running the job, and releasing the lock when the job completes or times out. Implement distributed locking with Redis using the SET command with NX and PX options, which atomically sets a lock key only if it does not already exist and sets an expiration to prevent stale locks from blocking execution permanently. The redlock library implements the Redlock algorithm for distributed mutual exclusion across multiple Redis instances for higher availability. BullMQ inherently solves this problem for queue-based jobs because only one worker processes each job from the queue.

Monitoring Job Execution and Failures

Scheduled jobs require monitoring to detect when they fail silently or run unexpectedly long. BullMQ emits events for job completion, failure, stall, and progress that you can listen to and forward to your monitoring service. The Bull Board and Arena packages provide visual dashboards for monitoring BullMQ queues, showing job counts by state, recent job history with execution times, and failed jobs with their error messages. For critical scheduled jobs, implement a dead man's switch pattern: the job records a heartbeat timestamp in a Redis key at the start of every successful execution, and a separate monitoring service alerts when the heartbeat is older than the expected execution interval plus a grace period. Configure maximum job duration limits and kill jobs that exceed them to prevent a hung job from blocking the queue indefinitely.

Task Idempotency: Safe Retries

Scheduled tasks should be designed for idempotency, meaning that running the same task multiple times produces the same result as running it once. When a task is retried after failure midway through execution, idempotent design ensures that already-processed items are not double-processed. For database operations, use upsert semantics that insert a record if it does not exist or update it if it does, rather than unconditional inserts that fail or duplicate on retry. Track task completion state by storing a checkpoint of the last successfully processed item so retries can resume from the checkpoint rather than starting from the beginning. For tasks that send emails or make external API calls, record the successful completion of each action before moving to the next item, and check the completion record before executing each action to skip already-completed steps during a retry.

Testing and Deploying Scheduled Tasks

Testing scheduled tasks requires the ability to trigger them on demand rather than waiting for the actual scheduled time. Expose an internal API endpoint that runs a specific job immediately, protected by an internal network restriction or strong authentication. In automated test environments, replace the scheduling mechanism with a test harness that runs jobs synchronously in response to explicit test invocations. Test the job logic independently from the scheduling mechanism by extracting the task function and testing it with various inputs. In production deployments, separate the scheduler process from the web server process so that redeploying the web server does not interrupt in-progress scheduled jobs. In containerized deployments, run dedicated scheduler and worker containers alongside the web server containers, configured to share the same Redis instance for BullMQ job persistence.

Share this article

All posts
#Node.js#Server#Backend#DevOps#Tutorial
Abdur Razzak — Full Stack Web Developer

Free Consultation

Got a Project Idea? Let's Talk.