Redis Cheatsheet

Persistence and Config

Use this Redis reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

RDB snapshots

An RDB file is a compact point-in-time dump of the whole dataset. Redis forks, the child writes the file, and the parent keeps serving.

CommandWhat it does
BGSAVE [SCHEDULE]Fork and snapshot in the background
SAVESnapshot on the main thread, blocking every client. Never in production
LASTSAVEUnix time of the last successful save
BGREWRITEAOFRewrite the AOF, compacting it
DEBUG RELOADSave and reload from disk, a diagnostic, not a backup
# redis.conf
save 3600 1        # after 3600s if at least 1 key changed
save 300 100       # after 300s if at least 100 keys changed
save 60 10000      # after 60s if at least 10000 keys changed
save ""            # DISABLE snapshotting entirely

dir              /var/lib/redis      # the working directory
dbfilename       dump.rdb            # the RDB filename inside dir
rdbcompression   yes                 # LZF-compress strings
rdbchecksum      yes                 # CRC64 footer, ~10 percent slower loads
stop-writes-on-bgsave-error yes      # fail loudly when saves break
redis-cli CONFIG GET save          # "3600 1 300 100 60 10000"
redis-cli BGSAVE
redis-cli LASTSAVE                 # compare before and after to confirm
redis-cli INFO persistence | grep -E 'rdb_(bgsave_in_progress|last_bgsave_status)'

The gotchas are all about the fork:

- The fork can double memory. Copy-on-write means the child shares pages until the parent writes them, so a write-heavy instance can approach 2× RSS during a save. Provision headroom and set vm.overcommit_memory = 1 on Linux or the fork itself can fail. - Losing everything since the last snapshot is the design. save 60 10000 means up to a minute of writes are gone after a hard kill. - stop-writes-on-bgsave-error yes makes Redis refuse writes once a background save fails. That is intentional, and it is the failure people misread as a Redis bug when the disk fills up.

AOF

The append-only file logs every write command, so recovery replays them. It is the durable option.

appendonly           yes
appendfsync          everysec       # always | everysec | no
appenddirname        "appendonlydir"   # 7.0+, a DIRECTORY not a file
appendfilename       "appendonly.aof"  # base name inside that directory
no-appendfsync-on-rewrite  no
auto-aof-rewrite-percentage 100      # rewrite when it doubles
auto-aof-rewrite-min-size   64mb
aof-use-rdb-preamble        yes      # the base file is RDB format, loads faster
aof-timestamp-enabled       no       # 7.0+, annotate for point-in-time recovery
appendfsyncDurabilityCost
alwaysEvery write is fsynced before the replySlowest, bounded by disk latency
everysecAt most 1 second of writes lost (default)Near-zero impact, the right default
noWhenever the OS decides, typically 30sFastest, weakest

AOF is a directory since 7.0, not a single file. The multi-part layout is appendonlydir/ containing a base file (RDB-encoded), one or more incremental files, and a .manifest that lists them.

appendonlydir/
  appendonly.aof.1.base.rdb      # snapshot at the last rewrite
  appendonly.aof.1.incr.aof      # commands since then
  appendonly.aof.manifest        # which files, in what order

This replaced the old in-place rewrite, which had to buffer new writes in memory during a rewrite and could spike badly. A 7.0 rewrite just writes a fresh base and starts a new incr file, so BGREWRITEAOF is far cheaper than it used to be.

redis-cli CONFIG SET appendonly yes     # enables it live, triggers a rewrite
redis-cli BGREWRITEAOF
redis-cli INFO persistence | grep -E 'aof_(enabled|rewrite_in_progress|last_write_status)'
redis-check-aof --fix appendonlydir/appendonly.aof.manifest   # truncated tail repair
redis-check-rdb dump.rdb

Choosing, and the honest trade

RDB onlyAOF onlyBoth (recommended)
Worst-case data lossSince the last snapshot, minutes1s with everysec, 0 with always1s
Restart time on a large datasetFast, it is a binary loadSlower, it replays commandsFast, the base is RDB
File sizeSmallestLarger, compacted by rewritesBoth files on disk
Good for backupsYes, one portable fileAwkward, it is a directoryUse the RDB half
Write throughput costFork pauses onlyContinuous appends plus fsyncBoth
Corruption blast radiusThe whole snapshotA truncated tail is repairableBest case

The default posture for a durable store is both on, with appendfsync everysec. Redis prefers the AOF on startup when it exists, so the RDB is your portable backup artifact and the AOF is your recovery mechanism.

