·QueueHub Team·17 min read

Securing Redis Connections: TLS, Passwords & ACLs for BullMQ

RedissecurityTLSACLBullMQ

Securing Redis Connections: A Practical Guide for BullMQ Users

Redis powers virtually every BullMQ deployment in production. It's fast, lightweight, and incredibly capable — but it was designed for trusted networks. The default Redis configuration ships with no authentication, listens on all interfaces, and grants every command to anyone who can reach the port. For a production queue system carrying job data, retry logic, and scheduling metadata, that's a critical incident waiting to happen.

If your Redis instance is compromised, your entire queue system is compromised — job payloads can be read, queues can be emptied, and workers can be disrupted. This guide covers the three layers of Redis security — passwords, ACLs, and TLS — and shows you exactly how to configure each one with BullMQ and Queue Hub.

Why Redis Security Matters

The Default Insecurity

Redis binds to 0.0.0.0:6379 by default with no authentication enabled. The "Protected Mode" feature introduced in Redis 3.2+ offers some protection by rejecting external connections when the server is running with default configuration, but it is not a security boundary — it only blocks connections from outside the loopback interface when no password is set and no bind address is explicitly configured.

The practical consequences are severe. Commands like FLUSHALL, CONFIG SET, DEBUG, and SHUTDOWN are all available to anyone who can reach the port. Real-world attacks against exposed Redis instances are well-documented: cryptocurrency miners install coin-mining cron jobs via CONFIG SET, attackers use FLUSHALL followed by ransom demands, and entire datasets are exfiltrated through SLAVEOF or replication hijacking. Shodan regularly indexes tens of thousands of openly accessible Redis servers.

What BullMQ Stores in Redis

BullMQ stores everything in Redis in plain text:

  • Job payloads: The data you pass via queue.add('job', data) is stored as a Redis hash
  • Job results and stack traces: Return values and error details from workers
  • Queue metadata: Repeatable job schedules, worker heartbeats, stalled job tracking
  • Rate limiting counters: Used by BullMQ's rate limiter

BullMQ's own documentation strongly recommends "avoid storing sensitive data in the job altogether; if not possible, encrypt before adding to the queue." But even job metadata can be sensitive — knowing that a system processes "password-reset" or "invoice-generation" jobs reveals business logic to an attacker.

If an attacker compromises your Redis connection, they can:

  1. Read all job data with simple HGETALL commands
  2. Drop queues entirely with DEL or FLUSHALL
  3. Disrupt workers by deleting or corrupting queue keys
  4. Modify job states to cause incorrect application behavior

Defense in Depth

Securing Redis for BullMQ requires multiple layers working together:

  1. Network isolation — Firewall rules, bind to loopback or trusted interfaces, avoid public exposure
  2. Authentication — Password-based auth (requirepass) or ACL-based user auth (Redis 6+)
  3. Encryption in transit — TLS to protect credentials and data on the wire
  4. Least privilege — ACL command and key restrictions to limit what each client can do
  5. Regular updates — Keep Redis and BullMQ updated for security patches

Let's dive into each authentication and encryption layer.

Layer 1 — Redis Password Authentication

requirepass (Legacy, Pre-Redis 6)

The simplest form of Redis authentication is the requirepass directive in redis.conf:

# redis.conf
requirepass s3cure-r3d1s-p@ssword!

When set, clients must authenticate with the AUTH <password> command before issuing any other commands. This approach has several characteristics:

  • Single shared password for all clients connecting to the server
  • No user isolation — every authenticated client gets the same full permissions
  • Plaintext storage in redis.conf (though the file should be readable only by the Redis user)
  • Password sent in cleartext — requires TLS to protect in transit

AUTH in Practice with BullMQ

BullMQ uses ioredis internally, which handles the AUTH command automatically. You can provide credentials either via a connection URL or an options object:

import { Queue, Worker } from 'bullmq';

// Password via URL (most common approach)
const queue = new Queue('my-queue', {
  connection: {
    url: 'redis://:s3cure-p@ss@localhost:6379',
  },
});

// Password via options object
const worker = new Worker('my-queue', async job => {
  // Process the job...
}, {
  connection: {
    host: 'localhost',
    port: 6379,
    password: 's3cure-p@ss',
  },
});

