Chapter 40

Production Configuration and Security Hardening

Chapter 40: Production Configuration and Security Hardening

Redis ships with defaults optimized for developer convenience, not production security. A default installation is bound to 0.0.0.0, has no password, exposes all commands, and applies no memory limits. This chapter closes every gap: network isolation, authentication and fine-grained authorization via ACL, dangerous command protection, TLS mutual authentication, kernel tuning, and a complete 25-point production security checklist.


1. Network Binding

1.1 The bind Directive

# redis.conf

# Bind only to the loopback and private network interface.
# Never bind to 0.0.0.0 or a public IP address.
bind 127.0.0.1 192.168.1.100

# protected-mode: when no password is configured and bind covers more than
# loopback, Redis refuses all non-loopback connections.
# Keep this on even with a password as a second line of defense.
protected-mode yes

# Standard port โ€” consider changing to reduce automated scanner exposure
port 6379

# Unix socket: highest performance for same-host communication
unixsocket /var/run/redis/redis.sock
unixsocketperm 770
# Verify only internal addresses are listening
ss -tlnp | grep 6379
# Safe:    127.0.0.1:6379   192.168.1.100:6379
# Danger:  0.0.0.0:6379     :::6379

2. Authentication: requirepass and ACL

2.1 requirepass (pre-Redis 6.0)

# redis.conf
requirepass "YourStr0ng!Password#2024"

# Client authentication
AUTH YourStr0ng!Password#2024

# Password requirements:
# - Minimum 32 characters
# - Mix of uppercase, lowercase, digits, symbols
# - Not in any dictionary; not based on company name or dates

ACL replaces the single shared password with a full user management system: named users, hashed passwords, allowed key patterns, and command-level permissions.

# Syntax: ACL SETUSER <name> [on|off] [>password] [~key-pattern] [+command]

# Read-only application user โ€” can only access keys matching cache:*
ACL SETUSER readonly_user on >ReadPass#123 ~cache:* +@read

# Read-write application user with restricted key namespaces
ACL SETUSER app_user on >AppPass#456 \
    ~cache:* ~session:* \
    +get +set +del +expire +hset +hget +hgetall +ttl +exists

# Administrator (full access)
ACL SETUSER admin_user on >AdminPass#789 ~* &* +@all

# Pub/Sub-only user
ACL SETUSER pubsub_user on >PubSubPass ~* &events:* +subscribe +publish +unsubscribe

# Inspect the current ACL list
ACL LIST
# user default off nopass ~* &* +@all
# user app_user on #<sha256_hash> ~cache:* ~session:* +get +set ...

# Check the current session's identity
ACL WHOAMI

# Dry-run: can user X execute command Y on key Z?
ACL DRYRUN app_user get cache:user:1001     # โ†’ OK or error

# Load ACL file (after editing it on disk)
ACL LOAD

# Persist current in-memory ACL state to file
ACL SAVE
# redis.conf โ€” point to an external ACL file
aclfile /etc/redis/users.acl

# /etc/redis/users.acl
user default off
user app_user on #5e884898da28047151d0e56f8dc629277b7b1ad36601f39a6e4c6a40f5e0a16 \
    ~cache:* ~session:* +get +set +del +expire +hset +hget +hgetall +ttl +exists
user admin_user on #<admin_sha256> ~* &* +@all

2.3 Built-In Command Categories

+@all          # every command
+@read         # GET, HGET, LRANGE, SMEMBERS, ZRANGE, etc.
+@write        # SET, HSET, LPUSH, SADD, ZADD, etc.
+@string       # string-type commands only
+@hash         # hash-type commands only
+@list         # list-type commands only
+@set          # set-type commands only
+@sortedset    # sorted set commands only
+@geo          # GEO commands
+@stream       # Stream commands
+@pubsub       # SUBSCRIBE, PUBLISH, etc.
+@admin        # CONFIG, INFO, DEBUG, etc.
+@dangerous    # FLUSHALL, KEYS, DEBUG, SHUTDOWN, etc.
-@dangerous    # remove dangerous commands from an otherwise full grant

3. Renaming Dangerous Commands

# redis.conf

