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:
- Client A opens a connection and sends
SUBSCRIBE alerts:system. Redis adds Client A’s connection to the subscriber list for the channel namealerts:systemin 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. - 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. - Redis looks up
alerts:systemin the channel table and finds Client A listed as a subscriber. It writes amessagearray containing the channel name and payload directly into Client A’s output buffer. - Redis separately walks its list of active patterns (empty in this example), checking for glob matches against
alerts:system; finding none, nopmessagedeliveries happen. - The
PUBLISHcommand returns to Client B with the integer1— the total number of clients it just delivered to. - Redis flushes Client A’s output buffer over the network. Client A’s
redis-cli, still blocked reading from that connection, prints the incomingmessagearray 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) soPSUBSCRIBEpatterns likechat:room:*stay predictable. - Check
PUBSUB NUMSUBorPUBSUB CHANNELSwhen 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
- Open two
redis-cliterminals. In the first, runSUBSCRIBE chat:room:1. In the second, publish two or three different text messages tochat:room:1and watch them appear in the first terminal in the order you sent them. - Using the same two terminals, unsubscribe the first terminal’s connection, then subscribe it to the pattern
chat:room:*instead withPSUBSCRIBE. Publish tochat:room:1andchat:room:2from the second terminal and confirm both arrive aspmessagereplies. Then, from a third terminal, runPUBSUB NUMPATto confirm exactly one pattern is active. - From a single terminal, run
PUBLISH inventory:updates "sku 555 restocked"before subscribing anyone, and note the reply. Then subscribe a second terminal toinventory:updatesand publish the same message again — compare the two replies and explain, in your own words, why they differ.
Summary
PUBLISH channel messagesends a message to every subscriber ofchanneland returns the number of clients that received it;0means nobody was listening and the message is lost.SUBSCRIBElistens for exact channel names;PSUBSCRIBElistens for glob-style patterns and delivers matches aspmessageinstead ofmessage.- 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, orRESET— always use separate connections for subscribing and publishing. - Use
PUBSUB CHANNELS,PUBSUB NUMSUB, andPUBSUB NUMPATto 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.