When requirepass Is Not Enough

While requirepass is a significant step up from no authentication, it has fundamental limitations:

  • Every client has full access to all commands and all keys — a compromised worker token is equivalent to full root access
  • No audit trail — you cannot tell which client performed which operation
  • No key scoping — a producer that only needs LPUSH and ZADD still gets FLUSHALL and KEYS *
  • Password is transmitted in cleartext — without TLS, anyone on the network can sniff the AUTH command

For Redis 6+, the recommended approach is to migrate from requirepass to Access Control Lists (ACLs).

Layer 2 — Redis 6+ ACLs (Access Control Lists)

ACL Basics

Redis 6 introduced named users with granular permissions. Instead of a single shared password, you create distinct users with specific command and key access. The authentication format changes from AUTH <password> to AUTH <username> <password> (backward-compatible with the old form for the default user).

ACLs control three dimensions:

  • Commands: Which commands the user can execute (+GET, -FLUSHALL, +@read, -@dangerous)
  • Keys: Which key patterns the user can access (~bull:*, ~cache:*)
  • Pub/Sub channels (Redis 6.2+): Which channels the user can subscribe to

The Default User

user default on nopass ~* &* +@all

By default, the default user has full access to all commands and keys with no password. When you set requirepass in redis.conf, it actually sets the password for this default user internally.

Creating ACL Users

You can create ACL users via the redis-cli using the ACL SETUSER command:

> ACL SETUSER bullmq-worker on >worker-p@ss ~bull:* +@read +@write -@dangerous +info +eval +evalsha +script +client
> ACL SETUSER bullmq-admin on >admin-p@ss ~* +@all -@dangerous

These commands create two users:

  • bullmq-worker: Restricted to BullMQ key namespace, read/write operations, no dangerous commands
  • bullmq-admin: Full access minus dangerous commands — suitable for dashboards and monitoring

ACL Command Categories Relevant to BullMQ

Category Commands Needed by BullMQ?
+@read GET, HGET, ZSCAN, LRANGE, etc. ✅ Queue/job reads
+@write SET, HSET, LPUSH, ZADD, etc. ✅ Queue/job writes
+@admin CONFIG, SAVE, SHUTDOWN, ACL, etc. ❌ Not needed
-@dangerous FLUSHALL, FLUSHDB, DEBUG, KEYS, CLIENT, etc. ⚠️ Block except CLIENT LIST + INFO
+@fast O(1) commands Used internally
+@slow Non-O(1) commands Used internally

Minimal ACL for BullMQ

Based on community testing and BullMQ GitHub issue #2809, here is the minimal ACL rule set for a BullMQ worker:

~bull:* +@read +@write -@dangerous +info +eval +evalsha +script +client|list

Breaking down each component:

  • ~bull:* — Restrict to the BullMQ key namespace only. BullMQ stores all its keys under the bull: prefix by default, so this scopes the user to queue data exclusively.
  • +@read — All read commands (GET, HGETALL, ZRANGE, LRANGE, etc.). BullMQ reads job data, queue metadata, and worker lists.
  • +@write — All write commands (SET, HSET, ZADD, LPUSH, etc.). BullMQ adds jobs, updates job states, and writes worker heartbeats.
  • -@dangerous — Block FLUSHALL, FLUSHDB, CONFIG, DEBUG, SHUTDOWN, and other destructive operations.
  • +info — The INFO command, required by BullMQ's connection health checks and dashboard monitoring.
  • +eval +evalsha +script — Lua scripting support. BullMQ uses Lua scripts (via EVAL) for atomic queue operations like moving jobs between states.
  • +client|list — Required for getWorkers() / getWorkersCount() calls.

For a dashboard or admin tool like Queue Hub, you might want a slightly broader rule set:

~bull:* +@all -@dangerous +info +client|list

Connecting BullMQ with ACL Credentials

import IORedis from 'ioredis';
import { Queue, Worker } from 'bullmq';

// ACL auth via URL (username in the user part of the URL)
const connection = new IORedis('redis://bullmq-worker:worker-p@ss@redis.example.com:6379');

