Fix Redis 'ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context'

intermediate๐Ÿ”ด Redis2026-05-07| Redis 2.xโ€“7.x, any OS (Linux/macOS/Windows), any Redis client (redis-cli, redis-py, ioredis, Jedis)

Error Message

(error) ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT / RESET allowed in this context
#redis#pubsub#subscribe#publish#context

The Error

You run a Redis command โ€” SET, GET, HSET, PUBLISH, or anything else โ€” and get this back:

(error) ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT / RESET allowed in this context

That connection is stuck in Pub/Sub subscriber mode. Once a connection calls SUBSCRIBE or PSUBSCRIBE, Redis locks it down hard. It will only accept subscriber-related commands until it exits that mode. Everything else โ€” including PUBLISH โ€” triggers this error.

Why This Happens

Pub/Sub mode is connection-scoped. The moment a client sends SUBSCRIBE channel, that TCP connection enters a dedicated listening state. Redis blocks all other commands on purpose โ€” it needs to guarantee message delivery without interference from writes or reads on the same socket.

Common scenarios where this trips people up:

  • Reusing the same Redis client instance for both subscribing and publishing/writing
  • A connection pool that hands out an already-subscribed connection to regular commands
  • Calling PUBLISH on the subscriber connection โ€” a very common mistake, since PUBLISH is a write command, not a subscriber command
  • Forgetting to unsubscribe before reusing a connection

The Fix

Subscriber connections must be dedicated. Full stop. Use one connection exclusively for SUBSCRIBE/PSUBSCRIBE, and a completely separate connection for everything else โ€” including PUBLISH.

Fix in redis-cli

Open two terminals โ€” one per role:

# Terminal 1 โ€” subscriber only
redis-cli
127.0.0.1:6379> SUBSCRIBE news
Reading messages... (press Ctrl-C to quit)

# Terminal 2 โ€” publisher / regular commands
redis-cli
127.0.0.1:6379> PUBLISH news "hello"
(integer) 1
127.0.0.1:6379> SET foo bar
OK

To exit Pub/Sub mode on the subscriber side, press Ctrl-C, or send UNSUBSCRIBE. On Redis 6.2+, RESET also works.

Fix in Python (redis-py)

redis-py ships with a PubSub object that manages its own connection under the hood โ€” exactly so you don't accidentally mix them:

import redis
import threading

r = redis.Redis(host='localhost', port=6379)

# Subscriber โ€” dedicated pubsub object (its own connection)
def message_handler(message):
    print(f"Received: {message['data']}")

pubsub = r.pubsub()
pubsub.subscribe(**{'news': message_handler})

# Run subscriber in background thread
thread = pubsub.run_in_thread(sleep_time=0.01)

# Main connection โ€” use r for everything else
r.set('foo', 'bar')          # OK โ€” uses main connection
r.publish('news', 'hello')   # OK โ€” uses main connection, not pubsub

thread.stop()

Where people go wrong โ€” using a raw connection object directly:

# This is fine โ€” r and pubsub are separate objects
pubsub = r.pubsub()
pubsub.subscribe('news')
r.set('foo', 'bar')  # OK

# This breaks:
raw_conn = r.connection_pool.get_connection('_')
raw_conn.send_command('SUBSCRIBE', 'news')
raw_conn.send_command('SET', 'foo', 'bar')  # ERR only (P)SUBSCRIBE...

Fix in Node.js (ioredis)

Instantiate two separate clients โ€” one for subscribing, one for everything else:

const Redis = require('ioredis');

const subscriber = new Redis();
const publisher = new Redis();

subscriber.subscribe('news', (err, count) => {
  if (err) throw err;
  console.log(`Subscribed to ${count} channel(s)`);
});

subscriber.on('message', (channel, message) => {
  console.log(`[${channel}] ${message}`);
});

// publisher handles all writes and PUBLISH calls
publisher.publish('news', 'hello');
publisher.set('foo', 'bar');

Fix in Java (Jedis)

JedisPool pool = new JedisPool("localhost", 6379);

// Subscriber โ€” blocks the thread, needs a dedicated connection
new Thread(() -> {
    try (Jedis jedis = pool.getResource()) {
        jedis.subscribe(new JedisPubSub() {
            @Override
            public void onMessage(String channel, String message) {
                System.out.println(channel + ": " + message);
            }
        }, "news");
    }
}).start();

// Normal commands โ€” grab a separate connection from the pool
try (Jedis jedis = pool.getResource()) {
    jedis.publish("news", "hello");
    jedis.set("foo", "bar");
}

Resetting a stuck subscriber connection (Redis 6.2+)

Got a connection stuck in Pub/Sub mode that you want to reuse? RESET brings it straight back to normal โ€” no need to close and reopen the socket:

127.0.0.1:6379> SUBSCRIBE news
...
127.0.0.1:6379> RESET
+RESET

On Redis versions before 6.2, UNSUBSCRIBE from every channel first, or just close the connection and open a fresh one.

Verify the Fix

  • Run your subscriber logic โ€” confirm it receives messages cleanly.
  • On the separate connection, run a write: SET test ok should return OK.
  • Publish from that same connection: PUBLISH news "ping" should return an integer (the subscriber count), not an error.
  • Check that your subscriber actually receives the message โ€” if it does, both connections are healthy.
# Quick sanity check across two terminals
# Terminal 1
redis-cli SUBSCRIBE test-channel

# Terminal 2
redis-cli PUBLISH test-channel "works"
# Expected: (integer) 1
# Terminal 1 should print:
# 1) "message"
# 2) "test-channel"
# 3) "works"

Quick Reference

Commands allowed on a subscriber connection:

  • SUBSCRIBE / UNSUBSCRIBE โ€” manage channel subscriptions
  • PSUBSCRIBE / PUNSUBSCRIBE โ€” manage pattern subscriptions
  • PING โ€” keepalive check (returns a special Pub/Sub pong, not the usual +PONG)
  • QUIT โ€” close the connection
  • RESET โ€” exit Pub/Sub mode (Redis 6.2+)

Everything else โ€” GET, SET, PUBLISH, HSET, EXPIRE, all of it โ€” must go through a non-subscriber connection.

Related Error Notes