Redis Pub/Sub lets applications send messages to named channels and have Redis deliver them instantly to every client subscribed to those channels. It is a good fit for live notifications, cache invalidation and chat-style updates in applications that already use Redis. In this tutorial you will install Redis on Ubuntu 24.04, practice the Pub/Sub commands, create ACL users that can only publish or only subscribe to specific channels, and build a small Python notification listener. You will also see where Pub/Sub stops being the right tool and Redis Streams should be used instead.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • Basic familiarity with redis-cli is helpful but not required.

Step 1 - Installing Redis

Install the Redis server and command line tools from the Ubuntu repositories:

sudo apt update
sudo apt install -y redis-server

The package enables and starts the redis-server service, listening only on 127.0.0.1 and ::1. Check that it responds:

redis-cli ping
PONG

Step 2 - Publishing and subscribing

Pub/Sub uses three commands: SUBSCRIBE to listen on one or more channels, PUBLISH to send a message to a channel, and PSUBSCRIBE to listen on every channel that matches a glob pattern. Open a first terminal and subscribe to the notifications channel:

redis-cli SUBSCRIBE notifications
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "notifications"
3) (integer) 1

The connection is now in subscriber mode and waits for messages. In a second terminal, publish one:

redis-cli PUBLISH notifications "Maintenance at 02:00 UTC"
(integer) 1

The return value is the number of clients that received the message. The first terminal shows it:

1) "message"
2) "notifications"
3) "Maintenance at 02:00 UTC"

Now publish to a channel nobody listens to:

redis-cli PUBLISH alerts "Disk almost full"
(integer) 0

Nobody received it, and Redis did not store it. This is the most important property of Pub/Sub: messages are fire and forget. A subscriber that is offline, or connects one second later, never sees them.

Pattern subscriptions

Naming channels hierarchically, for example user:42:notifications, lets a service subscribe to a whole family of channels with one pattern. Stop the first subscriber with CTRL+C and run:

redis-cli PSUBSCRIBE 'user:*:notifications'

Publish to two different users from the second terminal:

redis-cli PUBLISH user:42:notifications "Your invoice is ready"
redis-cli PUBLISH user:7:notifications "New login from Madrid"

Pattern messages include the pattern that matched, followed by the real channel:

1) "pmessage"
2) "user:*:notifications"
3) "user:42:notifications"
4) "Your invoice is ready"

Inspecting active channels

The PUBSUB command shows what is being listened to. With the pattern subscriber still running:

redis-cli PUBSUB NUMPAT
redis-cli PUBSUB CHANNELS '*'
redis-cli PUBSUB NUMSUB notifications

NUMPAT counts pattern subscriptions, CHANNELS lists channels with at least one direct subscriber, and NUMSUB returns the number of subscribers per channel. These commands are useful to confirm that your consumers are actually connected before you blame the publisher.

Step 3 - Restricting channels with ACL users

By default, Ubuntu's Redis has a single default user with no password and access to everything. Any process that can reach the port can publish fake notifications or read every channel. Redis ACLs let you create users limited to specific commands and channel patterns (the &pattern rule).

Open the Redis configuration file:

sudo nano /etc/redis/redis.conf

Add these lines at the end of the file. Replace the three passwords with long random strings, for example generated with openssl rand -base64 32:

# Administrator: full access
user default on >your_admin_password ~* &* +@all

# Backend that publishes notifications
user notifier on >your_notifier_password resetchannels &user:*:notifications -@all +publish +ping

# Service that only listens for notifications
user listener on >your_listener_password resetchannels &user:*:notifications -@all +subscribe +psubscribe +unsubscribe +punsubscribe +ping

What each rule means:

  • on enables the user and >password adds a password.
  • ~* grants access to all keys. The two Pub/Sub users have no ~ rule, so they cannot read or write any key.
  • resetchannels &user:*:notifications removes any channel access and then allows only channels matching user:*:notifications.
  • -@all +publish ... starts from no commands and adds only the ones listed. ping is allowed so clients and health checks can test the connection.

Restart Redis to apply the configuration:

sudo systemctl restart redis-server

Unauthenticated commands are now rejected:

redis-cli ping
(error) NOAUTH Authentication required.

Test the listener user. --askpass prompts for the password instead of leaving it in your shell history:

redis-cli --user listener --askpass PSUBSCRIBE 'user:*:notifications'

From a second terminal, publish as notifier:

redis-cli --user notifier --askpass PUBLISH user:42:notifications "Payment received"
(integer) 1

Now confirm the restrictions. The notifier cannot publish outside its channel pattern, and the listener cannot publish at all:

redis-cli --user notifier --askpass PUBLISH admin:broadcast "test"
redis-cli --user listener --askpass PUBLISH user:42:notifications "test"

Both commands fail with an error that starts with NOPERM. The exact wording depends on the Redis version:

