REDIS HARDENING
Baseline settings for a Redis server: listen only where needed, keep protected mode on, named ACL users instead of one shared password, least-privilege rules, the dangerous admin features left locked, TLS, and checks from outside. Checked against the Redis documentation and the redis.conf shipped with Redis 8, and the examples were run on Redis 8.10 (October 2026). Redis is built for trusted clients on a trusted network. Never expose the port to the internet: one FLUSHALL deletes every key.
FIND THE CONFIG, SEE WHAT IS EXPOSED#
redis-cli INFO server | grep -E 'redis_version|config_file|tcp_port' redis-cli CONFIG GET bind redis-cli CONFIG GET protected-mode ss -ltnp | grep redis # which addresses it listens on # No config_file in INFO = started without a redis.conf, so the # built-in defaults apply (see the Docker note below).
LISTEN ONLY WHERE NEEDED#
# redis.conf (the shipped file already has these two lines): bind 127.0.0.1 -::1 protected-mode yes # To serve other hosts, add only the address they use, then firewall # port 6379 so that only the application hosts reach it: bind 127.0.0.1 -::1 192.0.2.10 # Protected mode: while it is on and the default user has no password, # Redis accepts only loopback and Unix socket clients, whatever "bind" # says; others get "DENIED Redis is running in protected mode ...". # It is a safety net, not access control: set passwords, keep it on. # The official Docker image is built with protected mode OFF and, with # no config file, listens on every interface with no password. Never # publish its port (-p 6379:6379) without a redis.conf and ACL users.
ACL USERS, NOT ONE SHARED PASSWORD#
# Out of the box: "user default on nopass sanitize-payload ~* &* +@all", # so every new connection is logged in with full rights. requirepass # only sets a password on that default user. Use named users and # switch the default user off. In redis.conf: aclfile /etc/redis/users.acl # Use either aclfile or "user" lines in redis.conf, not both: Redis # refuses to start with both. requirepass is ignored once aclfile is set. # /etc/redis/users.acl (readable only by the redis user): user default off user admin on #<sha256-of-password> ~* &* +@all user app on #<sha256-of-password> ~app:* &app:* +@all -@dangerous user report on #<sha256-of-password> ~* resetchannels -@all +@read +@connection # "#<hash>" stores the SHA-256 of the password, not the password. # Make the hash: printf %s 'the-password' | sha256sum # ">password" also works, but keeps the clear text in the file. -- Generate a strong secret (64 hex characters, 256 bits): ACL GENPASS -- Reload the file after editing (the old rules stay if it has an error): ACL LOAD -- Write changes made with ACL SETUSER back to the file: ACL SAVE # Log in as a named user: redis-cli --user app --askpass
LEAST PRIVILEGE#
# New users start with no rights at all: off, no password, -@all. # Rules apply left to right; ACL SETUSER adds to the existing rules. # ~app:* keys the user may touch (%R~ read only, %W~ write only) # &app:* Pub/Sub channels (none by default since Redis 7) # -@dangerous blocks FLUSHALL, FLUSHDB, KEYS, DEBUG, MIGRATE, RESTORE, # INFO, SAVE, REPLICAOF, CONFIG GET/SET/REWRITE, # CLIENT LIST/KILL/PAUSE and more (ACL CAT dangerous) # Key patterns do NOT limit commands that take no key: a user with # ~tenant1:* +@all can still run FLUSHALL. Remove those explicitly: ACL SETUSER tenant1 on >strong-password ~tenant1:* +@all -flushall -flushdb -swapdb # Redis 8 bundles modules (JSON, Search, Bloom, TimeSeries ...): their # commands are in the normal categories too, so +@read allows JSON.GET # and FT.SEARCH, and +@write allows JSON.SET. They also have their own # categories (@json, @search, @timeseries, @bloom ...): see ACL CAT. # (The ACL docs page still says categories exclude module commands; # Redis 8.10 behaves as above. Check with ACL DRYRUN, never assume.) -- Check a rule without running the command: ACL DRYRUN app FLUSHALL -- Review every user: ACL LIST
KEEP THE ADMIN FEATURES LOCKED (REDIS 7+)#
# redis.conf defaults, keep them: enable-protected-configs no # CONFIG SET dir / dbfilename refused enable-debug-command no # DEBUG refused enable-module-command no # MODULE LOAD refused # "local" allows them from loopback only; "yes" from anywhere. # Why: CONFIG SET dir + dbfilename + SAVE lets a client write a file # anywhere the redis user can write (a classic takeover path). # rename-command CONFIG "" is DEPRECATED: block commands with ACL # rules (-@dangerous, -config) instead. # Run Redis as an unprivileged "redis" user, never as root.
TLS#
# redis.conf (TLS is off by default): port 0 # turn off the plain-text port tls-port 6379 tls-cert-file /etc/redis/tls/redis.crt tls-key-file /etc/redis/tls/redis.key tls-ca-cert-file /etc/redis/tls/ca.crt # Defaults: only TLSv1.2 and TLSv1.3, and clients must present a # certificate signed by tls-ca-cert-file (tls-auth-clients yes). # Set tls-auth-clients optional or no only if passwords are the # only client check you want. tls-replication yes # on replicas, TLS to the primary redis-cli -h db.example.com -p 6379 --tls --cacert ca.crt \ --cert client.crt --key client.key --user app --askpass # redis-cli checks the CA but NOT the host name in the certificate: # use a private CA that signs only your Redis servers. # Optional: log clients in from their certificate, no password needed # (CN=app is logged in as user "app"; no such user = default user): tls-auth-clients-user CN # Replicas and cluster nodes on a shared CA: also require a known name # in the peer certificate with tls-expected-peer-name.
AUDIT AND MONITOR#
-- Failed logins and denied commands (who, what, when): ACL LOG 10 ACL LOG RESET -- Connected clients and the user each one runs as: CLIENT LIST # ACL LOG keeps the last 128 entries (acllog-max-len). # Lua: never build a script body from untrusted input; pass values # as KEYS / ARGV to EVAL instead.
CHECK FROM OUTSIDE#
redis-cli -h db.example.com -p 6379 PING # PONG = open to anyone: fix it now # NOAUTH ... = a password is required (default user off or has one) # DENIED ... = protected mode is the only thing blocking access # no answer = firewalled (the goal for hosts that need no access) redis-cli -h db.example.com -p 6379 --user app --askpass FLUSHALL # expect: NOPERM ... no permissions to run the 'flushall' command
REDIS HARDENING =============== Baseline settings for a Redis server: listen only where needed, keep protected mode on, named ACL users instead of one shared password, least-privilege rules, the dangerous admin features left locked, TLS, and checks from outside. Checked against the Redis documentation and the redis.conf shipped with Redis 8, and the examples were run on Redis 8.10 (October 2026). Redis is built for trusted clients on a trusted network. Never expose the port to the internet: one FLUSHALL deletes every key. FIND THE CONFIG, SEE WHAT IS EXPOSED ------------------------------------ redis-cli INFO server | grep -E 'redis_version|config_file|tcp_port' redis-cli CONFIG GET bind redis-cli CONFIG GET protected-mode ss -ltnp | grep redis # which addresses it listens on # No config_file in INFO = started without a redis.conf, so the # built-in defaults apply (see the Docker note below). LISTEN ONLY WHERE NEEDED ------------------------ # redis.conf (the shipped file already has these two lines): bind 127.0.0.1 -::1 protected-mode yes # To serve other hosts, add only the address they use, then firewall # port 6379 so that only the application hosts reach it: bind 127.0.0.1 -::1 192.0.2.10 # Protected mode: while it is on and the default user has no password, # Redis accepts only loopback and Unix socket clients, whatever "bind" # says; others get "DENIED Redis is running in protected mode ...". # It is a safety net, not access control: set passwords, keep it on. # The official Docker image is built with protected mode OFF and, with # no config file, listens on every interface with no password. Never # publish its port (-p 6379:6379) without a redis.conf and ACL users. ACL USERS, NOT ONE SHARED PASSWORD ---------------------------------- # Out of the box: "user default on nopass sanitize-payload ~* &* +@all", # so every new connection is logged in with full rights. requirepass # only sets a password on that default user. Use named users and # switch the default user off. In redis.conf: aclfile /etc/redis/users.acl # Use either aclfile or "user" lines in redis.conf, not both: Redis # refuses to start with both. requirepass is ignored once aclfile is set. # /etc/redis/users.acl (readable only by the redis user): user default off user admin on #<sha256-of-password> ~* &* +@all user app on #<sha256-of-password> ~app:* &app:* +@all -@dangerous user report on #<sha256-of-password> ~* resetchannels -@all +@read +@connection # "#<hash>" stores the SHA-256 of the password, not the password. # Make the hash: printf %s 'the-password' | sha256sum # ">password" also works, but keeps the clear text in the file. -- Generate a strong secret (64 hex characters, 256 bits): ACL GENPASS -- Reload the file after editing (the old rules stay if it has an error): ACL LOAD -- Write changes made with ACL SETUSER back to the file: ACL SAVE # Log in as a named user: redis-cli --user app --askpass LEAST PRIVILEGE --------------- # New users start with no rights at all: off, no password, -@all. # Rules apply left to right; ACL SETUSER adds to the existing rules. # ~app:* keys the user may touch (%R~ read only, %W~ write only) # &app:* Pub/Sub channels (none by default since Redis 7) # -@dangerous blocks FLUSHALL, FLUSHDB, KEYS, DEBUG, MIGRATE, RESTORE, # INFO, SAVE, REPLICAOF, CONFIG GET/SET/REWRITE, # CLIENT LIST/KILL/PAUSE and more (ACL CAT dangerous) # Key patterns do NOT limit commands that take no key: a user with # ~tenant1:* +@all can still run FLUSHALL. Remove those explicitly: ACL SETUSER tenant1 on >strong-password ~tenant1:* +@all -flushall -flushdb -swapdb # Redis 8 bundles modules (JSON, Search, Bloom, TimeSeries ...): their # commands are in the normal categories too, so +@read allows JSON.GET # and FT.SEARCH, and +@write allows JSON.SET. They also have their own # categories (@json, @search, @timeseries, @bloom ...): see ACL CAT. # (The ACL docs page still says categories exclude module commands; # Redis 8.10 behaves as above. Check with ACL DRYRUN, never assume.) -- Check a rule without running the command: ACL DRYRUN app FLUSHALL -- Review every user: ACL LIST KEEP THE ADMIN FEATURES LOCKED (REDIS 7+) ----------------------------------------- # redis.conf defaults, keep them: enable-protected-configs no # CONFIG SET dir / dbfilename refused enable-debug-command no # DEBUG refused enable-module-command no # MODULE LOAD refused # "local" allows them from loopback only; "yes" from anywhere. # Why: CONFIG SET dir + dbfilename + SAVE lets a client write a file # anywhere the redis user can write (a classic takeover path). # rename-command CONFIG "" is DEPRECATED: block commands with ACL # rules (-@dangerous, -config) instead. # Run Redis as an unprivileged "redis" user, never as root. TLS --- # redis.conf (TLS is off by default): port 0 # turn off the plain-text port tls-port 6379 tls-cert-file /etc/redis/tls/redis.crt tls-key-file /etc/redis/tls/redis.key tls-ca-cert-file /etc/redis/tls/ca.crt # Defaults: only TLSv1.2 and TLSv1.3, and clients must present a # certificate signed by tls-ca-cert-file (tls-auth-clients yes). # Set tls-auth-clients optional or no only if passwords are the # only client check you want. tls-replication yes # on replicas, TLS to the primary redis-cli -h db.example.com -p 6379 --tls --cacert ca.crt \ --cert client.crt --key client.key --user app --askpass # redis-cli checks the CA but NOT the host name in the certificate: # use a private CA that signs only your Redis servers. # Optional: log clients in from their certificate, no password needed # (CN=app is logged in as user "app"; no such user = default user): tls-auth-clients-user CN # Replicas and cluster nodes on a shared CA: also require a known name # in the peer certificate with tls-expected-peer-name. AUDIT AND MONITOR ----------------- -- Failed logins and denied commands (who, what, when): ACL LOG 10 ACL LOG RESET -- Connected clients and the user each one runs as: CLIENT LIST # ACL LOG keeps the last 128 entries (acllog-max-len). # Lua: never build a script body from untrusted input; pass values # as KEYS / ARGV to EVAL instead. CHECK FROM OUTSIDE ------------------ redis-cli -h db.example.com -p 6379 PING # PONG = open to anyone: fix it now # NOAUTH ... = a password is required (default user off or has one) # DENIED ... = protected mode is the only thing blocking access # no answer = firewalled (the goal for hosts that need no access) redis-cli -h db.example.com -p 6379 --user app --askpass FLUSHALL # expect: NOPERM ... no permissions to run the 'flushall' command
Defensive reference on CyberRamen. Offensive / red-team sheets live on OffensiveRamen.com.