SUBSCRIBE and PUBLISH

Redis pub/sub (publish/subscribe) is a messaging pattern that lets one client broadcast a message on a named channel while any number of other clients listen for it in real time. The two core commands are PUBLISH, which sends a message to a channel, and SUBSCRIBE, which tells Redis to deliver every future message on that channel to the calling connection. There is no queue, no storage, and no history involved — if nobody is listening at the moment a message is published, it is gone forever. This makes pub/sub excellent for real-time fan-out such as chat rooms, live dashboards, and cache-invalidation broadcasts, but a poor fit for anything that needs guaranteed or delayed delivery.

Overview: How Pub/Sub Works

Redis keeps pub/sub state entirely in server memory, separate from the keyspace. Internally the server maintains a table that maps each channel name to the list of client connections subscribed to it via SUBSCRIBE, plus a second list of pattern/client pairs registered via PSUBSCRIBE. Neither structure is a Redis key: you cannot find a channel with KEYS or inspect it with TYPE. A subscription exists only as long as the client’s connection stays open — disconnect, and the subscription is gone.

When a client runs PUBLISH channel message, Redis performs the following, all inside one atomic step because Redis is single-threaded and never interleaves commands: it looks up the exact channel name in the channel table and copies the message into the output buffer of every exact subscriber; then it walks the list of registered patterns, tests each one against the channel name using glob-style matching, and delivers the message (wrapped as a pmessage instead of a message) to every client whose pattern matches. The command’s integer reply is the total number of clients that received the message — exact subscribers plus pattern matches combined. A reply of 0 means the message had no audience and was discarded; Redis does not queue it for later.

Because delivery happens synchronously as part of the single-threaded command loop, message ordering is guaranteed per channel: two messages published to the same channel arrive at every subscriber in the order they were published. There is no acknowledgment step and no persistence — pub/sub traffic is never written to the RDB snapshot or the AOF log, so a server restart, and even a subscriber that was briefly disconnected, permanently loses any messages sent during the gap. This is why pub/sub is described as “at-most-once, fire-and-forget” delivery.

Once a connection issues SUBSCRIBE or PSUBSCRIBE, it enters subscribe mode. In the classic RESP2 protocol, a connection in subscribe mode can only run SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PING, QUIT, or RESET — regular commands like GET or PUBLISH are rejected on that same connection until it unsubscribes from everything. This is why real applications always use a dedicated connection for subscribing and a separate connection for publishing or running normal commands.

In Redis Cluster, a plain PUBLISH is forwarded to every node in the cluster so a message reaches its subscribers no matter which node they connected to. Redis 7 added sharded pub/sub (SPUBLISH/SSUBSCRIBE) to avoid that cluster-wide broadcast for high-throughput cases by confining delivery to the shard that owns the channel’s hash slot — an advanced tool worth knowing exists, though standard PUBLISH/SUBSCRIBE is what most applications need. Keep in mind Redis pub/sub is not a substitute for a durable message queue; if you need delivery guarantees, replay, or consumer groups, that is what the Streams type (XADD/XREAD) is for.

Syntax

SUBSCRIBE channel [channel ...]
PSUBSCRIBE pattern [pattern ...]
PUBLISH channel message
UNSUBSCRIBE [channel ...]
PUNSUBSCRIBE [pattern ...]
PUBSUB CHANNELS [pattern]
PUBSUB NUMSUB [channel ...]
PUBSUB NUMPAT
Command Description Time Complexity
SUBSCRIBE Subscribes the connection to one or more exact channel names and enters subscribe mode. O(N) for N given channels
PSUBSCRIBE Subscribes to one or more glob-style patterns (e.g. orders:*); the client receives messages from any channel whose name matches. O(N) for N given patterns
PUBLISH Sends a message to every exact subscriber of the channel and every client whose pattern matches it. Returns the number of receivers. O(N+M): N subscribers to the channel, M total registered patterns
UNSUBSCRIBE Removes exact-channel subscriptions for the given channels, or all of them if none are named. O(N)
PUNSUBSCRIBE Removes pattern subscriptions for the given patterns, or all of them if none are named. O(N+M)
PUBSUB CHANNELS Lists currently active channels (those with at least one subscriber), optionally filtered by a glob pattern. O(N) active channels
PUBSUB NUMSUB Returns the subscriber count for each named channel. O(N) requested channels
PUBSUB NUMPAT Returns the number of distinct patterns currently subscribed to, across all clients. O(N) patterns

