Redis ACLs are surprisingly permissive by default, which means that out of the box, any client that can connect to your Redis instance can potentially access and modify all data and execute all commands. This is rarely what you want, especially in production environments where you have multiple applications or services interacting with the same Redis instance. The core problem Redis ACLs solve is providing a robust, granular way to enforce the principle of least privilege for each client connection.
Let’s see this in action. Imagine a simple Redis setup where we want one application to only be able to read keys prefixed with user:, and another application to only be able to write to keys prefixed with cache:.
First, we need to enable ACLs in our redis.conf. If you don’t have it already, create one:
# redis.conf
aclfile /etc/redis/redis.acl
requirepass your_very_strong_root_password
Then, restart Redis. Now, create your ACL file (e.g., /etc/redis/redis.acl):
# /etc/redis/redis.acl
user default off # Disable the default 'allkeys allcommands' user
user reader on # Enable the 'reader' user
# Grant read-only access to all keys starting with 'user:'
+@read * ~user:*
# Allow specific read commands if needed, but @read covers most
# +GET +MGET +SCAN ...
user writer on # Enable the 'writer' user
# Grant read and write access to all keys starting with 'cache:'
+@write * ~cache:*
# Allow specific write commands
+SET +DEL +INCR ...
Now, when clients connect, they’ll need to authenticate using these ACLs.
Here’s how you’d connect as the reader user from redis-cli:
redis-cli -u redis://reader:reader_password@localhost:6379
(Note: You’d set reader_password when creating the user in Redis, which we’ll cover next.)
And as the writer user:
redis-cli -u redis://writer:writer_password@localhost:6379
The mental model of Redis ACLs revolves around users, commands, and key patterns.
- Users: These are named entities that represent a client connection. Each user has a password (or can be passwordless, though not recommended for security), and a set of permissions.
- Permissions: These define what a user can do. Permissions are composed of:
- Commands: Specific Redis commands like
GET,SET,DEL. You can grant or deny individual commands. Redis also provides command categories (e.g.,@read,@write,@set) for easier management. - Keys: This is where the fine-grained control comes in. You can restrict access to specific keys or patterns of keys.
- Commands: Specific Redis commands like
- Key Patterns: These are glob-style patterns (like
user:*orsession_id_*) that match keys in your Redis database. - Access Control Lists (ACLs): The overall system that maps users to their permissions. The
aclfiledirective inredis.confpoints to the file where these rules are defined.
Let’s dive deeper into the commands. You can manage ACLs directly from redis-cli as well, which is often more dynamic than editing the aclfile and reloading.
First, connect as a user with SETUSER privileges (usually the default user, or a user you’ve explicitly granted this):
redis-cli -a your_very_strong_root_password
To create the reader and writer users from redis-cli:
> ACL SETUSER reader on >reader_password ~* +@read
OK
> ACL SETUSER writer on >writer_password ~* +@write
OK
Here’s what those commands mean:
ACL SETUSER <username>: The command to define or modify a user.on: Enables the user.offwould disable it.>reader_password: Sets the password for thereaderuser.~*: This part defines the key pattern.~*means all keys. We’ll refine this.+@read: Grants all commands in the@readcategory.
Now, let’s make those key restrictions more specific. The ~pattern syntax in ACLs is crucial.
> ACL SETUSER reader on >reader_password ~user:* +@read
OK
> ACL SETUSER writer on >writer_password ~cache:* +@write
OK
In ~user:*, the * is a wildcard matching any sequence of characters. So, user:123, user:abc, user:profile:data would all be matched.
If you try to GET a key like user:100 as the writer user, it will succeed. If you try to GET a key like cache:session:xyz as the writer user, it will succeed. However, if the reader user tries to SET a key like user:100, they will get an (error) Operation not permitted error, because +@read does not include write commands, and the key pattern ~user:* doesn’t inherently grant write permissions. Similarly, if the writer user tries to GET a key like user:100, they will get a permission error because their permissions are restricted to keys matching ~cache:*.
The most surprising thing about Redis ACLs is that you can grant permissions to specific commands and specific key patterns independently, and the intersection of these permissions is what a user actually gets. It’s not just an "allow all keys for this command" or "allow all commands for this key." You can have a user who can GET any key, but can only SET keys prefixed with temp:, and cannot DEL any key at all.
Consider this:
> ACL SETUSER limited_get_set on >limited_password ~user:* +GET ~temp:* +SET
OK
This user, limited_get_set, can GET any key (because ~user:* is applied to +GET, and GET is a read command, and all keys are implicitly available for GET unless explicitly denied. The ~user:* here is actually a filter on what keys they can write to if they had write permissions. For read commands, the ~pattern acts as a restriction if no key pattern is specified, or as a filter if a key pattern is specified. Let’s clarify this: +GET ~user:* means "allow the GET command, but only on keys matching user:*". +SET ~temp:* means "allow the SET command, but only on keys matching temp:*". If you want to allow GET on all keys, and SET only on temp:*, you’d do:
> ACL SETUSER limited_get_set on >limited_password +GET ~temp:* +SET
OK
This user can GET any key. They can SET any key that matches temp:*. If they try to SET a key like cache:123, they’ll get a permission error. The ~user:* pattern in the initial example was misleading if the intent was to allow GET on all keys. The general rule is: if a command is granted, and a key pattern is specified for that command, the command is only allowed on keys matching that pattern. If no key pattern is specified for a command, it’s allowed on all keys unless there’s a global key restriction or a specific denial.
The nuance of how ~pattern interacts with +command and ~* is where most confusion lies. When you specify +COMMAND ~PATTERN, that command is allowed only on keys matching PATTERN. If you want a command to be allowed on all keys, you omit the ~PATTERN part for that specific command grant. The ~* at the end of an ACL SETUSER command is a default key pattern for any commands that don’t have an explicit key pattern specified. So, +@read ~* grants all read commands on all keys. +@write ~cache:* grants all write commands only on keys matching cache:*.
The next concept you’ll likely encounter is how to deny specific commands or keys, or how to manage ACLs across multiple Redis instances using replication.