# Free Inngest Alternative: Cron and Node Workers on a VPS

> Build your own free Inngest and BullMQ alternative: scheduled background tasks and worker scripts on your VPS without Redis or cloud queue fees.

Source: https://boringpush.com/blog/inngest-alternative-nextjs-cron-workers

Modern Next.js tutorials suggest that even the simplest background tasks need a queue service.

Inngest.

Upstash QStash.

Or a managed Redis running BullMQ.

Those services have free tiers and paid plans. Check their current pricing before you decide.

If all you need is housekeeping, a queue service is more than the job asks for.

- Trim the database's write-ahead file every hour.
- Take a daily database snapshot.
- Clean up old analytics events.
- Rotate logs so they do not fill the disk.

![Cron to Node worker to atomic backup pipeline](/blog/inngest-alternative-nextjs-cron-workers/vps-worker-pipeline.svg "crontab wakes one Node script; the script does its job, logs, exits; the OS takes its memory back.")

## What you need

A schedule. Small jobs that run, finish and free their memory.

Nothing sitting in the background eating RAM between runs. No vendor account. No queue to watch.

Group jobs by how often they run.

Every minute.

Hourly.

Daily.

Weekly.

Monthly.

Each job is one small script. To add one, put it in the right group.

## Safe by default

Two properties matter.

The same group never runs twice at once. A slow job cannot pile runs on top of each other.

And when any job in a group fails, the run exits with an error and writes the details to a log file. It does not fail silently.

## Inngest or scheduled jobs

| | Inngest or a hosted queue | Scheduled jobs on your server |
|---|---|---|
| Monthly cost | A plan once you outgrow the free tier | Nothing extra |
| Moving parts | A vendor account plus your app | The scheduler already on your server |
| Retries and fan-out | Built in | You write them |
| Timing | Event-driven, sub-minute | Minute, hourly, daily, weekly, monthly |
| Where it breaks | A vendor outage or plan limit | Your server |

## What the kit already runs

Daily, a database snapshot and old analytics cleanup.

Hourly, trimming the write-ahead file so it cannot grow without limit.

Weekly, log rotation. Monthly, compacting the databases.

These are the chores most apps forget. They run without you.

## A first job in ten minutes

Pick one chore you do by hand today. A weekly sales summary you email yourself.

Write it as a small script that does the work and exits. Make it safe to run twice.

Put it in the weekly group. Run it by hand once and watch the output. Let the schedule take over.

Check the log the next morning. If it ran, you replaced a calendar reminder with a job.

## Keep an eye on it

Read the log files once a week. A job that quietly stopped is the main risk with any scheduler.

A failed scheduled job writes to a log and exits with an error. It does not message your phone on its own.

The kit's Telegram and Discord alerts cover payment and server failures. If you want a scheduled job to ping you, call the alert from that job.

## Where cron stops and queues take over

A scheduler like cron fits fixed calendar shapes.

Nightly database snapshots, hourly WAL checkpoints, and log rotations. Cron is fire-and-forget: it has no dynamic job payload, and no automatic retry with backoff.

If you need event-driven tasks that retry when an API fails (like welcome emails or Stripe webhooks), you need a real queue. BoringPush includes `boringqueue` on SQLite for that work, with automatic retries and zero Redis bills.

## Make every job safe to run twice

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 means a crash costs one row. A job that can re-run fears nothing, including 3 a.m.

## Know this first

Use the right tool for the job shape.

Cron runs on a fixed clock. It has no payload and no retry backoff.

BoringQueue runs on events (`dispatchJob`). It handles dynamic payloads, retries with exponential backoff, and dead-letter recovery.

For scheduled housekeeping, cron is exactly enough. For user-triggered background tasks, `boringqueue` takes over.

## Why BoringPush is the way to do this

Background jobs without a vendor.

BoringPush installs the schedule when it sets up your server. Backups, cleanup and log rotation already run.

When you need an event queue, `boringqueue` is already built into the codebase.

## FAQ

### Is this really a free Inngest alternative?

For scheduled work and background jobs, yes. Cron handles the time-based beats, while `boringqueue` handles transactional jobs with retries, without paying cloud SaaS fees.

### Do I need Redis?

No. The jobs run on the scheduler already on your server. There is nothing to install and nothing to pay for.

### What happens if a job fails?

The run exits with an error and the details go to a log file. The kit does not send a phone alert for scheduled failures on its own, so add one to any job you care about.

### Can two runs overlap?

No. Each group takes a lock before it starts, and a second run of the same group skips itself if the first is still going.

### Can jobs send email?

Yes, a job can use the same email sending as the rest of the app. Make the job safe to run twice, so a retry never sends the same email again.

### Where do I add my own job?

In the group that matches how often it should run. Write a small script, put it there and it runs on that schedule.

## Keep reading

- [Background Workers Without Redis in Next.js](/blog/background-workers-no-redis-nextjs) — the pattern in more detail
- [Free Vercel Cron Alternative: Run Jobs Every Minute](/blog/free-vercel-cron-alternative-every-minute) — the same schedule, framed against Vercel's limits
- [SQLite in Prod Is Fine. You're Just Missing 3 Settings](/blog/sqlite-prod-wal-backups-vacuum) — what the housekeeping jobs protect