Examples

Example 1: Publishing with no one listening

Every example below uses a namespaced channel name, the same convention used for key names. First, see what happens when you publish to a channel that has zero subscribers:

PUBLISH alerts:system "disk usage above 90%"

Output:

(integer) 0

The reply is the count of clients that received the message. Since no other connection is subscribed to alerts:system yet, the count is 0 and the message is discarded immediately — Redis does not hold on to it for a subscriber that connects a moment later.

Example 2: Subscribing and receiving a message

Pub/sub needs two separate connections to observe: one that subscribes and blocks waiting for messages, and another that publishes. Open two redis-cli sessions side by side. In the first terminal, subscribe:

# Terminal A (subscriber)
SUBSCRIBE alerts:system

Output:

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "alerts:system"
3) (integer) 1

The connection is now blocked, waiting in subscribe mode. In the second terminal, publish to the same channel:

# Terminal B (publisher)
PUBLISH alerts:system "disk usage above 90%"

Output:

(integer) 1

Terminal B immediately gets back (integer) 1, confirming one client received it. Back in Terminal A, without you typing anything, Redis pushes the message straight through:

1) "message"
2) "alerts:system"
3) "disk usage above 90%"

That three-element array is the shape of every message push: the literal string "message", the channel it arrived on, and the payload.

Example 3: Pattern subscriptions with PSUBSCRIBE

Use PSUBSCRIBE to listen to a whole family of channels at once. In Terminal A:

# Terminal A (pattern subscriber)
PSUBSCRIBE orders:*

Output:

Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "orders:*"
3) (integer) 1

In Terminal B, publish to two different channels that both match the pattern:

# Terminal B (publisher)
PUBLISH orders:new "order 1042 created"
PUBLISH orders:cancelled "order 1040 cancelled"

Output:

(integer) 1
(integer) 1

Terminal A, which never subscribed to orders:new or orders:cancelled directly, receives both because they match the orders:* pattern:

1) "pmessage"
2) "orders:*"
3) "orders:new"
4) "order 1042 created"
1) "pmessage"
2) "orders:*"
3) "orders:cancelled"
4) "order 1040 cancelled"

Notice the reply has four elements instead of three — pattern messages include both the pattern that matched and the specific channel the message was published to.

Example 4: Inspecting pub/sub state with PUBSUB

PUBSUB lets you introspect subscriptions from any ordinary connection, without entering subscribe mode yourself:

PUBSUB CHANNELS
PUBSUB CHANNELS orders:*
PUBSUB NUMSUB orders:new
PUBSUB NUMPAT

Output:

(empty array)
(empty array)
1) "orders:new"
2) (integer) 0
(integer) 0

Run from a fresh connection with no other subscribers active, PUBSUB CHANNELS reports an empty list and NUMSUB/NUMPAT report zero. If you ran the same three commands while the Example 2 and Example 3 subscribers above were still connected, PUBSUB CHANNELS would instead list alerts:system and, once something publishes to them, orders:new/orders:cancelled; NUMSUB orders:new would show 1; and NUMPAT would show 1 for the active orders:* pattern. PUBSUB is the safe way to check “is anyone even listening?” before you rely on a publish reaching someone.

How It Works, Step by Step

Matching Example 2 above, here is exactly what Redis does internally:

  1. Client A opens a connection and sends SUBSCRIBE alerts:system. Redis adds Client A’s connection to the subscriber list for the channel name alerts:system in its in-memory channel table, then immediately sends back the subscribe confirmation array. Client A’s connection is now flagged as being in subscribe mode.
  2. Client B, on a separate connection, sends PUBLISH alerts:system "disk usage above 90%". Because this runs on the single command-processing thread, no other command can interleave with it.
  3. Redis looks up alerts:system in the channel table and finds Client A listed as a subscriber. It writes a message array containing the channel name and payload directly into Client A’s output buffer.
  4. Redis separately walks its list of active patterns (empty in this example), checking for glob matches against alerts:system; finding none, no pmessage deliveries happen.
  5. The PUBLISH command returns to Client B with the integer 1 — the total number of clients it just delivered to.
  6. Redis flushes Client A’s output buffer over the network. Client A’s redis-cli, still blocked reading from that connection, prints the incoming message array the instant it arrives — no polling involved.

Common Mistakes

Mistake 1: Assuming a published message waits for a subscriber

A common misreading of pub/sub is expecting it to behave like a queue that holds messages until someone connects. It does not:

