devopscover_image: https://qcanary.dev/screenshots/dashboard-overview.png
Background jobs are the invisible backbone of your Node.js app. When they fail, they fail silently.
BullMQ is a great default for Node.js background jobs. It's fast, Redis-backed, and flexible enough for emails, reports, webhooks, and retry-heavy workflows. But once BullMQ is in production, a critical question shows up quickly:
How do you monitor queues without giving another service direct access to Redis?
Many hosted queue dashboards ask for your Redis URL. That works, but it comes with heavy security tradeoffs:
π Credential exposure. Your production Redis URL gets stored by a third party.
π₯ Firewall changes. You have to open port 6379 or set up VPC peeringβjust for a dashboard.
π Compliance surface. SOC 2 and zero-trust policies require a vendor risk assessment for that access.
ποΈ Full data access. Redis has no row-level security. Once someone has the URL, they have everything.
This post walks through an alternative: using BullMQ's built-in QueueEvents emitter to monitor queues without ever sharing a Redis credential.
The Problem With Direct Redis Monitoring
A traditional hosted queue monitor connects directly to Redis and inspects queue state. That's convenient for building dashboards, but uncomfortable for production systems.
Depending on your Redis permissions and deployment model, a Redis connection can expose far more than queue metrics β keys, job state, retry metadata, delayed jobs, and sometimes payload data. Even when a vendor is trustworthy, the credential becomes another secret to audit, rotate, and protect.
The better model is simple:
Keep Redis private. Send only the monitoring events you actually need.
How BullMQ QueueEvents Changes the Game
BullMQ emits lifecycle events as jobs move through a queue. QueueEvents is the BullMQ primitive for subscribing to those changes inside your own process:
completed β failed β active β waiting β stalled β delayed β drained
Instead of polling Redis from a hosted service, you can subscribe from inside your runtime and translate these lifecycle events into monitoring records.
This eliminates:
β
Inbound network access to Redis
β
Redis credential sharing with vendors
β
The need to inspect arbitrary Redis keys
β
Firewall changes and VPC peering
Monitoring data is buffered locally and sent over standard outbound HTTPS.
The Agent Pattern in Practice
Here's how it works in a real application. First, install the agent:
bash
npm install @qcanary/agent
Then attach it to your existing BullMQ queues:
typescript
import { Queue } from "bullmq";
import { QueueMonitor } from "@qcanary/agent";
const emailQueue = new Queue("email", {
connection: { host: "127.0.0.1", port: 6379 },
});
const monitor = new QueueMonitor({
apiKey: process.env.QCANARY_API_KEY,
queues: [emailQueue],
});
await monitor.start();
The agent does a few things under the hood:
Attaches to QueueEvents as a local subscriber β your Redis connection never leaves the process.
Buffers events into batches (100 events or 5 seconds, whichever comes first).
Ships only lightweight metadata over HTTPS: job ID, queue name, status, duration, error message.
Retries with exponential backoff on failure.
Drops oldest events if the buffer fills β monitoring should never slow down your workers.
No job payloads. No Redis keys. No connection strings. Just events.
Setting Up Your First Alert
Dashboards are useful. Alerts are what make monitoring operational.
A good first alert is failure rate:
text
Queue: email
Condition: failure rate > 5%
Window: 10 minutes
Destination: Slack
One failed job may be noise. A sustained 5% failure rate over 10 minutes usually means something changed: a provider outage, expired credentials, a bad deploy, or a slow dependency.
Other useful alerts to configure:
No activity β catch queues that silently stopped receiving work.
Queue depth β catch growing backlogs before users feel the delay.
Job duration β catch slow jobs and external dependency regressions.
Comparing the Approaches
Aspect
Sharing Redis URL
Agent-Based (QueueEvents)
Credential exposure Redis URL stored by vendor None β Redis stays local
Network changes Open port 6379 or VPC peering None β outbound HTTPS only
Compliance impact Vendor risk assessment required None β no shared infrastructure
Data sent Direct Redis access (everything) Only lifecycle metadata
Payload visibility Vendor can read all job data Only metadata β payloads stay local
Setup time 10 minutes + network config 5 minutes, no network config
What to Monitor First
Start with high-impact queues:
Email delivery
Billing webhooks
User notifications
Scheduled reports
Data imports and exports
Third-party sync jobs
Keep thresholds conservative at first. After a week of history, tune them based on normal production behavior.
What's Next for QCanary? (Expanding the Ecosystem)
Right now, QCanary is built specifically for BullMQ because of its clean event model. But we know the Node.js ecosystem is diverse.
We are actively expanding our agent to support the original Bull library, so legacy Node.js applications can secure their monitoring without migrating their queue infrastructure. Furthermore, we are laying the groundwork to support queue systems in other languages, bringing this same zero-trust, agent-based observability to Python, Go, and beyond.
If you're using Bull or another queue system, we'd love to hear from you!
When Sharing Redis Still Makes Sense
The agent pattern isn't always the right call:
Local development β credential sharing doesn't matter on localhost.
Full payload inspection needed β if you need to see job data in the dashboard, the agent can't help (it intentionally excludes payloads).
Existing VPC peering β if the network boundary is already established, sharing Redis adds no new attack surface.
For everyone else β production deployments with VPCs, compliance requirements, or zero-trust policies β the agent pattern eliminates an entire class of security risk.
Try It Free
QCanary provides real-time dashboards, Slack/email/webhook alerts, and job history for BullMQ β all without ever seeing your Redis URL.
Free tier: 1 project, 3 queues, 10K events/day, 3-day history.
Setup: Install the agent, attach to your queues, events start flowing.
β qcanary.dev
β qcanary.dev/docs
Found this useful? Follow me for more Node.js infrastructure content. Questions? Drop them in the comments below.
Top comments (0)