Redis Security: Authentication, TLS, and Network Isolation
A deep-dive into securing Redis deployments: ACL-based authentication, TLS transport encryption, network binding, dangerous command renaming, and cluster authentication.
Redis ships with no authentication and listens on all interfaces by default. That combination makes it one of the most frequently compromised databases on the internet — hundreds of thousands of exposed Redis instances serve as pivot points into production environments every year. This guide covers every layer of the Redis security model: authentication, transport encryption, network isolation, dangerous command control, and cluster authentication.
The Default Redis Security Problem
A vanilla Redis installation binds to 0.0.0.0:6379 and accepts any command from any connecting client with no password check. Redis's design philosophy has always prioritized speed inside a trusted network; the assumption is that Redis never reaches an untrusted surface. That assumption fails constantly.
Run a fresh Redis server and you can immediately:
redis-cli FLUSHALL # destroy all data
redis-cli CONFIG SET dir /etc/cron.d/ # write arbitrary files via RDB
redis-cli CONFIG SET dbfilename root # combined with BGSAVE = cron backdoor
The classic Redis compromise chain — pivot through CONFIG SET + BGSAVE to write SSH authorized_keys or cron jobs — has been well-documented since 2015. It still works against any Redis instance that skips authentication and network binding.
Authentication: requirepass vs ACL (Redis 6+)
The Old Way: requirepass
Before Redis 6, authentication was a single global password set in redis.conf:
requirepass your_strong_password_here
Clients authenticate with:
redis-cli -a your_strong_password_here PING
Or in application code:
const client = redis.createClient({
url: 'redis://:your_strong_password_here@localhost:6379'
});
requirepass has a critical limitation: every client — your application, your cache warming job, your analytics pipeline — gets the same credentials with full access to every command and every key. There's no way to grant read-only access to one service and read-write to another.
The Right Way: Access Control Lists (Redis 6+)
Redis 6 introduced a proper ACL system. Each user gets an independent username, password hash, enabled/disabled state, allowed commands, and allowed key patterns.
In redis.conf:
# Disable the default user (no password, full access)
user default off
# Read-only service account — can only GET/MGET/HGET keys matching cache:*
user cache_reader on >cache_reader_password ~cache:* +get +mget +hget +hgetall +lrange +smembers
# Application service account — full access to app:* keys, no dangerous commands
user app_service on >app_service_password ~app:* +@all -@dangerous -flushall -flushdb -config -debug
# Admin account — full access, only connect from localhost
user redis_admin on >admin_password ~* &* +@all
Apply without restart:
redis-cli ACL LOAD
Inspect the current ACL:
redis-cli ACL LIST
redis-cli ACL WHOAMI
redis-cli ACL CAT # list command categories
redis-cli ACL CAT dangerous # list dangerous commands
The @dangerous category covers commands like FLUSHALL, FLUSHDB, KEYS, CONFIG, DEBUG, SAVE, BGSAVE, BGREWRITEAOF, SHUTDOWN, and SLAVEOF. Blocking this category from application accounts is the single most impactful ACL change you can make.
Store ACLs in a separate file for cleaner management:
# redis.conf
aclfile /etc/redis/users.acl
TLS Transport Encryption
Redis 6 added native TLS support. Without it, all data — including your authentication credentials — travels in plaintext.
Generating Certificates
For production, use certificates signed by your internal CA or a public CA. For testing:
# Generate CA key and certificate
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/CN=Redis CA"
# Generate server key and CSR
openssl genrsa -out redis.key 4096
openssl req -new -key redis.key -out redis.csr \
-subj "/CN=redis.internal"
# Sign the server certificate
openssl x509 -req -days 365 -in redis.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out redis.crt
Redis TLS Configuration
# redis.conf
# Disable plaintext port, enable TLS port
port 0
tls-port 6380
# Certificate paths
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
# Require client certificates (mutual TLS)
tls-auth-clients yes
# Enforce TLS 1.2 minimum
tls-protocols "TLSv1.2 TLSv1.3"
# Strong cipher suites only
tls-ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256"
tls-ciphersuites "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256"
# Prefer server cipher order
tls-prefer-server-ciphers yes
Connect with TLS from the CLI:
redis-cli -p 6380 \
--tls \
--cert /etc/redis/tls/client.crt \
--key /etc/redis/tls/client.key \
--cacert /etc/redis/tls/ca.crt \
PING
Node.js with ioredis:
import Redis from 'ioredis';
import fs from 'fs';
const redis = new Redis({
host: 'redis.internal',
port: 6380,
tls: {
cert: fs.readFileSync('/etc/redis/tls/client.crt'),
key: fs.readFileSync('/etc/redis/tls/client.key'),
ca: fs.readFileSync('/etc/redis/tls/ca.crt'),
rejectUnauthorized: true,
},
username: 'app_service',
password: 'app_service_password',
});
Network Binding: Never 0.0.0.0
Redis must not bind to all interfaces unless every interface is trusted. Change redis.conf:
# Only listen on loopback and a specific private interface
bind 127.0.0.1 10.0.1.5
# Explicitly enable protected mode (default on, but be explicit)
protected-mode yes
protected-mode yes means Redis will refuse connections from remote IPs unless a password is set. It's a last-resort safety net — don't rely on it; use explicit binding.
For containerized deployments, use Docker network isolation instead of exposing the Redis port to the host:
# docker-compose.yml
services:
redis:
image: redis:7-alpine
command: redis-server /usr/local/etc/redis/redis.conf
volumes:
- ./redis.conf:/usr/local/etc/redis/redis.conf
networks:
- internal_network
# No 'ports:' section — Redis is not reachable from the host
app:
image: myapp:latest
environment:
REDIS_URL: redis://app_service:password@redis:6379
networks:
- internal_network
networks:
internal_network:
driver: bridge
internal: true # No external routing
Dangerous Command Renaming
Even with ACLs, you may want to rename or disable dangerous commands at the protocol level so they cannot be called even if an ACL is misconfigured. Use rename-command in redis.conf:
# Completely disable FLUSHALL and FLUSHDB
rename-command FLUSHALL ""
rename-command FLUSHDB ""
# Rename DEBUG to a random string — internal tooling still works if it knows the name
rename-command DEBUG "DEBUG_d7f3a91b2c"
# Disable CONFIG from the network (use redis-cli locally instead)
rename-command CONFIG ""
# Disable SHUTDOWN to prevent remote shutdown
rename-command SHUTDOWN ""
# Rename KEYS to something unusual (KEYS is O(N) and kills production)
rename-command KEYS "KEYS_UNSAFE_d91f"
Note: rename-command and ACL are complementary. ACL blocks commands per-user at authentication time; rename-command removes commands from the protocol entirely, regardless of user. Use both.
Cluster Authentication
Redis Cluster adds peer-to-peer communication between nodes. Without cluster authentication, any process on the network can join or interact with cluster nodes.
Cluster Password (Pre-Redis 7)
# All nodes must share the same requirepass and masterauth
requirepass cluster_shared_password
masterauth cluster_shared_password
TLS for Cluster (Redis 6+)
# redis.conf (each node)
tls-replication yes
tls-cluster yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
With tls-cluster yes, all intra-cluster gossip is encrypted. Nodes authenticate each other via the shared TLS certificate authority.
Redis Cluster with Kubernetes
When running Redis Cluster on Kubernetes, use network policies to restrict which pods can reach Redis node ports:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: redis-cluster-access
spec:
podSelector:
matchLabels:
app: redis
ingress:
- from:
- podSelector:
matchLabels:
redis-client: "true"
ports:
- port: 6379
- port: 16379 # Cluster bus port
Additional Hardening
Disable persistence if not needed: RDB and AOF files can contain sensitive data. If Redis is a pure cache, disable them:
save ""
appendonly no
Set a memory limit: Prevent Redis from consuming all system memory, which can cause OOM kills of other processes:
maxmemory 2gb
maxmemory-policy allkeys-lru
Enable slow log: Detect anomalous command patterns:
slowlog-log-slower-than 10000 # microseconds
slowlog-max-len 128
Audit with ACL LOG:
redis-cli ACL LOG # show recent ACL violations
redis-cli ACL LOG RESET # clear the log
Security Checklist
Before deploying Redis to production, verify each of these:
bindis set to specific IPs, not0.0.0.0protected-mode yes- Default user is disabled (
user default off) - Application accounts use ACL with minimal command sets
@dangerouscategory blocked for all non-admin accountsrequirepassor ACL password is at least 32 random characters- TLS is enabled with certificates from a trusted CA
FLUSHALL,FLUSHDB,CONFIG,DEBUG,SHUTDOWNare renamed or disabled- Redis port is not exposed to the public internet
- Redis process runs as a dedicated non-root user
maxmemoryis set to prevent runaway memory consumption- ACL LOG is enabled and monitored
The most common Redis breach starts with a forgotten exposed port and no password. Fix the network first, then layer ACLs and TLS on top. Defense in depth means that even if one control fails — a misconfigured firewall rule, a stolen credential — the attacker still can't reach production data.