# Free Self-Hosted Inngest Alternative for Next.js

> Escape SaaS step limits and webhook lock-in. A $0 self-hosted background worker engine for Next.js running on your own VPS.

Source: https://boringpush.com/blog/free-selfhosted-inngest-alternative-nextjs

Inngest and Trigger.dev are popular tools in the modern Next.js ecosystem.

They solved a real headache: running asynchronous tasks and workflows from serverless environments like Vercel without managing a standalone Redis cluster.

Their developer experience is sleek, their SDKs are clean, and their documentation is polished.

Then your application starts growing, and you run into the realities of hosted background job platforms.

## The pricing cliff of hosted workflow services

Hosted background job engines use a business model based on execution steps.

Every time a background task runs a step, delays execution, or retries after a failure, a counter increments on their servers.

When your application is in private beta, their free tier handles your traffic without issue.

Once you launch real features (like automated onboarding emails, daily activity digests, Stripe webhook synchronization, or web scraping routines), your step counts explode.

Suddenly, you exceed the free tier boundary.

The pricing jump is steep. You jump from zero dollars to twenty-five, fifty, or one hundred dollars per month for a single service.

If you run multiple side projects or client applications, those monthly subscriptions compound quickly.

## The architectural risk: Webhook tunnel lock-in

Beyond the monthly financial cost, there is an architectural dependency to consider.

Cloud background platforms do not execute your code on their servers. They orchestrate work by sending HTTP webhook requests back into your Next.js application routes (`/api/inngest`).

This architectural model introduces three distinct points of friction:

First, your application routes must be publicly accessible on the internet for jobs to run. During local development, you must run background tunnels or local gateway emulators.

Second, if the cloud provider experiences an infrastructure outage, their orchestrator stops dispatching webhooks to your server, halting your background processing.

Third, your customer data and execution payloads travel across the public internet to third-party servers, creating compliance and data residency questions.

You are renting external infrastructure to orchestrate code that could run right on your own machine.

## The alternative: What self-hosted background workers look like

If you deploy your Next.js application to a virtual private server, you do not need an external cloud orchestrator.

You already have a dedicated CPU, local storage, and a running operating system.

In BoringPush, the self-hosted alternative is `boringqueue`. It gives you the core capabilities of hosted platforms without the external subscription.

| Feature | Hosted Inngest / Trigger.dev | BoringQueue (Self-Hosted) |
|---|---|---|
| Monthly cost | $0 on free tier, then $25 to $100+/mo | $0 flat on your existing VPS |
| Execution limits | Bounded by step limits and credit tiers | Unlimited jobs, bounded only by hardware |
| Network latency | Network round-trip over public webhooks | 0.2ms local disk write |
| Third-party dependency | High (outage halts job dispatch) | Zero (completely self-contained) |
| Local development | Requires local Dev Server / Tunnel | Runs natively with Node (`bp jobs work`) |
| Inspection | Browser web dashboard | Command-line CLI (`bp jobs list`) |

## How to dispatch and handle jobs in BoringPush

The developer experience inside BoringPush mirrors the simplicity of modern workflow tools, without the external dependency.

To dispatch a background task from any Next.js route:

```javascript
import { dispatchJob } from "@/libs/boringqueue";

export async function POST(req) {
  const { userId, plan } = await req.json();

  // Dispatched locally to SQLite in under 1ms
  dispatchJob("billing/activate", { userId, plan }, {
    maxAttempts: 5,
    runAt: "+1m",
    referenceKey: `activate-${userId}`,
  });

  return Response.json({ status: "processing" });
}
```

The job logic lives right in your repository under `scripts/jobs/billing/activate.mjs`:

```javascript
export default async function (payload, { job, signal }) {
  const { userId, plan } = payload;

  // Real work happens here with your local DB models
  await provisionUserAccount(userId, plan, { signal });

  // Finished cleanly: mark completed
  return { activated: true };
}
```

The worker process runs under PM2 in the background.

If an API call fails, it retries with automatic exponential backoff. If you dispatch duplicate jobs with the same `referenceKey`, it ignores duplicates automatically.

## When should you pay for Inngest?

Honest architecture acknowledges trade-offs. There are scenarios where paying for a hosted workflow platform is justified:

1. Complex visual step functions: If your business logic requires twenty sequential steps with conditional human-in-the-loop approvals spanning several weeks, Inngest's step engine is world-class.
2. Serverless purists: If your entire company is committed to running exclusively on Vercel without ever provisioning a single virtual private server, you must use a hosted queue.

For solo developers, indie hackers, and small teams building SaaS applications, paying for cloud queue steps is an unforced error.

You can own your background processing, run unlimited jobs, and keep your infrastructure bills at zero.

## FAQ

### Does BoringQueue require a Docker container to run?

No. Unlike self-hosting Inngest or Temporal, which require complex Docker Compose configurations, `boringqueue` is written in plain Node.js and uses embedded SQLite. It requires zero Docker containers and zero extra dependencies.

### How do I view failed jobs without a web dashboard?

BoringPush includes a built-in CLI command: `npm run bp -- jobs list --status dead`. This prints structured JSON containing the failed job IDs, payloads, and exact error stack traces directly to your terminal.

### Can I retry failed jobs after an outage?

Yes. Run `npm run bp -- jobs retry-dead`. The queue re-enqueues all dead-letter tasks for immediate execution by the worker pool.

### How much memory does the background worker use?

The PM2 worker process typically consumes around thirty to fifty megabytes of RAM while idle, scaling up slightly during heavy compute tasks.

### What happens if I outgrow a single VPS?

A single $5 Hetzner VPS can comfortably process hundreds of thousands of background jobs per day on SQLite. If you reach the scale where a single server is bottlenecked, your revenue will easily justify migrating to enterprise distributed message brokers.

## Keep reading

- [Next.js Background Jobs Without Redis: $0 BullMQ](/blog/nextjs-background-jobs-no-redis-bullmq-alternative) — the core queue engine
- [Why Next.js Background Tasks Die in Production](/blog/why-nextjs-background-tasks-die-in-production) — solving serverless freezes
- [Free Vercel Cron Alternative: Jobs Every Minute](/blog/free-vercel-cron-alternative-every-minute) — scheduled task alternatives
