JOB
30s
BullMQ Timeout
stalled • attempt • worker

BullMQ Job stalled with ElastiCache while Redis is healthy

Why BullMQ timeout and Job stalled errors happen with AWS ElastiCache Redis, and how to fix worker and connection settings.

7 min read15/06/2026

The problem

When deploying a NestJS application to production using BullMQ with AWS ElastiCache (Redis), jobs frequently stall even though Redis appears to be running fine:

Error: Job stalled
  attempts made: 1
  timeout of 30000ms exceeded

The queue still accepts messages, but workers disconnect after a period of idle time.

Root cause

AWS ElastiCache has a default TCP keepalive timeout of 350 seconds. If a connection has no activity during that window, ElastiCache closes it from the server side.

BullMQ uses long-lived Redis connections to listen for blocking operations (BRPOP, BLMOVE). When no jobs are in the queue, the connection goes idle and ElastiCache closes it — without the client noticing.

When a worker then tries to use that closed connection, it throws a timeout error.

Configuring BullMQ correctly

There are two approaches to fix this:

Enable TCP keepalive

import { BullModule } from '@nestjs/bullmq';

BullModule.forRoot({
  connection: {
    host: process.env.REDIS_HOST,
    port: 6379,
    keepAlive: 30000,       // send keepalive every 30s
    connectTimeout: 10000,
    maxRetriesPerRequest: 3,
  },
});

Use ioredis with explicit options

import Redis from 'ioredis';

const redisConnection = new Redis({
  host: process.env.REDIS_HOST,
  port: 6379,
  keepAlive: 30000,
  enableOfflineQueue: false,
  retryStrategy: (times) => Math.min(times * 50, 2000),
  reconnectOnError: (err) => {
    const targetError = 'READONLY';
    return err.message.includes(targetError);
  },
});

BullModule.forRoot({ connection: redisConnection });

Increase the timeout on ElastiCache

In your ElastiCache parameter group, raise tcp-keepalive to 60 and set timeout to 0 (disabled):

tcp-keepalive = 60
timeout = 0

Conclusion

The issue is not ElastiCache or BullMQ individually — it's the mismatch between AWS's TCP idle timeout and BullMQ's connection lifecycle. The best solution is:

  1. Set keepAlive: 30000 in ioredis config
  2. Raise connectTimeout to 10–15s
  3. Implement a proper retry strategy

With this configuration, the worker actively sends TCP keepalive packets before ElastiCache closes the connection.