// Or via options object
const connWithOpts = new IORedis({
  host: 'redis.example.com',
  port: 6379,
  username: 'bullmq-worker',
  password: 'worker-p@ss',
  maxRetriesPerRequest: null,  // Required for Workers
});

const queue = new Queue('email-notifications', { connection: connWithOpts });
const worker = new Worker('email-notifications', async job => {
  // Process job...
}, { connection: connWithOpts });

requirepass vs ACL — Migration Strategy

If both requirepass and an ACL password for the default user are set, requirepass takes precedence. This can cause confusing auth failures during migration.

Recommendation: On Redis 6+, use ACL exclusively. Remove requirepass from redis.conf.

Migration path:

  1. Start by creating ACL users alongside your existing requirepass — the default user still works as before
  2. Update your clients one by one to use AUTH <username> <password> (or the corresponding URL/options format in ioredis)
  3. Once all clients are migrated, remove requirepass from redis.conf
  4. Optionally disable the default user with user default off — but only after verifying all ACL users work correctly

Layer 3 — TLS/SSL Encryption

Why TLS for Redis

Password authentication alone is insufficient when network traffic can be intercepted. Without TLS:

  • The AUTH command (and the password) travels in cleartext over the network
  • All job payload data travels in plaintext across the wire
  • Anyone with access to the network (same subnet, compromised switch, malicious cloud tenant) can read everything

TLS is also a compliance requirement for many regulated environments. HIPAA, SOC2, PCI-DSS, and GDPR all require encryption in transit for sensitive data.

TLS is especially critical for cloud-managed Redis services — AWS ElastiCache, Upstash, Redis Cloud, Aiven — where traffic crosses network boundaries you don't fully control.

Enabling TLS on the Redis Server

Redis 6+ has built-in TLS support (requires compiling with make BUILD_TLS=yes). Configure it in redis.conf:

port 0                          # Disable plain TCP entirely
tls-port 6379                   # TLS on standard port
tls-cert-file /etc/redis/redis.crt
tls-key-file /etc/redis/redis.key
tls-ca-cert-file /etc/redis/ca.crt
tls-auth-clients yes            # Enable mutual TLS (optional but recommended)

This configuration disables plain TCP and serves only TLS-encrypted connections on port 6379.

TLS via Stunnel (Legacy Approach)

For Redis versions before 6, or environments where you cannot modify the Redis server configuration, stunnel provides a TLS proxy:

# stunnel.conf
[redis]
client = yes
accept = 127.0.0.1:6380
connect = redis.example.com:6379

Clients connect to 127.0.0.1:6380 in plaintext; stunnel handles the TLS encryption to the remote Redis server. This is a practical stopgap but adds operational complexity.

TLS with Managed Providers

Provider TLS Mechanism Port Notes
AWS ElastiCache In-transit encryption (TLS) 6379 Server certs managed by AWS; no client cert needed
Upstash Always-on TLS 6380 URL scheme rediss://
Redis Cloud TLS enabled on demand 6380 Can require client cert
Valkey (self-hosted) Built-in TLS 6380 Same as Redis 6+ TLS config
Aiven Always-on TLS 6380 CA cert available in console

BullMQ Connection with TLS

ioredis (which BullMQ uses internally) supports the rediss:// URL scheme out of the box. Passing an empty tls: {} object enables TLS using the system's default certificate authority bundle.

import IORedis from 'ioredis';
import { Queue, Worker } from 'bullmq';

// rediss:// URL scheme (auto-enables TLS in ioredis)
const queue = new Queue('my-queue', {
  connection: {
    url: 'rediss://bullmq-worker:worker-p@ss@redis.example.com:6380',
  },
});

// TLS with empty tls object (trusts default CAs — works for AWS ElastiCache)
const conn = new IORedis({
  host: 'my-cluster.cache.amazonaws.com',
  port: 6379,
  password: 'my-password',
  tls: {},                              // Enables TLS, uses system CA bundle
  maxRetriesPerRequest: null,
});

// TLS with custom certificates (mutual TLS)
import fs from 'fs';

const connWithCerts = new IORedis({
  host: 'redis.example.com',
  port: 6380,
  tls: {
    ca: fs.readFileSync('/path/to/ca.crt'),
    cert: fs.readFileSync('/path/to/client.crt'),  // Optional: mutual TLS
    key: fs.readFileSync('/path/to/client.key'),   // Optional: mutual TLS
    rejectUnauthorized: true,                       // Enforce certificate validation
  },
  maxRetriesPerRequest: null,
});