# Disable entirely (empty string)
rename-command FLUSHDB   ""
rename-command FLUSHALL  ""
rename-command DEBUG     ""
rename-command KEYS      ""
rename-command SHUTDOWN  ""

# Rename to a secret string (internal tooling can still use it)
rename-command CONFIG    "CONFIG_a8f3d2c1b4e5"
rename-command DEBUG     "DEBUG_x9y8z7w6v5u4"

# IMPORTANT: rename-command is NOT supported in Cluster mode.
# In Cluster, rely on ACL -@dangerous instead.
# Adapting client code when CONFIG is renamed
CONFIG_CMD = "CONFIG_a8f3d2c1b4e5"

def redis_config_get(param: str):
    return r.execute_command(CONFIG_CMD, "GET", param)

4. TLS Mutual Authentication (mTLS)

4.1 Generating Certificates

# Step 1: Create a Certificate Authority (CA)
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
    -subj "/C=US/O=YourCompany/CN=Redis Internal CA"

# Step 2: Server certificate
openssl genrsa -out redis.key 2048
openssl req -new -key redis.key -out redis.csr \
    -subj "/C=US/O=YourCompany/CN=redis.internal"
openssl x509 -req -days 365 \
    -in redis.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out redis.crt

# Step 3: Client certificate (required for mTLS)
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
    -subj "/C=US/O=YourCompany/CN=redis-client"
openssl x509 -req -days 365 \
    -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
    -out client.crt

4.2 Redis TLS Configuration

# redis.conf

# Disable plaintext port
port 0

# Enable TLS port
tls-port 6380

# Certificate files
tls-cert-file    /etc/redis/certs/redis.crt
tls-key-file     /etc/redis/certs/redis.key
tls-ca-cert-file /etc/redis/certs/ca.crt

# Require client certificates (mutual TLS)
tls-auth-clients yes

# Minimum protocol version
tls-protocols "TLSv1.2 TLSv1.3"

# Strong cipher suites
tls-ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
tls-ciphersuites "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256"
tls-prefer-server-ciphers yes

# Replicate and cluster traffic also encrypted
tls-replication yes
tls-cluster yes

4.3 Client Connections with TLS

import redis

r = redis.Redis(
    host="redis.internal",
    port=6380,
    ssl=True,
    ssl_certfile="/path/to/client.crt",
    ssl_keyfile="/path/to/client.key",
    ssl_ca_certs="/path/to/ca.crt",
    ssl_check_hostname=True,
)
print(r.ping())   # True
# redis-cli with TLS
redis-cli -h redis.internal -p 6380 \
    --tls \
    --cert  /path/to/client.crt \
    --key   /path/to/client.key \
    --cacert /path/to/ca.crt \
    ping
# PONG

5. maxmemory Sizing and Configuration

5.1 Sizing Formula

maxmemory = (physical RAM  -  OS reserve  -  other processes) ร— 0.75โ€“0.85

Example โ€” 32 GB server, Redis only:
  OS + monitoring agents: 2.5 GB
  Available for Redis: 29.5 GB
  maxmemory = 29.5 ร— 0.80 โ‰ˆ 23 GB

Why not 100%?
  Redis forks on BGSAVE. Fork uses Copy-on-Write (COW):
  the child shares pages with the parent until they diverge.
  On a write-heavy workload, RSS can nearly double during the fork window.
  Always reserve 15โ€“25% of maxmemory for COW overhead.

5.2 Configuration

# redis.conf
maxmemory 23gb
maxmemory-policy allkeys-lru     # evict least-recently-used keys across all keys
maxmemory-samples 10             # sample 10 candidates per eviction (default 5; higher = more accurate)

# Eviction policy selection guide:
# noeviction       โ€” write errors when full (use for primary data store)
# allkeys-lru      โ€” evict LRU key from the whole keyspace (general cache)
# allkeys-lfu      โ€” evict least-frequently-used key (hot-spot preservation)
# allkeys-random   โ€” random eviction (rarely optimal)
# volatile-lru     โ€” evict LRU among keys with a TTL set
# volatile-lfu     โ€” evict LFU among keys with a TTL set
# volatile-random  โ€” random eviction among keys with a TTL set
# volatile-ttl     โ€” evict the key with the shortest remaining TTL

