RDS
TCP
RE
Redis Reconnect
failover • readonly • retry

NestJS Redis disconnects: configure ioredis reconnect

How to configure ioredis in NestJS to reconnect safely when Redis disconnects, TCP idle timeouts happen, or ElastiCache failover returns READONLY.

6 min read08/06/2026

When this becomes a real production problem

In production, the failure mode is usually not "Redis is fully down". It is more often stuck commands, slow queues, or reconnect loops after a short network issue or an ElastiCache failover. That is where ioredis defaults tend to be too weak.

Common causes:

  • Temporary network blips
  • Redis server restart (maintenance/update)
  • Cloud provider TCP timeout
  • Load balancer connection draining

NestJS with ioredis has retry logic by default, but it is not optimized for production environments.

Retry strategy

import Redis from 'ioredis';
import { Logger } from '@nestjs/common';

const logger = new Logger('Redis');

export function createRedisConnection(): Redis {
  return new Redis({
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    password: process.env.REDIS_PASSWORD,

    // Reconnect with exponential backoff
    retryStrategy: (times: number) => {
      if (times > 10) {
        logger.error('Redis: max retries reached, giving up');
        return null; // stop retrying
      }
      const delay = Math.min(times * 100, 3000);
      logger.warn(`Redis: retry #${times} in ${delay}ms`);
      return delay;
    },

    // Reconnect on READONLY error (ElastiCache failover)
    reconnectOnError: (err: Error) => {
      logger.error('Redis connection error:', err.message);
      return err.message.includes('READONLY');
    },

    // Connection options
    keepAlive: 30000,
    connectTimeout: 10000,
    commandTimeout: 5000,
    enableOfflineQueue: true,
    maxRetriesPerRequest: 3,
    lazyConnect: false,
  });
}

Health check

import { Injectable } from '@nestjs/common';
import {
  HealthIndicator,
  HealthIndicatorResult,
} from '@nestjs/terminus';
import Redis from 'ioredis';

@Injectable()
export class RedisHealthIndicator extends HealthIndicator {
  constructor(private readonly redis: Redis) {
    super();
  }

  async isHealthy(key: string): Promise<HealthIndicatorResult> {
    try {
      const ping = await this.redis.ping();
      return this.getStatus(key, ping === 'PONG');
    } catch (err) {
      return this.getStatus(key, false, { error: err.message });
    }
  }
}

Monitoring and alerting

Listen to ioredis events to log and alert:

const redis = createRedisConnection();

redis.on('connect', () => logger.log('Redis connected'));
redis.on('ready', () => logger.log('Redis ready'));
redis.on('error', (err) => logger.error('Redis error', err));
redis.on('close', () => logger.warn('Redis connection closed'));
redis.on('reconnecting', (ms: number) =>
  logger.warn(`Redis reconnecting in ${ms}ms`)
);
redis.on('end', () => logger.error('Redis connection ended'));

With this setup, the application automatically recovers from a Redis outage without needing a restart.