// TLS with self-signed CA (development only)
const connDev = new IORedis({
  host: 'redis.dev.local',
  port: 6380,
  tls: {
    rejectUnauthorized: false,  // ⚠️ Only for development — never in production
  },
});

Queue Hub TLS Handling

Queue Hub auto-detects the rediss:// URL prefix and configures TLS accordingly. It supports all standard Redis URL formats:

  • redis://:password@host:6379 — Plain TCP with password auth
  • redis://user:password@host:6379 — ACL authentication
  • rediss://user:password@host:6380 — TLS with ACL credentials
  • rediss://host:6379 — TLS without auth (ElastiCache default)

The implementation detail from the Queue Hub codebase:

this.connection = new Redis(this.redisUrl, {
  maxRetriesPerRequest: null,
  enableReadyCheck: true,
  lazyConnect: true,
  retryStrategy: () => null,
  ...(this.redisUrl.startsWith("rediss://") && {
    tls: { rejectUnauthorized: false },
  }),
});

Putting It All Together — Complete Secure BullMQ Setup

Here's a complete end-to-end configuration combining all three security layers.

Redis Server Configuration (redis.conf)

bind 127.0.0.1                     # Or private subnet only
port 0                             # Disable plain TCP
tls-port 6379                      # TLS-only on standard port
tls-cert-file /etc/redis/redis.crt
tls-key-file /etc/redis/redis.key
tls-ca-cert-file /etc/redis/ca.crt
maxmemory-policy noeviction        # Critical for BullMQ — prevents key eviction
aclfile /etc/redis/users.acl       # Store ACL definitions in external file

ACL File (users.acl)

user default off                   # Disable default user entirely
user bullmq-producer on >prod-p@ss ~bull:* +@read +@write -@dangerous +eval +evalsha +script
user bullmq-worker on >work-p@ss ~bull:* +@read +@write -@dangerous +eval +evalsha +script +client|list
user bullmq-admin on >admin-p@ss ~bull:* +@all -@dangerous +client|list +info

This creates three separate users following the principle of least privilege:

  • producer: Can add jobs and read queue state, no client management
  • worker: Same as producer plus CLIENT LIST for worker discovery
  • admin: Broader access for dashboards and monitoring tools like Queue Hub

BullMQ Application Code

Producer (Queue):

import IORedis from 'ioredis';
import { Queue } from 'bullmq';

const connection = new IORedis({
  host: 'redis.internal.example.com',
  port: 6379,
  username: 'bullmq-producer',
  password: 'prod-p@ss',
  tls: {},                                // Enable TLS
  maxRetriesPerRequest: null,             // Required for shared connections
});

const queue = new Queue('email-notifications', { connection });

Worker:

import IORedis from 'ioredis';
import { Worker } from 'bullmq';

const connection = new IORedis({
  host: 'redis.internal.example.com',
  port: 6379,
  username: 'bullmq-worker',
  password: 'work-p@ss',
  tls: {},
  maxRetriesPerRequest: null,
  enableReadyCheck: false,
});

const worker = new Worker('email-notifications', async job => {
  // Process job...
}, { connection });

Queue Hub Configuration

Configure Queue Hub via the REDIS_URL environment variable or the Settings UI:

  • AWS ElastiCache: rediss://mycluster.cache.amazonaws.com:6379
  • Upstash/Valkey: rediss://default:token@us1-crazy-redis.upstash.io:6380
  • Self-hosted with ACL: redis://bullmq-admin:admin-p@ss@redis.example.com:6379
  • Self-hosted with TLS and ACL: rediss://bullmq-admin:admin-p@ss@redis.example.com:6380

Agent Tunneling for Private Redis

The Private Redis Problem

Many production Redis instances run in private VPCs and are not publicly accessible. Opening firewall ports for monitoring tools introduces additional attack surface.

Queue Hub solves this with agent tunneling — a WebSocket-based reverse tunnel. A lightweight agent runs inside your network, establishes an outbound WebSocket connection to Queue Hub, and proxied Redis commands flow through the tunnel. No inbound firewall rules needed.