6. Kernel Parameter Tuning

# /etc/sysctl.conf

# Allow overcommit so BGSAVE fork never fails due to memory accounting
# 0 = heuristic (default)  1 = always allow  2 = strict
vm.overcommit_memory = 1

# Expand TCP connection queues for high-concurrency environments
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# TCP keepalive โ€” detect dead connections faster
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3

# Increase global file descriptor limit
fs.file-max = 1000000

# Apply immediately
sysctl -p
# Transparent Huge Pages (THP) โ€” MUST be disabled
# THP causes COW latency spikes during fork and increased fragmentation
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

# Persist across reboots (systemd approach)
cat > /etc/systemd/system/disable-thp.service << 'EOF'
[Unit]
Description=Disable Transparent Huge Pages
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=redis.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled'
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag'

[Install]
WantedBy=multi-user.target
EOF
systemctl enable disable-thp

# Verify
cat /sys/kernel/mm/transparent_hugepage/enabled
# Expected: always madvise [never]
# Per-process file descriptor limits for the redis user
# /etc/security/limits.conf
redis soft nofile 65535
redis hard nofile 65535

# Or via systemd unit
# /etc/systemd/system/redis.service (override)
[Service]
LimitNOFILE=65535
# redis.conf (matching kernel tuning)
tcp-backlog 511          # must be <= net.core.somaxconn
tcp-keepalive 300        # Redis-level keepalive (seconds)
timeout 300              # close idle client connections after 5 minutes

7. Persistence Security

# redis.conf

# RDB snapshots
save 3600 1              # save if 1 write occurred in the last hour
save 300 100             # save if 100 writes in 5 minutes
save 60 10000            # save if 10,000 writes in 1 minute
stop-writes-on-bgsave-error yes    # fail writes if RDB save fails (safety net)
rdbcompression yes
rdbchecksum yes          # CRC64 checksum validation on load
dbfilename dump.rdb
dir /var/lib/redis

# AOF (write-ahead log โ€” recommended for production)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec     # fsync every second: good balance of safety and performance
# appendfsync always     # fsync on every write: maximum durability, lowest throughput
# appendfsync no         # OS decides: highest throughput, possible data loss

no-appendfsync-on-rewrite no       # keep fsyncing during AOF rewrite
auto-aof-rewrite-percentage 100    # trigger rewrite when AOF doubles in size
auto-aof-rewrite-min-size 64mb    # but only if AOF is at least 64 MB
aof-use-rdb-preamble yes          # hybrid RDB+AOF format for fast loads

# Directory permissions
# chown redis:redis /var/lib/redis
# chmod 750 /var/lib/redis

8. Audit Logging Options

# Redis has no built-in audit log. Choose the approach that fits your environment.

# Option 1: Short-term MONITOR capture (impacts performance significantly)
redis-cli monitor >> /var/log/redis/audit.log &

# Option 2: Proxy-layer auditing (recommended for production)
# Deploy Envoy sidecar or Twemproxy to intercept and log all commands.
# Supports filtering by user, IP address, or command type.

# Option 3: Keyspace notifications with an external subscriber
CONFIG SET notify-keyspace-events "KEA"    # all key events
redis-cli subscribe '__keyevent@0__:set' '__keyevent@0__:del'

9. Production Security Checklist (25 Items)

Network Isolation (5 items)

โœ… 1.  bind is set to loopback + private network interfaces only; 0.0.0.0 is never used.
โœ… 2.  Firewall rules: port 6379/6380 is open only to application server IPs; closed to the public internet.
โœ… 3.  Redis and application servers share the same VPC / security group; no public network routing.
โœ… 4.  Sentinel port 26379 and Cluster bus ports 17000+ have their own restrictive firewall rules.
โœ… 5.  Same-host applications use Unix Socket instead of TCP (performance + security).

Authentication and Authorization (5 items)