For a pure cache, turn both off (save "", appendonly no). Persistence on a cache buys nothing and costs fork pauses on every save. That is what a throwaway instance is started with:

redis-server --port 6399 --save '' --appendonly no

And the blunt caveat: even appendfsync always gives you single-node durability, not transactional guarantees. Replication is asynchronous by default, so an acknowledged write can be lost when a primary fails over. Redis is not a database of record.

Memory limits

CONFIG SET maxmemory 2gb
CONFIG SET maxmemory-policy allkeys-lru     # default is noeviction
CONFIG GET maxmemory-samples                # 5, raise to ~10 for tighter LRU
CONFIG SET lazyfree-lazy-eviction yes       # free evicted keys off the main thread
SettingNote
maxmemory0 means unlimited, which means the OOM killer decides
maxmemory-policyDefault noeviction, so writes fail at the limit
maxmemory-samplesLRU and LFU are approximate, this is the sample size
maxmemory-clients7.0+, caps total client buffer memory
lazyfree-lazy-*eviction, expire, server-del, user-del, all worth yes

maxmemory counts the dataset plus buffers but not the copy-on-write overhead of a fork, so set it to roughly half of physical memory on a persisted instance. The eviction policy table is in the keys and expiry topic.

CONFIG

CommandWhat it does
CONFIG GET pattern [pattern ...]Read parameters, globs allowed
CONFIG SET p v [p v ...]Change at runtime, multiple pairs since 7.0
CONFIG REWRITEPersist the running config back into redis.conf
CONFIG RESETSTATZero the INFO stats and commandstats counters
redis-cli CONFIG GET 'maxmemory*'
redis-cli CONFIG SET maxmemory 2gb maxmemory-policy allkeys-lfu
redis-cli CONFIG REWRITE          # otherwise the change dies with the process

CONFIG SET is not persistent. Without CONFIG REWRITE the setting is gone on restart, and CONFIG REWRITE requires that the server was started with a config file. Typical locations are /etc/redis/redis.conf (Debian and Ubuntu), /etc/redis.conf (RHEL), and /opt/homebrew/etc/redis.conf (Homebrew on Apple silicon). Confirm with CONFIG GET dir plus INFO server | grep config_file.

Security

bind 127.0.0.1 -::1        # interfaces to listen on, NOT an allow-list
protected-mode yes          # refuse external clients when no auth is set
port 6379
tls-port 6380               # both can be open, or set port 0 for TLS only
requirepass "a-long-random-string"
rename-command FLUSHALL ""  # legacy blunt instrument, ACLs are better
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
tls-auth-clients yes        # require client certs

ACLs (6.0+) replace the single shared password with real users.

CommandWhat it does
ACL WHOAMIThe user this connection is authenticated as
ACL LISTEvery user's rule string
ACL GETUSER nameOne user's flags, keys, channels, and command permissions
ACL SETUSER name rules...Create or modify a user
ACL DELUSER nameRemove a user
ACL CAT [category]Command categories, or the commands in one
ACL GENPASS [bits]A cryptographically random password
ACL SAVE / ACL LOADPersist to or reload from aclfile
ACL LOG [count | RESET]Recent authentication and permission failures
redis-cli ACL WHOAMI       # "default"
redis-cli ACL LIST         # "user default on nopass sanitize-payload ~* &* +@all"

# A read-only app user scoped to one key prefix
redis-cli ACL SETUSER hu-reader on '>secret' '~hu:*' '&*' +@read +ping
# A worker that may also write its own queues
redis-cli ACL SETUSER hu-worker on '>secret2' '~hu:q:*' +@read +@write +@list
redis-cli ACL DELUSER hu-reader
redis-cli ACL LOG 5

Rule syntax: on/off enables the user, >pass adds a password and nopass allows any, ~pattern grants key access and %R~/%W~ split read from write (7.0+), &pattern grants pub/sub channels, +cmd/-cmd and +@category/-@category grant and revoke commands, and allkeys, allchannels, allcommands are the wildcards.

The default default user is nopass ~* &* +@all, which is why an internet-reachable Redis with no requirepass is fully open. protected-mode yes is the guard that saves you when you forget, and it only helps until someone sets a password or binds an interface. Never expose 6379 publicly.

Replication

