deploy · tutorial · vps
Cron Beat Redis: $0 Background Workers
Scheduled jobs without Redis, BullMQ, or bills: system cron plus idempotent workers plus dead-letter rows. The $0 pattern inside.

A simple job needed three services
Daily digest emails. Simple, right?
The tutorial says: Redis for the queue, BullMQ for workers, a separate worker process, persistence config, monitoring for the queue.
Three services.
Two bills.
One afternoon of YAML.
To send emails once a day.
Every service you add is another thing that can fail without telling you.
A worker that exits quietly leaves jobs piling up where nobody looks.
Queue stack or scheduled jobs
| A daily digest | Redis and BullMQ | A scheduled script |
|---|---|---|
| Services to run | Redis, a worker, a monitor | The scheduler already on your server |
| Extra cost | A managed Redis plan, if you rent one | None |
| Typical failure | Eviction, a silent worker, a backlog | The script fails and says so |
| Ceiling | Millions of jobs | Hourly and daily batches |
Below real scale, queues are overkill. Three reasons.
You do not have the throughput. Millions of jobs an hour need a queue. Hundreds a day need a loop over database rows.
Every service is a pager. Persistence, worker liveness, queue depth. Three more things to watch at 2 a.m.
Silent workers are the real incident. A queue worker dies quietly while jobs pile up. A scheduled job that fails exits with an error and writes a log.
Audible failure beats a dashboard nobody opens.
What to do
Group your work by how often it runs.
Every minute.
Hourly.
Daily.
Weekly.
Monthly.
Write each job as a small script that does its work and exits.
Make every job safe to run twice. That is the whole secret of boring background work.
- A digest job picks the rows not yet sent, sends, and marks each one sent.
- A cleanup job deletes old rows in small batches and logs how many went.
- A retry job tries failed rows a limited number of times, then flags them for a human.
One unit of work per transaction. A crash costs you one row.
A job that can re-run fears nothing, including 3 a.m.
Make sure the same group never runs twice at once. A slow night must not stack runs on top of each other.
And make failure loud. A failed job should exit with an error and write its details to a log.
Where cron stops and queues take over
Scheduled cron fits calendar shapes: hourly, daily, weekly.
It does not fit event-driven user actions. If someone signs up and you need to send an email, you do not want a cron job polling your database every sixty seconds.
When a background task needs to run right away, retry on API errors, and keep payloads safe across server restarts, use a queue. BoringPush includes boringqueue for that work: one SQLite table, a worker thread pool, and zero Redis bills.
What BoringPush already runs
The kit ships the housekeeping most apps forget.
Daily, it takes a database snapshot and clears old analytics events. Hourly, it trims the database's write-ahead file so it cannot grow without limit.
Weekly, it rotates the logs. Monthly, it compacts the databases.
For transactional work, dispatchJob() pushes tasks to db/queue.db, where worker threads process them with automatic exponential backoff.
Know this first
Keep the boundary clear in your head.
Scheduled jobs (cron) run on a clock. They have no payload and no retry backoff.
Event jobs (boringqueue) run on demand. They take payloads, retry when APIs hiccup, and support delayed runs like +2h.
Neither requires Redis, BullMQ, or cloud queue subscriptions.
Why BoringPush is the way to do this
Both layers live right in your repo.
Cron for server maintenance. boringqueue for transactional background tasks.
Both run on SQLite on your $5 server. Zero external broker bills.
BoringPush runs hourly, daily, weekly and monthly jobs on the server you already have.
Backups, cleanup and log rotation are already in the box. The same group never runs twice at once.
Add a job by adding a script.
FAQ
Are background workers without Redis reliable enough for money paths?
Yes, with the two habits above: jobs that are safe to re-run and failures that are loud. Retries of failed charges or receipts fit a daily batch well. Reliability comes from re-runnability and visible failure, not from a queue brand.
How are overlapping runs prevented?
Each group of jobs takes a lock before it starts. If a previous run of the same group is still alive, the new one skips itself and says why. Watch how long your jobs take, because a runtime that keeps growing is an early warning that you are outgrowing the schedule.
Should jobs share the app process or run separately?
Separately. Each run starts fresh, so a crashing job cannot take the app down, and an app restart cannot kill a job halfway. Sharing a process couples the two uptimes.
How are job secrets handled?
Jobs read the same production settings as the app. You keep one settings file and one way of syncing it, so there is a single truth and many readers.
Can scheduled jobs retry with backoff?
Yes, if you store it in data. Keep a "try again after" time and an attempt count on the row, and let the schedule tick on a fixed beat. The schedule triggers; the rows decide who is due.
When do I actually need Redis and BullMQ?
When you need sub-minute scheduling with guarantees, priority lanes where paid jobs jump the queue, sustained throughput in the thousands per minute, or real-time fan-out. Measure first. If scheduled jobs demonstrably fail you, move over deliberately.
Keep reading
- PM2 Reloads Nobody Feels: Next.js With Zero Downtime — the reload workers survive
- git push production deploy: Skip the 9-Minute CI Queue — the hook deploying workers too
- Fix Production From Your Phone: The Grocery-Line Drill — alerts workers fire into
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


