Redis Security Hardening in Practice: Unauthorized Access and RCE Defense Config
Redis defaults allow unauthenticated access; attackers get RCE by writing crontab or SSH keys. Full hardening checklist: bind address, strong auth, dangerous-command disable, and an automated check script.
Redis hardening is easily skipped in middleware defense: Redis defaults to no auth and listens on 0.0.0.0:6379. Once public, attackers get remote command execution (RCE) by writing crontab, SSH public keys, or loading a malicious module via replication. Full config path from detection to hardening follows.
Scope
- Production self-hosted Redis 4.x–7.x standalone or Sentinel cluster
- Redis in Docker / Kubernetes with the port mapped to the host or the public internet
- Hosts where a scan found 6379 open and risk must be closed fast
Prerequisites
- Server root or sudo; can edit Redis config and restart the service
- Confirm the Redis command set the app depends on (check with developers before disabling commands)
Mechanism
Redis unauthorized-access chains fall into four types:
- Write crontab: use `CONFIG SET dir/dbfilename` to drop a malicious job into `/var/spool/cron` for a reverse shell.
- Write SSH public key: write `authorized_keys` into `~/.ssh` for passwordless login.
- Replication RCE: `SLAVEOF` makes the target a replica of the attacker's Redis, then load a malicious `.so` module (affects some 4.0–6.2 versions).
- Data theft: `KEYS *` / `GET` dumps cache and session data, causing leak and account takeover.
Only prerequisite: 6379 reachable with no auth. Hardening core is three layers: network isolation + strong auth + disable dangerous commands.
Procedure
1. Detect unauthorized access
`# Local detection (simulate attacker)
redis-cli -h 127.0.0.1 -p 6379 ping
# Returns PONG with no AUTH error → unauthorized access risk exists
# Remote detection (use the target public IP; authorized testing only)
nc -zv <target-ip> 6379
redis-cli -h <target-ip> -p 6379 info server | head -5`
2. Bind address: no public listen
`# Edit /etc/redis/redis.conf (or /etc/redis.conf)
# Listen only on loopback and internal addresses; do not leave 0.0.0.0
bind 127.0.0.1 192.168.1.10
# Explicitly tighten the default path that protected-mode would still allow
protected-mode yes
# When listening only on a local Unix socket, TCP can be fully disabled
port 6379`
bind must be a concrete internal IP; do not comment out the bind line. On Docker, use `-p 127.0.0.1:6379:6379`, not bare `-p 6379:6379`.
3. Enable strong auth
`# Generate a high-strength random password (32+ characters)
openssl rand -hex 32 # e.g. output c9a7... (save for later)
# Write to config
requirepass "paste-the-generated-password-here"
# Verify after restart
redis-cli -a 'your-password' ping # Returns PONG
redis-cli ping # should return (error) NOAUTH`
Note: the `requirepass` password is readable via `CONFIG GET`; disable `CONFIG` as well (step 4).
4. Disable dangerous commands
Rename or disable high-risk commands (disable recommended):
`# Append at the end of redis.conf
rename-command CONFIG ""
rename-command FLUSHALL ""
rename-command FLUSHDB ""
rename-command EVAL ""
rename-command EVALSHA ""
rename-command SLAVEOF ""
rename-command REPLICAOF ""
rename-command DEBUG ""
rename-command KEYS ""`
If the app actually needs `EVAL` (Lua) and similar, rename to a random string instead:
`rename-command EVAL "ev4l_9f2k1q"`
5. Persistence and replication security
`# Tighten persistence file permissions (default directory for the 6379-port RDB file)
chmod 700 /var/lib/redis
chmod 600 /var/lib/redis/dump.rdb
# Enable authentication for master-replica replication; set on the replica
replicaof 192.168.1.10 6379
masterauth "your-password"
# Disable dangerous module loading (if unused)
# Comment out loadmodule-related lines`
6. Firewall and automated checks
`# iptables: allow 6379 from the internal network only
iptables -A INPUT -p tcp --dport 6379 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -p tcp --dport 6379 -j DROP
# Daily check script (crontab every 10 minutes for unauthorized status)
cat > /usr/local/bin/redis_check.sh <<'EOF'
#!/bin/bash
timeout 2 redis-cli -h 127.0.0.1 -p 6379 ping | grep -q PONG && echo "$(date) [WARN] Redis unauthorized access possible!" >> /var/log/redis_check.log
EOF
chmod +x /usr/local/bin/redis_check.sh
echo "*/10 * * * * root /usr/local/bin/redis_check.sh" >> /etc/crontab`
Verification
`# 1. Auth in effect
redis-cli ping # NOAUTH
redis-cli -a your-password ping # PONG
# 2. Dangerous commands disabled
redis-cli -a your-password CONFIG GET dir # (error) ERR unknown command
# 3. Port exposure
ss -tlnp | grep 6379 # should show only 127.0.0.1 / internal IP
# 4. External scan (authorized environment)
nmap -p 6379 --script redis-info <target-ip>`
FAQ
FAQ1: After disabling CONFIG, the app cannot set expiry policy?
Runtime key commands (`EXPIRE`, `TTL`, etc.) are unaffected; only server-level `CONFIG` is gone. Change config by editing the file and restarting; no runtime `CONFIG SET`.
FAQ2: Why still configure ACL on 6.0+?
After 6.0 ACL, `requirepass` is only the default-user password. Create a dedicated account per app with the minimal command set:
`# Create an app account limited to the specified keyspace
ACL SETUSER appuser on >app_pass ~app:* +get +set +del +expire +ttl +exists +ping
# Verify
redis-cli -u redis://appuser:app_pass@127.0.0.1:6379/0 ping`
FAQ3: Does rename-command break cluster or Sentinel communication?
Yes. Cluster nodes use `CLUSTER`; Sentinel uses `SLAVEOF/CONFIG` and other internals. Disabling them breaks failover. For cluster/Sentinel, stick to bind + requirepass + ACL; contain dangerous commands with network-layer ACL (cloud security group allowlist internal only).
Summary
Apply four layers: network isolation → strong auth → command reduction → runtime monitoring. Bind to internal IPs, 32-char random password, disable CONFIG/EVAL/SLAVEOF and other dangerous commands, plus a check script. That cuts the unauthorized-access and replication-RCE chains. On newer versions, also enable ACL for least privilege.