How It Works

  1. Generate an agent token in Queue Hub
  2. Run the agent binary inside your VPC or network
  3. The agent opens a WebSocket connection back to Queue Hub
  4. Studio routes Redis commands through the tunnel
  5. The agent connects to your local Redis and returns results

Security Considerations

  • The agent token must be kept secret — treat it like an API key
  • TLS/WSS encrypts the tunnel itself (defense in depth)
  • The agent only opens outbound connections — no inbound firewall rules required
  • The agent can connect to Redis over TLS within your trusted network, so all three security layers (ACLs, TLS, and network isolation) are preserved

Common Pitfalls & Best Practices

Pitfall: Mixed requirepass and ACL

Setting both requirepass and ACL entries for the default user can cause confusing authentication failures. requirepass sets the default user's password, and ACL entries may conflict.

Fix: Use only ACL. Remove requirepass from redis.conf.

Pitfall: Missing CLIENT LIST Permission

queue.getWorkers() and getWorkersCount() can fail silently or return NOPERM errors.

Fix: Add +client|list to the ACL rule for any client that needs worker discovery — typically dashboards and monitoring tools.

Pitfall: TLS Without Certificate Validation

Setting tls: { rejectUnauthorized: false } skips certificate chain validation. While acceptable for AWS ElastiCache (where AWS manages the trust chain) and development environments, production deployments should always validate certificates.

Fix: Provide ca, cert, and key options in the tls configuration for production.

Pitfall: Worker with maxRetriesPerRequest != null

BullMQ Workers require maxRetriesPerRequest: null. Without this setting, BullMQ throws an error or the worker silently fails to process jobs.

Fix: Always set maxRetriesPerRequest: null on connection objects passed to BullMQ Workers.

Pitfall: Disabling Default User Too Early

If you run user default off without first creating alternative ACL users and verifying connectivity, all clients immediately lose access.

Fix: Create ACL users first, verify every client can connect with its new credentials, then disable the default user.

Pitfall: Redis Key Eviction

With default maxmemory-policy values like allkeys-lru, Redis can evict BullMQ job keys under memory pressure, causing data loss and unpredictable queue behavior.

Fix: Always set maxmemory-policy noeviction for BullMQ Redis instances. Monitor memory usage and scale your Redis instance as needed.

Best Practice Checklist

  • Bind Redis to loopback or private subnet only
  • Enable TLS on Redis server or use managed Redis with always-on TLS
  • Use ACLs instead of requirepass (Redis 6+)
  • Create separate ACL users for producer, worker, and admin/dashboard
  • Set maxmemory-policy noeviction
  • Use rediss:// URL scheme in BullMQ connections
  • Set maxRetriesPerRequest: null on worker connections
  • Encrypt sensitive job data before adding to queue
  • Add error event handlers on queue and worker connections
  • Rotate Redis passwords and ACL passwords regularly
  • For private Redis, use Queue Hub's agent tunneling instead of opening firewalls

Conclusion

Redis security is not optional for production BullMQ deployments. The default configuration with no authentication and full command access is a liability that can compromise your entire queue system.

The three layers — passwords, ACLs, and TLS — work together to provide genuine defense in depth:

  • Passwords (requirepass) provide basic authentication for legacy setups
  • ACLs give you the granularity to implement least-privilege access for job producers, workers, and dashboards
  • TLS protects credentials and job data in transit, keeping your queue operations private on the network

BullMQ and Queue Hub support all three layers out of the box. With a few configuration changes — creating ACL users, enabling TLS, and updating your connection settings — you can transform a vulnerable Redis deployment into a hardened, production-ready queue infrastructure.

Next Steps

  1. Review your current Redis configuration against the best-practice checklist above
  2. Try the ACL configurations in this guide with your BullMQ setup
  3. Set up Queue Hub with a TLS-secured Redis connection to visualize and manage your queues
  4. For teams running Redis on private networks, evaluate Queue Hub's agent tunneling for secure remote access without firewall changes

Ready to see your BullMQ queues in action? Try Queue Hub — the open-source dashboard for monitoring, managing, and debugging BullMQ queues with full support for TLS, ACLs, and agent tunneling for private Redis instances.

Related Articles