โœ… 6.  Authentication is mandatory โ€” requirepass or ACL with a strong password; no anonymous access.
โœ… 7.  ACL enforces least privilege: each application user is restricted to its own key namespace.
โœ… 8.  The default user is disabled: ACL SETUSER default off.
โœ… 9.  Different services use different Redis users; sensitive services use separate logical databases.
โœ… 10. Passwords and credentials are stored in a secrets manager (HashiCorp Vault, AWS Secrets Manager); never hard-coded.

Dangerous Command Protection (4 items)

โœ… 11. rename-command disables FLUSHDB, FLUSHALL, DEBUG, and SHUTDOWN.
โœ… 12. rename-command disables KEYS; all code uses SCAN.
โœ… 13. ACL includes -@dangerous for all application users.
โœ… 14. Destructive operations (FLUSHALL, DROP DATABASE equivalent) require dual approval in production.

Transport Security (2 items)

โœ… 15. TLS is enabled in production; minimum TLS 1.2, TLS 1.3 preferred; mTLS for sensitive environments.
โœ… 16. Replica and Cluster inter-node communication also uses TLS (tls-replication yes, tls-cluster yes).

Memory and Configuration (4 items)

โœ… 17. maxmemory is set with a 15โ€“25% buffer for BGSAVE COW overhead; eviction policy is configured.
โœ… 18. timeout is set (e.g., 300 seconds) to auto-close idle connections and prevent connection leaks.
โœ… 19. Transparent Huge Pages are disabled: echo never > .../transparent_hugepage/enabled (system-wide).
โœ… 20. vm.overcommit_memory = 1 prevents BGSAVE fork failure under high memory pressure.

Persistence and Backup (3 items)

โœ… 21. AOF is enabled (appendonly yes) with appendfsync everysec for production data safety.
โœ… 22. RDB snapshots are shipped to remote object storage (S3 / OSS) daily; 7-day retention minimum.
โœ… 23. Backup restoration drill is performed quarterly; RTO and RPO targets are documented and validated.

Monitoring and Alerting (2 items)

โœ… 24. redis_exporter + Prometheus + Grafana are deployed; all critical alerts from Chapter 39 are active.
โœ… 25. slowlog-log-slower-than is set to 5000 ฮผs (5 ms); slow query alerts and weekly reviews are in place.

10. Complete Production redis.conf Template

# ===== NETWORK =====
bind 127.0.0.1 192.168.1.100
port 6379
protected-mode yes
tcp-backlog 511
timeout 300
tcp-keepalive 300

# ===== TLS (uncomment to enable) =====
# port 0
# tls-port 6380
# tls-cert-file    /etc/redis/certs/redis.crt
# tls-key-file     /etc/redis/certs/redis.key
# tls-ca-cert-file /etc/redis/certs/ca.crt
# tls-auth-clients yes
# tls-protocols "TLSv1.2 TLSv1.3"
# tls-replication yes
# tls-cluster yes

# ===== AUTHENTICATION =====
# requirepass "YourStr0ng!Password#2024"   # replaced by ACL below
aclfile /etc/redis/users.acl

# ===== DANGEROUS COMMAND PROTECTION =====
rename-command FLUSHDB   ""
rename-command FLUSHALL  ""
rename-command DEBUG     ""
rename-command KEYS      ""
rename-command SHUTDOWN  "SHUTDOWN_9x8y7z"
rename-command CONFIG    "CONFIG_a1b2c3d4"

# ===== MEMORY =====
maxmemory 23gb
maxmemory-policy allkeys-lru
maxmemory-samples 10

# ===== PERSISTENCE =====
save 3600 1
save 300 100
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis

appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes

# ===== PERFORMANCE =====
hz 10
dynamic-hz yes
aof-rewrite-incremental-fsync yes
rdb-save-incremental-fsync yes

# ===== SLOW LOG =====
slowlog-log-slower-than 5000
slowlog-max-len 256

# ===== LOGGING =====
loglevel notice
logfile /var/log/redis/redis.log

# ===== PROCESS =====
daemonize yes
pidfile /var/run/redis/redis.pid

# ===== CLIENT LIMITS =====
maxclients 10000

Chapter Summary

Rate this chapter
4.5  / 5  (3 ratings)

๐Ÿ’ฌ Comments