REPLICAOF 10.0.0.1 6379      # make this node a replica (SLAVEOF is the old name)
REPLICAOF NO ONE             # promote to a primary
INFO replication             # role, connected_slaves, offsets, link status
WAIT numreplicas timeout_ms  # block until N replicas have the write, returns the count
replicaof 10.0.0.1 6379
masterauth "primary-password"
masteruser "replication-user"       # 6.0+, with ACLs
replica-read-only yes               # default, and keep it
repl-backlog-size 64mb              # bigger backlog, more partial resyncs
repl-diskless-sync yes
min-replicas-to-write 1             # refuse writes with too few replicas
min-replicas-max-lag 10

Replication is asynchronous. The primary acknowledges a write before any replica has it, so a failover can lose acknowledged writes. WAIT 1 100 blocks until at least one replica confirms and returns how many did, which narrows the window without closing it, because WAIT is not a consensus protocol and does not participate in failover.

redis-cli INFO replication | grep -E 'role|master_link_status|slave_repl_offset'

Replicas are read-only by default. They do not expire keys on their own, they wait for the primary's DEL, so a read from a replica can briefly return a logically expired value. A full resync forks and streams an RDB, which is why repl-backlog-size matters: a big enough backlog turns a brief disconnect into a cheap partial resync instead.

Sentinel

Sentinel gives you automatic failover, not sharding. A separate redis-sentinel process (run at least three, on distinct hosts) monitors one or more primaries. When a quorum agrees a primary is down, they elect a leader, promote a replica, and reconfigure the others. Clients ask a Sentinel for the current primary address rather than hardcoding it, so a Sentinel-aware client library is mandatory.

# sentinel.conf
sentinel monitor hu-primary 10.0.0.1 6379 2      # 2 = quorum
sentinel down-after-milliseconds hu-primary 5000
sentinel failover-timeout hu-primary 60000
sentinel parallel-syncs hu-primary 1
sentinel auth-pass hu-primary "primary-password"
redis-sentinel /etc/redis/sentinel.conf
redis-cli -p 26379 SENTINEL masters
redis-cli -p 26379 SENTINEL get-master-addr-by-name hu-primary
redis-cli -p 26379 SENTINEL failover hu-primary     # force one, for a drill

Quorum is how many Sentinels must agree the primary is down, and a majority of the Sentinel set must be reachable to actually authorize a failover, which is why an even number is a bad idea. Total memory is still one node's worth, since every replica holds a full copy.

Cluster

Cluster gives you sharding plus failover. The keyspace is divided into 16384 hash slots, each owned by one primary, and the slot is CRC16(key) mod 16384. Every node knows the full slot map and redirects a client that asks the wrong one.

redis-cli --cluster create 10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \
                           10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \
          --cluster-replicas 1
redis-cli -c -p 6379 CLUSTER INFO       # -c follows redirects
redis-cli CLUSTER NODES
redis-cli CLUSTER SLOTS                 # or CLUSTER SHARDS, 7.0+
redis-cli CLUSTER KEYSLOT 'hu:user:1042'
redis-cli --cluster check 10.0.0.1:6379
redis-cli --cluster reshard 10.0.0.1:6379

The constraint that shapes your data model is multi-key commands.

(error) CROSSSLOT Keys in request don't hash to the same slot

Anything touching several keys at once (MGET, SINTER, ZUNIONSTORE, a Lua script, MULTI) requires every key to live in the same slot. The fix is a hash tag: only the substring inside {} is hashed.

CLUSTER KEYSLOT 'hu:{1042}:profile'    # same slot as ...
CLUSTER KEYSLOT 'hu:{1042}:sessions'   # ... this one

Other cluster facts: numbered databases do not exist, so SELECT 1 errors and only db 0 is available. Regular pub/sub broadcasts to every node, which is why sharded pub/sub (SPUBLISH) exists. A MOVED redirect means the slot has moved permanently and the client should update its map, while ASK means a single key is mid-migration. And Cluster is still asynchronous replication, so a failover can lose writes.

StandaloneSentinelCluster
Automatic failoverNoYesYes
ShardingNoNoYes, 16384 slots
Total memoryOne nodeOne nodeSum of the primaries
Multi-key commandsUnrestrictedUnrestrictedSame slot only
Numbered databases1616Only db 0
Client support neededAnySentinel-awareCluster-aware
Minimum nodes13 Sentinels plus 1 primary and 1 replica3 primaries, 6 with replicas

Reach for Sentinel when one node holds your data comfortably and you only want availability. Reach for Cluster when the dataset or the write throughput exceeds one node, and accept the multi-key constraint in exchange.