(error) NOPERM this user has no permissions to access one of the channels used as arguments
(error) NOPERM this user has no permissions to run the 'publish' command

Step 4 - Building a notification listener in Python

In a real application, the listener is a long-running service that receives events and forwards them, for example to WebSocket clients. Install the redis Python client in a virtual environment:

sudo apt install -y python3-venv
python3 -m venv ~/pubsub-demo
~/pubsub-demo/bin/pip install redis

Create the listener:

nano ~/listener.py
import json
import os

import redis

r = redis.Redis(
    host="127.0.0.1",
    port=6379,
    username="listener",
    password=os.environ["REDIS_PASSWORD"],
    decode_responses=True,
)

pubsub = r.pubsub(ignore_subscribe_messages=True)
pubsub.psubscribe("user:*:notifications")
print("Listening on user:*:notifications")

for message in pubsub.listen():
    user_id = message["channel"].split(":")[1]
    try:
        payload = json.loads(message["data"])
    except json.JSONDecodeError:
        payload = {"text": message["data"]}
    print(f"user={user_id} payload={payload}")

ignore_subscribe_messages=True hides the subscription confirmations, so the loop only receives real messages. The password is read from an environment variable rather than being written in the code.

Create the publisher:

nano ~/notify.py
import json
import os
import sys

import redis

r = redis.Redis(
    host="127.0.0.1",
    username="notifier",
    password=os.environ["REDIS_PASSWORD"],
)

user_id, text = sys.argv[1], sys.argv[2]
receivers = r.publish(f"user:{user_id}:notifications", json.dumps({"text": text}))
print(f"Delivered to {receivers} subscriber(s)")

Start the listener in one terminal. read -s asks for the password without echoing it:

read -rs REDIS_PASSWORD && export REDIS_PASSWORD
~/pubsub-demo/bin/python ~/listener.py

In another terminal, export the notifier password the same way and send a notification:

read -rs REDIS_PASSWORD && export REDIS_PASSWORD
~/pubsub-demo/bin/python ~/notify.py 42 "Your server is ready"
Delivered to 1 subscriber(s)

The listener prints:

user=42 payload={'text': 'Your server is ready'}

Check the return value of publish in your application: 0 means no listener was connected and the event was dropped.

Step 5 - Understanding the limits of Pub/Sub

Pub/Sub keeps no history and has no acknowledgments. Keep these behaviors in mind:

  • Offline subscribers miss messages. If the listener restarts, everything published during the restart is lost.
  • Slow subscribers get disconnected. Redis buffers outgoing messages per subscriber. The default limit client-output-buffer-limit pubsub 32mb 8mb 60 closes a subscriber whose buffer exceeds 32 MB, or stays above 8 MB for 60 seconds, to protect the server's memory.
  • Every subscriber gets every message. There is no built-in way to split work between several workers.

When you need delivery guarantees or work sharing, use a Redis Stream instead. A stream stores messages as an append-only log, and consumer groups track which messages each worker has acknowledged. The same notification with a stream looks like this (run as the default user, since streams use keys):

redis-cli --askpass XADD notifications '*' user 42 text "Your server is ready"
redis-cli --askpass XGROUP CREATE notifications mailers 0
redis-cli --askpass XREADGROUP GROUP mailers worker-1 COUNT 10 STREAMS notifications '>'

The message stays in the stream until you trim it, a worker that was offline reads it when it comes back, and XACK marks it as processed.

Pub/SubStreams
Stores messagesNoYes, until trimmed
Offline consumersMiss messagesCatch up later
AcknowledgmentsNoYes (XACK)
Work sharingNo, all subscribers get everythingConsumer groups
Best forLive updates, cache invalidationJobs, events that must not be lost

Troubleshooting

NOAUTH Authentication required. The client did not authenticate. Pass --user and --askpass to redis-cli, or username and password to your client library.

NOPERM errors on PUBLISH or SUBSCRIBE. The command is not in the user's allowed list, or the channel name does not match the user's & pattern. Check with redis-cli --askpass ACL GETUSER notifier as the default user.

Redis does not start after editing redis.conf. Run sudo journalctl -u redis-server -n 30. A typo in a user line stops Redis from loading the configuration, and the log names the line.

The listener stops receiving after a while. It was probably disconnected for exceeding the output buffer limit. Check the Redis log (/var/log/redis/redis-server.log) for scheduled to be closed ASAP for overcoming of output buffer limits and make the listener process messages faster.

Conclusion

You installed Redis on Ubuntu 24.04, used PUBLISH, SUBSCRIBE and PSUBSCRIBE to send real-time messages, locked down publishers and subscribers to their own channel patterns with ACLs, and built a Python listener. As next steps, run the listener as a systemd service so it restarts automatically, push its messages to browsers through a WebSocket server, and move any event that must not be lost to Redis Streams with consumer groups.