{
  "id": 16452650,
  "title": "BullMQ in production: the retry, stalled-job and Redis connection mistakes that bite",
  "url": "https://urgent.news/2026/10/25/bullmq-in-production-the-retry-stalled-job-and-redis-connection",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-25T09:08:45.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/quietkernel/bullmq-in-production-the-retry-stalled-job-and-redis-connection-mistakes-that-bite-1go3"
  },
  "original_language": "en",
  "account": "BullMQ is a simple library to work with queues, workers and Redis connections. However, several common mistakes can cause issues under load, during deploys or when Redis restarts. This article covers these mistakes, explains why they happen, and provides solutions. All information is based on the BullMQ source (v6.4.2) and the official going-to-production guide. A lint configuration is provided at the end to catch four of these mistakes before shipping.\n\n1. Sharing one ioredis connection with maxRetriesPerRequest set to its default value. This is the most frequent mistake. If your project uses BullMQ 6, you need to install both bullmq and ioredis. Create a separate ioredis connection for each Worker, not sharing a single connection:\n\nimport { Queue, Worker } from 'bullmq';\nimport IORedis from 'ioredis';\n\nconst connection = new IORedis(process.env.REDIS_URL);\nconst queue = new Queue('emails', { connection });\nconst worker = new Worker('emails', processEmail, { connection });\n\n2. Not attaching an error listener to both Worker and Queue. Workers and Queues are Node.js events. If an error occurs and no listener is present, Node throws an error, causing the process to crash. Always listen for errors on both the Worker and Queue:\n\nworker.on('error', (err) => logger.error({ err }, 'worker error'));\nqueue.on('error', (err) => logger.error({ err }, 'queue error'));\n\n3. Assuming jobs automatically retry when an error occurs. By default, a job runs once and fails immediately if an error occurs. To enable retries, set the attempts option with a value greater than 1 and configure the backoff settings. This will ensure the job is retried according to the specified backoff strategy. If a job is unrecoverable, throw BullMQ's UnrecoverableError to stop retries:\n\nconst queue = new Queue('emails', {\nconnection,\ndefaultJobOptions: {\nattempts: 5,\nbackoff: { type: 'exponential', delay: 1000 },\nremoveOnComplete: { age: 24 * 3600, count: 1000 },\nremoveOnFail: { age: 7 * 24 * 3600, count: 5000 },\n},\n});\n\n4. Not using custom jobIds to avoid duplicates. When a webhook is delivered multiple times, adding the job with the same ID will result in duplicate jobs. To prevent this, use a custom jobId that uniquely identifies each job, such as including a unique identifier in the jobId.",
  "summary": "BullMQ is easy to start with. You create a Queue , a Worker and a Redis connection, and jobs flow. Most of the problems show up later, under load, during a deploy or when Redis restarts. This post covers common BullMQ mistakes, most of them called out in the official docs, why each one happens, and how to fix it. Everything here is checked against the BullMQ source (v6.4.2) and the official…",
  "key_points": [
    "Sharing a single ioredis connection with maxRetriesPerRequest at default value",
    "Attaching error listeners to both Worker and Queue to handle Node.js errors",
    "Setting attempts option with value > 1 and configuring backoff for job retries"
  ],
  "editors_take": "Misconfiguring connection sharing, error listeners, and retry logic turns a simple queuing library into a liability that risks process crashes and duplicate jobs under heavy production loads.",
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}