PUBLISH orders:new "order 9001 created"

Output:

(integer) 0

An integer reply of 0 means the message was delivered to nobody and is now gone. If a consumer needs to see every message even after being briefly offline, PUBLISH/SUBSCRIBE is the wrong tool — use the Streams type (XADD to write, XREAD or consumer groups to read) which persists entries so consumers can catch up.

Mistake 2: Running normal commands on a subscribed connection

Once a connection calls SUBSCRIBE, that same connection cannot be reused for ordinary commands until it unsubscribes from everything:

SUBSCRIBE updates:feed
PUBLISH updates:feed "hello"

Output:

1) "subscribe"
2) "updates:feed"
3) (integer) 1
(error) ERR Can't execute 'publish': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context

The fix is architectural, not syntactic: keep one connection dedicated to subscribing, and use a different connection (or a different client entirely) to publish or run regular commands.

Mistake 3: Using UNSUBSCRIBE to clear a pattern subscription

SUBSCRIBE/UNSUBSCRIBE and PSUBSCRIBE/PUNSUBSCRIBE track two separate lists. Calling UNSUBSCRIBE with no arguments only removes exact-channel subscriptions — a pattern registered with PSUBSCRIBE orders:* stays active until you explicitly call PUNSUBSCRIBE for it. Forgetting this leaves a connection quietly consuming messages from a pattern you thought you had cleared.

Mistake 4: Treating channels like keys

Channels are not keys, so commands like EXPIRE, TTL, or KEYS do not apply to them, and a channel “exists” only in the sense that at least one client is currently subscribed to it. There is nothing to clean up or expire — the moment the last subscriber disconnects or unsubscribes, the channel disappears from PUBSUB CHANNELS on its own.

Best Practices

  • Use pub/sub for ephemeral, real-time fan-out only — live notifications, presence updates, cache-invalidation signals — never for anything where a missed message is unacceptable.
  • If you need durability, replay, or multiple independent consumer groups reading the same feed, use Streams (XADD/XREADGROUP) instead of pub/sub.
  • Dedicate a separate connection to every subscriber; never share a connection between subscribing and issuing regular read or write commands.
  • Namespace channel names the same way you namespace keys (chat:room:42, orders:new) so PSUBSCRIBE patterns like chat:room:* stay predictable.
  • Check PUBSUB NUMSUB or PUBSUB CHANNELS when you need to know whether anyone is actually listening before treating a publish as “handled.”
  • In a clustered deployment with very high pub/sub volume, evaluate sharded pub/sub (SPUBLISH/SSUBSCRIBE) so messages are not broadcast to every node unnecessarily.
  • Keep messages small and self-describing, such as a compact JSON string, since there is no schema enforcement — the subscriber has to parse whatever bytes arrive.

Practice Exercises

  1. Open two redis-cli terminals. In the first, run SUBSCRIBE chat:room:1. In the second, publish two or three different text messages to chat:room:1 and watch them appear in the first terminal in the order you sent them.
  2. Using the same two terminals, unsubscribe the first terminal’s connection, then subscribe it to the pattern chat:room:* instead with PSUBSCRIBE. Publish to chat:room:1 and chat:room:2 from the second terminal and confirm both arrive as pmessage replies. Then, from a third terminal, run PUBSUB NUMPAT to confirm exactly one pattern is active.
  3. From a single terminal, run PUBLISH inventory:updates "sku 555 restocked" before subscribing anyone, and note the reply. Then subscribe a second terminal to inventory:updates and publish the same message again — compare the two replies and explain, in your own words, why they differ.

Summary

  • PUBLISH channel message sends a message to every subscriber of channel and returns the number of clients that received it; 0 means nobody was listening and the message is lost.
  • SUBSCRIBE listens for exact channel names; PSUBSCRIBE listens for glob-style patterns and delivers matches as pmessage instead of message.
  • Pub/sub state lives only in server memory tied to open connections — it is never written to a key, an RDB snapshot, or the AOF log, and disappears the instant a client disconnects.
  • A connection in subscribe mode can only run subscribe-related commands, PING, QUIT, or RESET — always use separate connections for subscribing and publishing.
  • Use PUBSUB CHANNELS, PUBSUB NUMSUB, and PUBSUB NUMPAT to introspect what is currently subscribed without entering subscribe mode yourself.
  • Pub/sub is fire-and-forget with no persistence or acknowledgment — reach for Streams instead when you need durable, replayable delivery.