Skip to main content
Redis · lesson 9 of 14
Redis intermediate Lesson 9 of 14

Also part of: Security Fundamentals

ACL: a User Restricted to One Key Pattern and One Command

A default Redis install lets every connection run every command against every key. ACL SETUSER changes that — including a real NOPERM the moment a restricted user reaches outside its allowed keys or commands.

By SkillByExample · Published

redis acl security authentication

By default, any client that can reach Redis at all can run any command against any key. ACL is what turns that into something scoped.

A user restricted to one command and one key pattern

docker exec redis-lesson redis-cli ACL SETUSER alice on ">p1pp0" "~cached:*" +get
docker exec redis-lesson redis-cli ACL LIST
OK
user alice on sanitize-payload #2d9c75273d72b32df726fb545c8a4edc719f0a95a6fd993950b10c474ad9c927 ~cached:* resetchannels -@all +get
user default on nopass sanitize-payload ~* &* +@all

alice can authenticate (on, with a password whose SHA-256 hash is shown, never the plaintext), can only touch keys matching cached:*, and can only run get — everything else starts from -@all (deny everything) with get added back explicitly.

The bug — three ways NOPERM shows up

docker exec redis-lesson redis-cli SET cached:1234 "zap"
# authenticated as alice, then:
GET foo
NOPERM No permissions to access a key

foo doesn’t match ~cached:* — rejected before Redis even checks whether get itself was allowed.

# authenticated as alice, then:
GET cached:1234
zap

cached:1234 matches the key pattern and get is explicitly granted — this one succeeds.

# authenticated as alice, then:
SET cached:1234 zap2
NOPERM User alice has no permissions to run the 'set' command

Same key, different command — alice was only ever granted get, so set is refused even on a key she can otherwise read.

Selectors: a second, independent rule set

docker exec redis-lesson redis-cli ACL SETUSER alice "(+set ~logs:*)"
docker exec redis-lesson redis-cli ACL LIST
OK
user alice on sanitize-payload #2d9c75273d72b32df726fb545c8a4edc719f0a95a6fd993950b10c474ad9c927 ~cached:* resetchannels -@all +get (~logs:* resetchannels -@all +set)

The part in parentheses is a selector — a completely separate rule set. alice now has two independent permission sets: root (get on cached:*) and selector (set on logs:*).

# authenticated as alice, then:
SET logs:1 entry
OK

Allowed — matches the selector. But:

# authenticated as alice, then:
GET logs:1
NOPERM No permissions to access a key

Refused — even though alice can now write to logs:* via the selector, reading from logs:* matches neither her root permissions (cached:* only) nor her selector (set only, no get). Root and selector permissions don’t combine into a union; a command has to fully satisfy one or the other on its own.

Key-level read/write permissions

docker exec redis-lesson redis-cli ACL SETUSER reader on nopass "+@read" "%R~data:*"
# authenticated as reader, then:
GET data:1
(nil)
# authenticated as reader, then:
SET data:1 x
NOPERM User reader has no permissions to run the 'set' command

+@read grants the whole category of read-only commands; %R~data:* scopes that to a key pattern with explicitly read-only access. GET succeeds (returning nil because the key doesn’t exist — not a permissions failure); SET is refused outright, since set was never in the @read category to begin with.

Practice

1. Generate a strong random password the way ACL SETUSER expects.
docker exec redis-lesson redis-cli ACL GENPASS
fd6d2c60a18a01ca30ec39b5de36c235ced9b1b6d6a7cfdc4170a6e4d643cf25

A 256-bit random value as a 64-character hex string — meant to be pasted into an ACL SETUSER >password rule, not memorized by a human.

2. List every command in a category before granting it wholesale.
docker exec redis-lesson redis-cli ACL CAT transaction
multi
exec
watch
discard
unwatch

ACL CAT <category> shows exactly what +@transaction would actually grant — worth checking before adding a whole category to a restricted user, rather than assuming from the name alone.

3. Remove a user's ability to run a specific dangerous command while keeping the rest of a category.
docker exec redis-lesson redis-cli ACL SETUSER ops on nopass "+@all" "-flushall" "-flushdb"
printf "AUTH ops ''\nFLUSHALL\n" | docker exec -i redis-lesson redis-cli
OK
OK
NOPERM User ops has no permissions to run the 'flushall' command

+@all grants everything, then -flushall/-flushdb remove two specific commands — the additive-then-subtractive pattern for “trust this user with almost everything, except these few genuinely dangerous operations.”

Next: scripting — Lua’s EVAL versus the newer Functions API.

Frequently Asked Questions

What's actually different between a user's root permissions and a selector?
Root permissions are the base rule set defined directly on ACL SETUSER; a selector, written in parentheses, is a separate, independently-evaluated rule set attached to the same user. A command is allowed if it fully matches the root permissions OR fully matches any one selector — permissions don't combine across the two. A selector granting +set on logs:* doesn't help a GET on logs:* if neither the root nor that selector actually grants get.
Why did GET foo fail but GET cached:1234 succeed for the same user?
alice's root permissions included ~cached:* — a key pattern restricting which keys she can touch at all, regardless of command. foo doesn't match that pattern, so Redis refuses before even checking whether GET itself was allowed; cached:1234 matches, and get was explicitly granted, so it succeeds.
What does the long #hex string in ACL LIST mean?
It's the SHA-256 hash of the user's password — Redis never stores or displays the plaintext, only this hash, which is compared against a hash of whatever the client sends on AUTH. Passwords aren't meant to be human-memorable secrets here; ACL GENPASS exists specifically to generate strong random ones for this purpose.
What's the difference between +@read and a %R~ key permission?
+@read grants an entire category of commands (GET, MGET, STRLEN, and every other read-only command) with no restriction on which keys they can target. %R~data:* instead restricts to a key pattern with read-only access on those specific keys, regardless of which read command is used. They compose: a user with +@read and %R~data:* can only read, and only from keys matching data:*.
Is it safe to leave the default user as-is in production?
No — a fresh Redis install's default user is on, requires no password (nopass), and has +@all and ~* — every command against every key. That's fine for a throwaway learning container, but production Redis should set a strong password on the default user at minimum, and ideally define specific restricted users (like this lesson's alice) for anything that isn't a trusted administrator connection.